Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP Semconv migration Part3 Server - v1.24.0 support #5401

Merged
merged 4 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 41 additions & 28 deletions instrumentation/net/http/otelhttp/internal/semconv/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import (
"fmt"
"net/http"
"os"
"strings"

"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
Expand All @@ -19,40 +21,51 @@
WriteError error
}

type HTTPServer interface {
// RequestTraceAttrs returns trace attributes for an HTTP request received by a
// server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue
type HTTPServer struct {
duplicate bool
}

// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response.
//
// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted.
ResponseTraceAttrs(ResponseTelemetry) []attribute.KeyValue
// RequestTraceAttrs returns trace attributes for an HTTP request received by a
// server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue {
if s.duplicate {
return append(oldHTTPServer{}.RequestTraceAttrs(server, req), newHTTPServer{}.RequestTraceAttrs(server, req)...)
}
return oldHTTPServer{}.RequestTraceAttrs(server, req)
}

// Route returns the attribute for the route.
Route(string) attribute.KeyValue
// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response.
//
// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted.
func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
if s.duplicate {
return append(oldHTTPServer{}.ResponseTraceAttrs(resp), newHTTPServer{}.ResponseTraceAttrs(resp)...)
}
return oldHTTPServer{}.ResponseTraceAttrs(resp)

Check warning on line 58 in instrumentation/net/http/otelhttp/internal/semconv/env.go

View check run for this annotation

Codecov / codecov/patch

instrumentation/net/http/otelhttp/internal/semconv/env.go#L56-L58

Added lines #L56 - L58 were not covered by tests
}

// var warnOnce = sync.Once{}
// Route returns the attribute for the route.
func (s HTTPServer) Route(route string) attribute.KeyValue {
return oldHTTPServer{}.Route(route)
}

func NewHTTPServer() HTTPServer {
// TODO (#5331): Detect version based on environment variable OTEL_HTTP_CLIENT_COMPATIBILITY_MODE.
// TODO (#5331): Add warning of use of a deprecated version of Semantic Versions.
return oldHTTPServer{}
env := strings.ToLower(os.Getenv("OTEL_HTTP_CLIENT_COMPATIBILITY_MODE"))
return HTTPServer{duplicate: env == "http/dup"}
}

// ServerStatus returns a span status code and message for an HTTP status code
Expand Down
42 changes: 42 additions & 0 deletions instrumentation/net/http/otelhttp/internal/semconv/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@

import (
"net"
"net/http"
"strconv"
"strings"

"go.opentelemetry.io/otel/attribute"
semconvNew "go.opentelemetry.io/otel/semconv/v1.24.0"
)

// splitHostPort splits a network address hostport of the form "host",
Expand Down Expand Up @@ -47,3 +51,41 @@
}
return host, int(p)
}

func requiredHTTPPort(https bool, port int) int { // nolint:revive
if https {
if port > 0 && port != 443 {
return port
}
} else {
if port > 0 && port != 80 {
return port
}
}
return -1

Check warning on line 65 in instrumentation/net/http/otelhttp/internal/semconv/util.go

View check run for this annotation

Codecov / codecov/patch

instrumentation/net/http/otelhttp/internal/semconv/util.go#L65

Added line #L65 was not covered by tests
}

func serverClientIP(xForwardedFor string) string {
if idx := strings.Index(xForwardedFor, ","); idx >= 0 {
xForwardedFor = xForwardedFor[:idx]

Check warning on line 70 in instrumentation/net/http/otelhttp/internal/semconv/util.go

View check run for this annotation

Codecov / codecov/patch

instrumentation/net/http/otelhttp/internal/semconv/util.go#L70

Added line #L70 was not covered by tests
}
return xForwardedFor
}

func netProtocol(proto string) (name string, version string) {
name, version, _ = strings.Cut(proto, "/")
name = strings.ToLower(name)
return name, version
}

var methodLookup = map[string]attribute.KeyValue{
http.MethodConnect: semconvNew.HTTPRequestMethodConnect,
http.MethodDelete: semconvNew.HTTPRequestMethodDelete,
http.MethodGet: semconvNew.HTTPRequestMethodGet,
http.MethodHead: semconvNew.HTTPRequestMethodHead,
http.MethodOptions: semconvNew.HTTPRequestMethodOptions,
http.MethodPatch: semconvNew.HTTPRequestMethodPatch,
http.MethodPost: semconvNew.HTTPRequestMethodPost,
http.MethodPut: semconvNew.HTTPRequestMethodPut,
http.MethodTrace: semconvNew.HTTPRequestMethodTrace,
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import (

type oldHTTPServer struct{}

var _ HTTPServer = oldHTTPServer{}

// RequestTraceAttrs returns trace attributes for an HTTP request received by a
// server.
//
Expand Down
197 changes: 197 additions & 0 deletions instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"

import (
"net/http"
"strings"

"go.opentelemetry.io/otel/attribute"
semconvNew "go.opentelemetry.io/otel/semconv/v1.24.0"
)

type newHTTPServer struct{}

// TraceRequest returns trace attributes for an HTTP request received by a
// server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
func (n newHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue {
count := 3 // ServerAddress, Method, Scheme

var host string
var p int
if server == "" {
host, p = splitHostPort(req.Host)
} else {
// Prioritize the primary server name.
host, p = splitHostPort(server)
if p < 0 {
MadVikingGod marked this conversation as resolved.
Show resolved Hide resolved
_, p = splitHostPort(req.Host)

Check warning on line 43 in instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go

View check run for this annotation

Codecov / codecov/patch

instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go#L41-L43

Added lines #L41 - L43 were not covered by tests
}
}

hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
count++
}

method, methodOriginal := n.method(req.Method)
if methodOriginal != (attribute.KeyValue{}) {
count++

Check warning on line 54 in instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go

View check run for this annotation

Codecov / codecov/patch

instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go#L54

Added line #L54 was not covered by tests
}

scheme := n.scheme(req.TLS != nil)

if peer, peerPort := splitHostPort(req.RemoteAddr); peer != "" {
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
// file-path that would be interpreted with a sock family.
count++
if peerPort > 0 {
count++
}
}

useragent := req.UserAgent()
if useragent != "" {
count++
}

clientIP := serverClientIP(req.Header.Get("X-Forwarded-For"))
if clientIP != "" {
count++
}

if req.URL != nil && req.URL.Path != "" {
count++
}

protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" && protoName != "http" {
count++

Check warning on line 84 in instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go

View check run for this annotation

Codecov / codecov/patch

instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go#L84

Added line #L84 was not covered by tests
}
if protoVersion != "" {
count++
}

attrs := make([]attribute.KeyValue, 0, count)
attrs = append(attrs,
semconvNew.ServerAddress(host),
method,
scheme,
)

if hostPort > 0 {
attrs = append(attrs, semconvNew.ServerPort(hostPort))
}
if methodOriginal != (attribute.KeyValue{}) {
attrs = append(attrs, methodOriginal)

Check warning on line 101 in instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go

View check run for this annotation

Codecov / codecov/patch

instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go#L101

Added line #L101 was not covered by tests
}

if peer, peerPort := splitHostPort(req.RemoteAddr); peer != "" {
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
// file-path that would be interpreted with a sock family.
attrs = append(attrs, semconvNew.NetworkPeerAddress(peer))
if peerPort > 0 {
attrs = append(attrs, semconvNew.NetworkPeerPort(peerPort))
}
}

if useragent := req.UserAgent(); useragent != "" {
attrs = append(attrs, semconvNew.UserAgentOriginal(useragent))
}

if clientIP != "" {
attrs = append(attrs, semconvNew.ClientAddress(clientIP))
}

if req.URL != nil && req.URL.Path != "" {
attrs = append(attrs, semconvNew.URLPath(req.URL.Path))
}

if protoName != "" && protoName != "http" {
attrs = append(attrs, semconvNew.NetworkProtocolName(protoName))

Check warning on line 126 in instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go

View check run for this annotation

Codecov / codecov/patch

instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go#L126

Added line #L126 was not covered by tests
}
if protoVersion != "" {
attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion))
}

return attrs
}

func (n newHTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{}

Check warning on line 137 in instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go

View check run for this annotation

Codecov / codecov/patch

instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go#L137

Added line #L137 was not covered by tests
}
if attr, ok := methodLookup[method]; ok {
return attr, attribute.KeyValue{}
}

orig := semconvNew.HTTPRequestMethodOriginal(method)
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
return attr, orig
}
return semconvNew.HTTPRequestMethodGet, orig
}

func (n newHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive
if https {
return semconvNew.URLScheme("https")

Check warning on line 152 in instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go

View check run for this annotation

Codecov / codecov/patch

instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go#L152

Added line #L152 was not covered by tests
}
return semconvNew.URLScheme("http")
}

// TraceResponse returns trace attributes for telemetry from an HTTP response.
//
// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted.
func (n newHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
var count int

if resp.ReadBytes > 0 {
count++
}
if resp.WriteBytes > 0 {
count++
}
if resp.StatusCode > 0 {
count++
}

attributes := make([]attribute.KeyValue, 0, count)

if resp.ReadBytes > 0 {
attributes = append(attributes,
semconvNew.HTTPRequestBodySize(int(resp.ReadBytes)),
)
}
if resp.WriteBytes > 0 {
attributes = append(attributes,
semconvNew.HTTPResponseBodySize(int(resp.WriteBytes)),
)
}
if resp.StatusCode > 0 {
attributes = append(attributes,
semconvNew.HTTPResponseStatusCode(resp.StatusCode),
)
}

return attributes
}

// Route returns the attribute for the route.
func (n newHTTPServer) Route(route string) attribute.KeyValue {
return semconvNew.HTTPRoute(route)

Check warning on line 196 in instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go

View check run for this annotation

Codecov / codecov/patch

instrumentation/net/http/otelhttp/internal/semconv/v1.24.0.go#L195-L196

Added lines #L195 - L196 were not covered by tests
}
Loading
Loading