chore: migrate to gitea
Some checks failed
golangci-lint / lint (push) Failing after 21s
Test / test (push) Failing after 2m17s

This commit is contained in:
2026-01-27 01:40:31 +01:00
parent a9bca767a9
commit 1a27ed5274
3163 changed files with 1216358 additions and 1529 deletions

View File

@@ -0,0 +1,80 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/request/body_wrapper.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package request provides types and functionality to handle HTTP request
// handling.
package request // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
import (
"io"
"sync"
)
var _ io.ReadCloser = &BodyWrapper{}
// BodyWrapper wraps a http.Request.Body (an io.ReadCloser) to track the number
// of bytes read and the last error.
type BodyWrapper struct {
io.ReadCloser
OnRead func(n int64) // must not be nil
mu sync.Mutex
read int64
err error
}
// NewBodyWrapper creates a new BodyWrapper.
//
// The onRead attribute is a callback that will be called every time the data
// is read, with the number of bytes being read.
func NewBodyWrapper(body io.ReadCloser, onRead func(int64)) *BodyWrapper {
return &BodyWrapper{
ReadCloser: body,
OnRead: onRead,
}
}
// Read reads the data from the io.ReadCloser, and stores the number of bytes
// read and the error.
func (w *BodyWrapper) Read(b []byte) (int, error) {
n, err := w.ReadCloser.Read(b)
n1 := int64(n)
w.updateReadData(n1, err)
w.OnRead(n1)
return n, err
}
func (w *BodyWrapper) updateReadData(n int64, err error) {
w.mu.Lock()
defer w.mu.Unlock()
w.read += n
if err != nil {
w.err = err
}
}
// Close closes the io.ReadCloser.
func (w *BodyWrapper) Close() error {
return w.ReadCloser.Close()
}
// BytesRead returns the number of bytes read up to this point.
func (w *BodyWrapper) BytesRead() int64 {
w.mu.Lock()
defer w.mu.Unlock()
return w.read
}
// Error returns the last error.
func (w *BodyWrapper) Error() error {
w.mu.Lock()
defer w.mu.Unlock()
return w.err
}

View File

@@ -0,0 +1,10 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
// Generate request package:
//go:generate gotmpl --body=../../../../../../internal/shared/request/body_wrapper.go.tmpl "--data={}" --out=body_wrapper.go
//go:generate gotmpl --body=../../../../../../internal/shared/request/body_wrapper_test.go.tmpl "--data={}" --out=body_wrapper_test.go
//go:generate gotmpl --body=../../../../../../internal/shared/request/resp_writer_wrapper.go.tmpl "--data={}" --out=resp_writer_wrapper.go
//go:generate gotmpl --body=../../../../../../internal/shared/request/resp_writer_wrapper_test.go.tmpl "--data={}" --out=resp_writer_wrapper_test.go

View File

@@ -0,0 +1,122 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/request/resp_writer_wrapper.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
import (
"net/http"
"sync"
)
var _ http.ResponseWriter = &RespWriterWrapper{}
// RespWriterWrapper wraps a http.ResponseWriter in order to track the number of
// bytes written, the last error, and to catch the first written statusCode.
// TODO: The wrapped http.ResponseWriter doesn't implement any of the optional
// types (http.Hijacker, http.Pusher, http.CloseNotifier, etc)
// that may be useful when using it in real life situations.
type RespWriterWrapper struct {
http.ResponseWriter
OnWrite func(n int64) // must not be nil
mu sync.RWMutex
written int64
statusCode int
err error
wroteHeader bool
}
// NewRespWriterWrapper creates a new RespWriterWrapper.
//
// The onWrite attribute is a callback that will be called every time the data
// is written, with the number of bytes that were written.
func NewRespWriterWrapper(w http.ResponseWriter, onWrite func(int64)) *RespWriterWrapper {
return &RespWriterWrapper{
ResponseWriter: w,
OnWrite: onWrite,
statusCode: http.StatusOK, // default status code in case the Handler doesn't write anything
}
}
// Write writes the bytes array into the [ResponseWriter], and tracks the
// number of bytes written and last error.
func (w *RespWriterWrapper) Write(p []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
if !w.wroteHeader {
w.writeHeader(http.StatusOK)
}
n, err := w.ResponseWriter.Write(p)
n1 := int64(n)
w.OnWrite(n1)
w.written += n1
w.err = err
return n, err
}
// WriteHeader persists initial statusCode for span attribution.
// All calls to WriteHeader will be propagated to the underlying ResponseWriter
// and will persist the statusCode from the first call.
// Blocking consecutive calls to WriteHeader alters expected behavior and will
// remove warning logs from net/http where developers will notice incorrect handler implementations.
func (w *RespWriterWrapper) WriteHeader(statusCode int) {
w.mu.Lock()
defer w.mu.Unlock()
w.writeHeader(statusCode)
}
// writeHeader persists the status code for span attribution, and propagates
// the call to the underlying ResponseWriter.
// It does not acquire a lock, and therefore assumes that is being handled by a
// parent method.
func (w *RespWriterWrapper) writeHeader(statusCode int) {
if !w.wroteHeader {
w.wroteHeader = true
w.statusCode = statusCode
}
w.ResponseWriter.WriteHeader(statusCode)
}
// Flush implements [http.Flusher].
func (w *RespWriterWrapper) Flush() {
w.mu.Lock()
defer w.mu.Unlock()
if !w.wroteHeader {
w.writeHeader(http.StatusOK)
}
if f, ok := w.ResponseWriter.(http.Flusher); ok {
f.Flush()
}
}
// BytesWritten returns the number of bytes written.
func (w *RespWriterWrapper) BytesWritten() int64 {
w.mu.RLock()
defer w.mu.RUnlock()
return w.written
}
// StatusCode returns the HTTP status code that was sent.
func (w *RespWriterWrapper) StatusCode() int {
w.mu.RLock()
defer w.mu.RUnlock()
return w.statusCode
}
// Error returns the last error.
func (w *RespWriterWrapper) Error() error {
w.mu.RLock()
defer w.mu.RUnlock()
return w.err
}

View File

@@ -0,0 +1,323 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/env.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
import (
"context"
"fmt"
"net/http"
"os"
"strings"
"sync"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
)
// OTelSemConvStabilityOptIn is an environment variable.
// That can be set to "http/dup" to keep getting the old HTTP semantic conventions.
const OTelSemConvStabilityOptIn = "OTEL_SEMCONV_STABILITY_OPT_IN"
type ResponseTelemetry struct {
StatusCode int
ReadBytes int64
ReadError error
WriteBytes int64
WriteError error
}
type HTTPServer struct {
duplicate bool
// Old metrics
requestBytesCounter metric.Int64Counter
responseBytesCounter metric.Int64Counter
serverLatencyMeasure metric.Float64Histogram
// New metrics
requestBodySizeHistogram metric.Int64Histogram
responseBodySizeHistogram metric.Int64Histogram
requestDurationHistogram metric.Float64Histogram
}
// 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, opts RequestTraceAttrsOpts) []attribute.KeyValue {
attrs := CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts)
if s.duplicate {
return OldHTTPServer{}.RequestTraceAttrs(server, req, attrs)
}
return attrs
}
func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue {
if s.duplicate {
return []attribute.KeyValue{
OldHTTPServer{}.NetworkTransportAttr(network),
CurrentHTTPServer{}.NetworkTransportAttr(network),
}
}
return []attribute.KeyValue{
CurrentHTTPServer{}.NetworkTransportAttr(network),
}
}
// 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 {
attrs := CurrentHTTPServer{}.ResponseTraceAttrs(resp)
if s.duplicate {
return OldHTTPServer{}.ResponseTraceAttrs(resp, attrs)
}
return attrs
}
// Route returns the attribute for the route.
func (s HTTPServer) Route(route string) attribute.KeyValue {
return CurrentHTTPServer{}.Route(route)
}
// Status returns a span status code and message for an HTTP status code
// value returned by a server. Status codes in the 400-499 range are not
// returned as errors.
func (s HTTPServer) Status(code int) (codes.Code, string) {
if code < 100 || code >= 600 {
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
}
if code >= 500 {
return codes.Error, ""
}
return codes.Unset, ""
}
type ServerMetricData struct {
ServerName string
ResponseSize int64
MetricData
MetricAttributes
}
type MetricAttributes struct {
Req *http.Request
StatusCode int
AdditionalAttributes []attribute.KeyValue
}
type MetricData struct {
RequestSize int64
// The request duration, in milliseconds
ElapsedTime float64
}
var (
metricAddOptionPool = &sync.Pool{
New: func() interface{} {
return &[]metric.AddOption{}
},
}
metricRecordOptionPool = &sync.Pool{
New: func() interface{} {
return &[]metric.RecordOption{}
},
}
)
func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
if s.requestDurationHistogram != nil && s.requestBodySizeHistogram != nil && s.responseBodySizeHistogram != nil {
attributes := CurrentHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes)
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
*recordOpts = append(*recordOpts, o)
s.requestBodySizeHistogram.Record(ctx, md.RequestSize, *recordOpts...)
s.responseBodySizeHistogram.Record(ctx, md.ResponseSize, *recordOpts...)
s.requestDurationHistogram.Record(ctx, md.ElapsedTime/1000.0, o)
*recordOpts = (*recordOpts)[:0]
metricRecordOptionPool.Put(recordOpts)
}
if s.duplicate && s.requestBytesCounter != nil && s.responseBytesCounter != nil && s.serverLatencyMeasure != nil {
attributes := OldHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes)
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
addOpts := metricAddOptionPool.Get().(*[]metric.AddOption)
*addOpts = append(*addOpts, o)
s.requestBytesCounter.Add(ctx, md.RequestSize, *addOpts...)
s.responseBytesCounter.Add(ctx, md.ResponseSize, *addOpts...)
s.serverLatencyMeasure.Record(ctx, md.ElapsedTime, o)
*addOpts = (*addOpts)[:0]
metricAddOptionPool.Put(addOpts)
}
}
// hasOptIn returns true if the comma-separated version string contains the
// exact optIn value.
func hasOptIn(version, optIn string) bool {
for _, v := range strings.Split(version, ",") {
if strings.TrimSpace(v) == optIn {
return true
}
}
return false
}
func NewHTTPServer(meter metric.Meter) HTTPServer {
env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn))
duplicate := hasOptIn(env, "http/dup")
server := HTTPServer{
duplicate: duplicate,
}
server.requestBodySizeHistogram, server.responseBodySizeHistogram, server.requestDurationHistogram = CurrentHTTPServer{}.createMeasures(meter)
if duplicate {
server.requestBytesCounter, server.responseBytesCounter, server.serverLatencyMeasure = OldHTTPServer{}.createMeasures(meter)
}
return server
}
type HTTPClient struct {
duplicate bool
// old metrics
requestBytesCounter metric.Int64Counter
responseBytesCounter metric.Int64Counter
latencyMeasure metric.Float64Histogram
// new metrics
requestBodySize metric.Int64Histogram
requestDuration metric.Float64Histogram
}
func NewHTTPClient(meter metric.Meter) HTTPClient {
env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn))
duplicate := hasOptIn(env, "http/dup")
client := HTTPClient{
duplicate: duplicate,
}
client.requestBodySize, client.requestDuration = CurrentHTTPClient{}.createMeasures(meter)
if duplicate {
client.requestBytesCounter, client.responseBytesCounter, client.latencyMeasure = OldHTTPClient{}.createMeasures(meter)
}
return client
}
// RequestTraceAttrs returns attributes for an HTTP request made by a client.
func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
attrs := CurrentHTTPClient{}.RequestTraceAttrs(req)
if c.duplicate {
return OldHTTPClient{}.RequestTraceAttrs(req, attrs)
}
return attrs
}
// ResponseTraceAttrs returns metric attributes for an HTTP request made by a client.
func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
attrs := CurrentHTTPClient{}.ResponseTraceAttrs(resp)
if c.duplicate {
return OldHTTPClient{}.ResponseTraceAttrs(resp, attrs)
}
return attrs
}
func (c HTTPClient) Status(code int) (codes.Code, string) {
if code < 100 || code >= 600 {
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
}
if code >= 400 {
return codes.Error, ""
}
return codes.Unset, ""
}
func (c HTTPClient) ErrorType(err error) attribute.KeyValue {
return CurrentHTTPClient{}.ErrorType(err)
}
type MetricOpts struct {
measurement metric.MeasurementOption
addOptions metric.AddOption
}
func (o MetricOpts) MeasurementOption() metric.MeasurementOption {
return o.measurement
}
func (o MetricOpts) AddOptions() metric.AddOption {
return o.addOptions
}
func (c HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts {
opts := map[string]MetricOpts{}
attributes := CurrentHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
opts["new"] = MetricOpts{
measurement: set,
addOptions: set,
}
if c.duplicate {
attributes := OldHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
opts["old"] = MetricOpts{
measurement: set,
addOptions: set,
}
}
return opts
}
func (s HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) {
if s.requestBodySize == nil || s.requestDuration == nil {
// This will happen if an HTTPClient{} is used instead of NewHTTPClient().
return
}
s.requestBodySize.Record(ctx, md.RequestSize, opts["new"].MeasurementOption())
s.requestDuration.Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption())
if s.duplicate {
s.requestBytesCounter.Add(ctx, md.RequestSize, opts["old"].AddOptions())
s.latencyMeasure.Record(ctx, md.ElapsedTime, opts["old"].MeasurementOption())
}
}
func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, opts map[string]MetricOpts) {
if s.responseBytesCounter == nil {
// This will happen if an HTTPClient{} is used instead of NewHTTPClient().
return
}
s.responseBytesCounter.Add(ctx, responseData, opts["old"].AddOptions())
}
func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue {
attrs := CurrentHTTPClient{}.TraceAttributes(host)
if s.duplicate {
return OldHTTPClient{}.TraceAttributes(host, attrs)
}
return attrs
}

View File

@@ -0,0 +1,14 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
// Generate semconv package:
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/bench_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=bench_test.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/env.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=env.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/env_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=env_test.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/httpconv.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=httpconv.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/httpconv_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=httpconv_test.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=util.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=util_test.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/v1.20.0.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=v1.20.0.go

View File

@@ -0,0 +1,573 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/httpconv.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package semconv provides OpenTelemetry semantic convention types and
// functionality.
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
import (
"fmt"
"net/http"
"reflect"
"slices"
"strconv"
"strings"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/noop"
semconvNew "go.opentelemetry.io/otel/semconv/v1.26.0"
)
type RequestTraceAttrsOpts struct {
// If set, this is used as value for the "http.client_ip" attribute.
HTTPClientIP string
}
type CurrentHTTPServer struct{}
// 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 (n CurrentHTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []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 {
_, p = SplitHostPort(req.Host)
}
}
hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
count++
}
method, methodOriginal := n.method(req.Method)
if methodOriginal != (attribute.KeyValue{}) {
count++
}
scheme := n.scheme(req.TLS != nil)
peer, peerPort := SplitHostPort(req.RemoteAddr)
if 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++
}
// For client IP, use, in order:
// 1. The value passed in the options
// 2. The value in the X-Forwarded-For header
// 3. The peer address
clientIP := opts.HTTPClientIP
if clientIP == "" {
clientIP = serverClientIP(req.Header.Get("X-Forwarded-For"))
if clientIP == "" {
clientIP = peer
}
}
if clientIP != "" {
count++
}
if req.URL != nil && req.URL.Path != "" {
count++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" && protoName != "http" {
count++
}
if protoVersion != "" {
count++
}
route := httpRoute(req.Pattern)
if route != "" {
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)
}
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 != "" {
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))
}
if protoVersion != "" {
attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion))
}
if route != "" {
attrs = append(attrs, n.Route(route))
}
return attrs
}
func (n CurrentHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue {
switch network {
case "tcp", "tcp4", "tcp6":
return semconvNew.NetworkTransportTCP
case "udp", "udp4", "udp6":
return semconvNew.NetworkTransportUDP
case "unix", "unixgram", "unixpacket":
return semconvNew.NetworkTransportUnix
default:
return semconvNew.NetworkTransportPipe
}
}
func (n CurrentHTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{}
}
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 CurrentHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive
if https {
return semconvNew.URLScheme("https")
}
return semconvNew.URLScheme("http")
}
// 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 (n CurrentHTTPServer) 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 CurrentHTTPServer) Route(route string) attribute.KeyValue {
return semconvNew.HTTPRoute(route)
}
func (n CurrentHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Histogram, metric.Int64Histogram, metric.Float64Histogram) {
if meter == nil {
return noop.Int64Histogram{}, noop.Int64Histogram{}, noop.Float64Histogram{}
}
var err error
requestBodySizeHistogram, err := meter.Int64Histogram(
semconvNew.HTTPServerRequestBodySizeName,
metric.WithUnit(semconvNew.HTTPServerRequestBodySizeUnit),
metric.WithDescription(semconvNew.HTTPServerRequestBodySizeDescription),
)
handleErr(err)
responseBodySizeHistogram, err := meter.Int64Histogram(
semconvNew.HTTPServerResponseBodySizeName,
metric.WithUnit(semconvNew.HTTPServerResponseBodySizeUnit),
metric.WithDescription(semconvNew.HTTPServerResponseBodySizeDescription),
)
handleErr(err)
requestDurationHistogram, err := meter.Float64Histogram(
semconvNew.HTTPServerRequestDurationName,
metric.WithUnit(semconvNew.HTTPServerRequestDurationUnit),
metric.WithDescription(semconvNew.HTTPServerRequestDurationDescription),
metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10),
)
handleErr(err)
return requestBodySizeHistogram, responseBodySizeHistogram, requestDurationHistogram
}
func (n CurrentHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
num := len(additionalAttributes) + 3
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 {
_, p = SplitHostPort(req.Host)
}
}
hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
num++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" {
num++
}
if protoVersion != "" {
num++
}
if statusCode > 0 {
num++
}
attributes := slices.Grow(additionalAttributes, num)
attributes = append(attributes,
semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
n.scheme(req.TLS != nil),
semconvNew.ServerAddress(host))
if hostPort > 0 {
attributes = append(attributes, semconvNew.ServerPort(hostPort))
}
if protoName != "" {
attributes = append(attributes, semconvNew.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion))
}
if statusCode > 0 {
attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode))
}
return attributes
}
type CurrentHTTPClient struct{}
// RequestTraceAttrs returns trace attributes for an HTTP request made by a client.
func (n CurrentHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
/*
below attributes are returned:
- http.request.method
- http.request.method.original
- url.full
- server.address
- server.port
- network.protocol.name
- network.protocol.version
*/
numOfAttributes := 3 // URL, server address, proto, and method.
var urlHost string
if req.URL != nil {
urlHost = req.URL.Host
}
var requestHost string
var requestPort int
for _, hostport := range []string{urlHost, req.Header.Get("Host")} {
requestHost, requestPort = SplitHostPort(hostport)
if requestHost != "" || requestPort > 0 {
break
}
}
eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
if eligiblePort > 0 {
numOfAttributes++
}
useragent := req.UserAgent()
if useragent != "" {
numOfAttributes++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" && protoName != "http" {
numOfAttributes++
}
if protoVersion != "" {
numOfAttributes++
}
method, originalMethod := n.method(req.Method)
if originalMethod != (attribute.KeyValue{}) {
numOfAttributes++
}
attrs := make([]attribute.KeyValue, 0, numOfAttributes)
attrs = append(attrs, method)
if originalMethod != (attribute.KeyValue{}) {
attrs = append(attrs, originalMethod)
}
var u string
if req.URL != nil {
// Remove any username/password info that may be in the URL.
userinfo := req.URL.User
req.URL.User = nil
u = req.URL.String()
// Restore any username/password info that was removed.
req.URL.User = userinfo
}
attrs = append(attrs, semconvNew.URLFull(u))
attrs = append(attrs, semconvNew.ServerAddress(requestHost))
if eligiblePort > 0 {
attrs = append(attrs, semconvNew.ServerPort(eligiblePort))
}
if protoName != "" && protoName != "http" {
attrs = append(attrs, semconvNew.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion))
}
return attrs
}
// ResponseTraceAttrs returns trace attributes for an HTTP response made by a client.
func (n CurrentHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
/*
below attributes are returned:
- http.response.status_code
- error.type
*/
var count int
if resp.StatusCode > 0 {
count++
}
if isErrorStatusCode(resp.StatusCode) {
count++
}
attrs := make([]attribute.KeyValue, 0, count)
if resp.StatusCode > 0 {
attrs = append(attrs, semconvNew.HTTPResponseStatusCode(resp.StatusCode))
}
if isErrorStatusCode(resp.StatusCode) {
errorType := strconv.Itoa(resp.StatusCode)
attrs = append(attrs, semconvNew.ErrorTypeKey.String(errorType))
}
return attrs
}
func (n CurrentHTTPClient) ErrorType(err error) attribute.KeyValue {
t := reflect.TypeOf(err)
var value string
if t.PkgPath() == "" && t.Name() == "" {
// Likely a builtin type.
value = t.String()
} else {
value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
}
if value == "" {
return semconvNew.ErrorTypeOther
}
return semconvNew.ErrorTypeKey.String(value)
}
func (n CurrentHTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{}
}
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 CurrentHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Histogram, metric.Float64Histogram) {
if meter == nil {
return noop.Int64Histogram{}, noop.Float64Histogram{}
}
var err error
requestBodySize, err := meter.Int64Histogram(
semconvNew.HTTPClientRequestBodySizeName,
metric.WithUnit(semconvNew.HTTPClientRequestBodySizeUnit),
metric.WithDescription(semconvNew.HTTPClientRequestBodySizeDescription),
)
handleErr(err)
requestDuration, err := meter.Float64Histogram(
semconvNew.HTTPClientRequestDurationName,
metric.WithUnit(semconvNew.HTTPClientRequestDurationUnit),
metric.WithDescription(semconvNew.HTTPClientRequestDurationDescription),
metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10),
)
handleErr(err)
return requestBodySize, requestDuration
}
func (n CurrentHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
num := len(additionalAttributes) + 2
var h string
if req.URL != nil {
h = req.URL.Host
}
var requestHost string
var requestPort int
for _, hostport := range []string{h, req.Header.Get("Host")} {
requestHost, requestPort = SplitHostPort(hostport)
if requestHost != "" || requestPort > 0 {
break
}
}
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
if port > 0 {
num++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" {
num++
}
if protoVersion != "" {
num++
}
if statusCode > 0 {
num++
}
attributes := slices.Grow(additionalAttributes, num)
attributes = append(attributes,
semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
semconvNew.ServerAddress(requestHost),
n.scheme(req),
)
if port > 0 {
attributes = append(attributes, semconvNew.ServerPort(port))
}
if protoName != "" {
attributes = append(attributes, semconvNew.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion))
}
if statusCode > 0 {
attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode))
}
return attributes
}
// TraceAttributes returns attributes for httptrace.
func (n CurrentHTTPClient) TraceAttributes(host string) []attribute.KeyValue {
return []attribute.KeyValue{
semconvNew.ServerAddress(host),
}
}
func (n CurrentHTTPClient) scheme(req *http.Request) attribute.KeyValue {
if req.URL != nil && req.URL.Scheme != "" {
return semconvNew.URLScheme(req.URL.Scheme)
}
if req.TLS != nil {
return semconvNew.URLScheme("https")
}
return semconvNew.URLScheme("http")
}
func isErrorStatusCode(code int) bool {
return code >= 400 || code < 100
}

View File

@@ -0,0 +1,127 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/util.go.tmpl
// 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"
"net/http"
"strconv"
"strings"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
semconvNew "go.opentelemetry.io/otel/semconv/v1.26.0"
)
// SplitHostPort splits a network address hostport of the form "host",
// "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port",
// "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and
// port.
//
// An empty host is returned if it is not provided or unparsable. A negative
// port is returned if it is not provided or unparsable.
func SplitHostPort(hostport string) (host string, port int) {
port = -1
if strings.HasPrefix(hostport, "[") {
addrEnd := strings.LastIndexByte(hostport, ']')
if addrEnd < 0 {
// Invalid hostport.
return
}
if i := strings.LastIndexByte(hostport[addrEnd:], ':'); i < 0 {
host = hostport[1:addrEnd]
return
}
} else {
if i := strings.LastIndexByte(hostport, ':'); i < 0 {
host = hostport
return
}
}
host, pStr, err := net.SplitHostPort(hostport)
if err != nil {
return
}
p, err := strconv.ParseUint(pStr, 10, 16)
if err != nil {
return
}
return host, int(p) // nolint: gosec // Byte size checked 16 above.
}
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
}
func serverClientIP(xForwardedFor string) string {
if idx := strings.IndexByte(xForwardedFor, ','); idx >= 0 {
xForwardedFor = xForwardedFor[:idx]
}
return xForwardedFor
}
func httpRoute(pattern string) string {
if idx := strings.IndexByte(pattern, '/'); idx >= 0 {
return pattern[idx:]
}
return ""
}
func netProtocol(proto string) (name string, version string) {
name, version, _ = strings.Cut(proto, "/")
switch name {
case "HTTP":
name = "http"
case "QUIC":
name = "quic"
case "SPDY":
name = "spdy"
default:
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,
}
func handleErr(err error) {
if err != nil {
otel.Handle(err)
}
}
func standardizeHTTPMethod(method string) string {
method = strings.ToUpper(method)
switch method {
case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace:
default:
method = "_OTHER"
}
return method
}

View File

@@ -0,0 +1,273 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/v120.0.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
import (
"errors"
"io"
"net/http"
"slices"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/noop"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
)
type OldHTTPServer struct{}
// 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 (o OldHTTPServer) RequestTraceAttrs(server string, req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue {
return semconvutil.HTTPServerRequest(server, req, semconvutil.HTTPServerRequestOptions{}, attrs)
}
func (o OldHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue {
return semconvutil.NetTransport(network)
}
// 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 (o OldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry, attributes []attribute.KeyValue) []attribute.KeyValue {
if resp.ReadBytes > 0 {
attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes)))
}
if resp.ReadError != nil && !errors.Is(resp.ReadError, io.EOF) {
// This is not in the semantic conventions, but is historically provided
attributes = append(attributes, attribute.String("http.read_error", resp.ReadError.Error()))
}
if resp.WriteBytes > 0 {
attributes = append(attributes, semconv.HTTPResponseContentLength(int(resp.WriteBytes)))
}
if resp.StatusCode > 0 {
attributes = append(attributes, semconv.HTTPStatusCode(resp.StatusCode))
}
if resp.WriteError != nil && !errors.Is(resp.WriteError, io.EOF) {
// This is not in the semantic conventions, but is historically provided
attributes = append(attributes, attribute.String("http.write_error", resp.WriteError.Error()))
}
return attributes
}
// Route returns the attribute for the route.
func (o OldHTTPServer) Route(route string) attribute.KeyValue {
return semconv.HTTPRoute(route)
}
// HTTPStatusCode returns the attribute for the HTTP status code.
// This is a temporary function needed by metrics. This will be removed when MetricsRequest is added.
func HTTPStatusCode(status int) attribute.KeyValue {
return semconv.HTTPStatusCode(status)
}
// Server HTTP metrics.
const (
serverRequestSize = "http.server.request.size" // Incoming request bytes total
serverResponseSize = "http.server.response.size" // Incoming response bytes total
serverDuration = "http.server.duration" // Incoming end to end duration, milliseconds
)
func (h OldHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) {
if meter == nil {
return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{}
}
var err error
requestBytesCounter, err := meter.Int64Counter(
serverRequestSize,
metric.WithUnit("By"),
metric.WithDescription("Measures the size of HTTP request messages."),
)
handleErr(err)
responseBytesCounter, err := meter.Int64Counter(
serverResponseSize,
metric.WithUnit("By"),
metric.WithDescription("Measures the size of HTTP response messages."),
)
handleErr(err)
serverLatencyMeasure, err := meter.Float64Histogram(
serverDuration,
metric.WithUnit("ms"),
metric.WithDescription("Measures the duration of inbound HTTP requests."),
)
handleErr(err)
return requestBytesCounter, responseBytesCounter, serverLatencyMeasure
}
func (o OldHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
n := len(additionalAttributes) + 3
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 {
_, p = SplitHostPort(req.Host)
}
}
hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
n++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" {
n++
}
if protoVersion != "" {
n++
}
if statusCode > 0 {
n++
}
attributes := slices.Grow(additionalAttributes, n)
attributes = append(attributes,
semconv.HTTPMethod(standardizeHTTPMethod(req.Method)),
o.scheme(req.TLS != nil),
semconv.NetHostName(host))
if hostPort > 0 {
attributes = append(attributes, semconv.NetHostPort(hostPort))
}
if protoName != "" {
attributes = append(attributes, semconv.NetProtocolName(protoName))
}
if protoVersion != "" {
attributes = append(attributes, semconv.NetProtocolVersion(protoVersion))
}
if statusCode > 0 {
attributes = append(attributes, semconv.HTTPStatusCode(statusCode))
}
return attributes
}
func (o OldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive
if https {
return semconv.HTTPSchemeHTTPS
}
return semconv.HTTPSchemeHTTP
}
type OldHTTPClient struct{}
func (o OldHTTPClient) RequestTraceAttrs(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue {
return semconvutil.HTTPClientRequest(req, attrs)
}
func (o OldHTTPClient) ResponseTraceAttrs(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue {
return semconvutil.HTTPClientResponse(resp, attrs)
}
func (o OldHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
/* The following semantic conventions are returned if present:
http.method string
http.status_code int
net.peer.name string
net.peer.port int
*/
n := 2 // method, peer name.
var h string
if req.URL != nil {
h = req.URL.Host
}
var requestHost string
var requestPort int
for _, hostport := range []string{h, req.Header.Get("Host")} {
requestHost, requestPort = SplitHostPort(hostport)
if requestHost != "" || requestPort > 0 {
break
}
}
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
if port > 0 {
n++
}
if statusCode > 0 {
n++
}
attributes := slices.Grow(additionalAttributes, n)
attributes = append(attributes,
semconv.HTTPMethod(standardizeHTTPMethod(req.Method)),
semconv.NetPeerName(requestHost),
)
if port > 0 {
attributes = append(attributes, semconv.NetPeerPort(port))
}
if statusCode > 0 {
attributes = append(attributes, semconv.HTTPStatusCode(statusCode))
}
return attributes
}
// Client HTTP metrics.
const (
clientRequestSize = "http.client.request.size" // Incoming request bytes total
clientResponseSize = "http.client.response.size" // Incoming response bytes total
clientDuration = "http.client.duration" // Incoming end to end duration, milliseconds
)
func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) {
if meter == nil {
return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{}
}
requestBytesCounter, err := meter.Int64Counter(
clientRequestSize,
metric.WithUnit("By"),
metric.WithDescription("Measures the size of HTTP request messages."),
)
handleErr(err)
responseBytesCounter, err := meter.Int64Counter(
clientResponseSize,
metric.WithUnit("By"),
metric.WithDescription("Measures the size of HTTP response messages."),
)
handleErr(err)
latencyMeasure, err := meter.Float64Histogram(
clientDuration,
metric.WithUnit("ms"),
metric.WithDescription("Measures the duration of outbound HTTP requests."),
)
handleErr(err)
return requestBytesCounter, responseBytesCounter, latencyMeasure
}
// TraceAttributes returns attributes for httptrace.
func (c OldHTTPClient) TraceAttributes(host string, attrs []attribute.KeyValue) []attribute.KeyValue {
return append(attrs, semconv.NetHostName(host))
}

View File

@@ -0,0 +1,10 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
// Generate semconvutil package:
//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/httpconv_test.go.tmpl "--data={}" --out=httpconv_test.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/httpconv.go.tmpl "--data={}" --out=httpconv.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/netconv_test.go.tmpl "--data={}" --out=netconv_test.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/netconv.go.tmpl "--data={}" --out=netconv.go

View File

@@ -0,0 +1,594 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconvutil/httpconv.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package semconvutil provides OpenTelemetry semantic convention utilities.
package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
import (
"fmt"
"net/http"
"slices"
"strings"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
)
type HTTPServerRequestOptions struct {
// If set, this is used as value for the "http.client_ip" attribute.
HTTPClientIP string
}
// HTTPClientResponse returns trace attributes for an HTTP response received by a
// client from a server. It will return the following attributes if the related
// values are defined in resp: "http.status.code",
// "http.response_content_length".
//
// This does not add all OpenTelemetry required attributes for an HTTP event,
// it assumes ClientRequest was used to create the span with a complete set of
// attributes. If a complete set of attributes can be generated using the
// request contained in resp. For example:
//
// HTTPClientResponse(resp, ClientRequest(resp.Request)))
func HTTPClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue {
return hc.ClientResponse(resp, attrs)
}
// HTTPClientRequest returns trace attributes for an HTTP request made by a client.
// The following attributes are always returned: "http.url", "http.method",
// "net.peer.name". The following attributes are returned if the related values
// are defined in req: "net.peer.port", "user_agent.original",
// "http.request_content_length".
func HTTPClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue {
return hc.ClientRequest(req, attrs)
}
// HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client.
// The following attributes are always returned: "http.method", "net.peer.name".
// The following attributes are returned if the
// related values are defined in req: "net.peer.port".
func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue {
return hc.ClientRequestMetrics(req)
}
// HTTPClientStatus returns a span status code and message for an HTTP status code
// value received by a client.
func HTTPClientStatus(code int) (codes.Code, string) {
return hc.ClientStatus(code)
}
// HTTPServerRequest 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.
//
// The following attributes are always returned: "http.method", "http.scheme",
// "http.target", "net.host.name". The following attributes are returned if
// they related values are defined in req: "net.host.port", "net.sock.peer.addr",
// "net.sock.peer.port", "user_agent.original", "http.client_ip".
func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue {
return hc.ServerRequest(server, req, opts, attrs)
}
// HTTPServerRequestMetrics returns metric 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.
//
// The following attributes are always returned: "http.method", "http.scheme",
// "net.host.name". The following attributes are returned if they related
// values are defined in req: "net.host.port".
func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue {
return hc.ServerRequestMetrics(server, req)
}
// HTTPServerStatus returns a span status code and message for an HTTP status code
// value returned by a server. Status codes in the 400-499 range are not
// returned as errors.
func HTTPServerStatus(code int) (codes.Code, string) {
return hc.ServerStatus(code)
}
// httpConv are the HTTP semantic convention attributes defined for a version
// of the OpenTelemetry specification.
type httpConv struct {
NetConv *netConv
HTTPClientIPKey attribute.Key
HTTPMethodKey attribute.Key
HTTPRequestContentLengthKey attribute.Key
HTTPResponseContentLengthKey attribute.Key
HTTPRouteKey attribute.Key
HTTPSchemeHTTP attribute.KeyValue
HTTPSchemeHTTPS attribute.KeyValue
HTTPStatusCodeKey attribute.Key
HTTPTargetKey attribute.Key
HTTPURLKey attribute.Key
UserAgentOriginalKey attribute.Key
}
var hc = &httpConv{
NetConv: nc,
HTTPClientIPKey: semconv.HTTPClientIPKey,
HTTPMethodKey: semconv.HTTPMethodKey,
HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey,
HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey,
HTTPRouteKey: semconv.HTTPRouteKey,
HTTPSchemeHTTP: semconv.HTTPSchemeHTTP,
HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS,
HTTPStatusCodeKey: semconv.HTTPStatusCodeKey,
HTTPTargetKey: semconv.HTTPTargetKey,
HTTPURLKey: semconv.HTTPURLKey,
UserAgentOriginalKey: semconv.UserAgentOriginalKey,
}
// ClientResponse returns attributes for an HTTP response received by a client
// from a server. The following attributes are returned if the related values
// are defined in resp: "http.status.code", "http.response_content_length".
//
// This does not add all OpenTelemetry required attributes for an HTTP event,
// it assumes ClientRequest was used to create the span with a complete set of
// attributes. If a complete set of attributes can be generated using the
// request contained in resp. For example:
//
// ClientResponse(resp, ClientRequest(resp.Request))
func (c *httpConv) ClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue {
/* The following semantic conventions are returned if present:
http.status_code int
http.response_content_length int
*/
var n int
if resp.StatusCode > 0 {
n++
}
if resp.ContentLength > 0 {
n++
}
if n == 0 {
return attrs
}
attrs = slices.Grow(attrs, n)
if resp.StatusCode > 0 {
attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode))
}
if resp.ContentLength > 0 {
attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength)))
}
return attrs
}
// ClientRequest returns attributes for an HTTP request made by a client. The
// following attributes are always returned: "http.url", "http.method",
// "net.peer.name". The following attributes are returned if the related values
// are defined in req: "net.peer.port", "user_agent.original",
// "http.request_content_length", "user_agent.original".
func (c *httpConv) ClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue {
/* The following semantic conventions are returned if present:
http.method string
user_agent.original string
http.url string
net.peer.name string
net.peer.port int
http.request_content_length int
*/
/* The following semantic conventions are not returned:
http.status_code This requires the response. See ClientResponse.
http.response_content_length This requires the response. See ClientResponse.
net.sock.family This requires the socket used.
net.sock.peer.addr This requires the socket used.
net.sock.peer.name This requires the socket used.
net.sock.peer.port This requires the socket used.
http.resend_count This is something outside of a single request.
net.protocol.name The value is the Request is ignored, and the go client will always use "http".
net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0.
*/
n := 3 // URL, peer name, proto, and method.
var h string
if req.URL != nil {
h = req.URL.Host
}
peer, p := firstHostPort(h, req.Header.Get("Host"))
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p)
if port > 0 {
n++
}
useragent := req.UserAgent()
if useragent != "" {
n++
}
if req.ContentLength > 0 {
n++
}
attrs = slices.Grow(attrs, n)
attrs = append(attrs, c.method(req.Method))
var u string
if req.URL != nil {
// Remove any username/password info that may be in the URL.
userinfo := req.URL.User
req.URL.User = nil
u = req.URL.String()
// Restore any username/password info that was removed.
req.URL.User = userinfo
}
attrs = append(attrs, c.HTTPURLKey.String(u))
attrs = append(attrs, c.NetConv.PeerName(peer))
if port > 0 {
attrs = append(attrs, c.NetConv.PeerPort(port))
}
if useragent != "" {
attrs = append(attrs, c.UserAgentOriginalKey.String(useragent))
}
if l := req.ContentLength; l > 0 {
attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l))
}
return attrs
}
// ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The
// following attributes are always returned: "http.method", "net.peer.name".
// The following attributes are returned if the related values
// are defined in req: "net.peer.port".
func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue {
/* The following semantic conventions are returned if present:
http.method string
net.peer.name string
net.peer.port int
*/
n := 2 // method, peer name.
var h string
if req.URL != nil {
h = req.URL.Host
}
peer, p := firstHostPort(h, req.Header.Get("Host"))
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p)
if port > 0 {
n++
}
attrs := make([]attribute.KeyValue, 0, n)
attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer))
if port > 0 {
attrs = append(attrs, c.NetConv.PeerPort(port))
}
return attrs
}
// ServerRequest returns 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.
//
// The following attributes are always returned: "http.method", "http.scheme",
// "http.target", "net.host.name". The following attributes are returned if they
// related values are defined in req: "net.host.port", "net.sock.peer.addr",
// "net.sock.peer.port", "user_agent.original", "http.client_ip",
// "net.protocol.name", "net.protocol.version".
func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue {
/* The following semantic conventions are returned if present:
http.method string
http.scheme string
net.host.name string
net.host.port int
net.sock.peer.addr string
net.sock.peer.port int
user_agent.original string
http.client_ip string
net.protocol.name string Note: not set if the value is "http".
net.protocol.version string
http.target string Note: doesn't include the query parameter.
*/
/* The following semantic conventions are not returned:
http.status_code This requires the response.
http.request_content_length This requires the len() of body, which can mutate it.
http.response_content_length This requires the response.
http.route This is not available.
net.sock.peer.name This would require a DNS lookup.
net.sock.host.addr The request doesn't have access to the underlying socket.
net.sock.host.port The request doesn't have access to the underlying socket.
*/
n := 4 // Method, scheme, proto, and host name.
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 {
_, p = splitHostPort(req.Host)
}
}
hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
n++
}
peer, peerPort := splitHostPort(req.RemoteAddr)
if peer != "" {
n++
if peerPort > 0 {
n++
}
}
useragent := req.UserAgent()
if useragent != "" {
n++
}
// For client IP, use, in order:
// 1. The value passed in the options
// 2. The value in the X-Forwarded-For header
// 3. The peer address
clientIP := opts.HTTPClientIP
if clientIP == "" {
clientIP = serverClientIP(req.Header.Get("X-Forwarded-For"))
if clientIP == "" {
clientIP = peer
}
}
if clientIP != "" {
n++
}
var target string
if req.URL != nil {
target = req.URL.Path
if target != "" {
n++
}
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" && protoName != "http" {
n++
}
if protoVersion != "" {
n++
}
attrs = slices.Grow(attrs, n)
attrs = append(attrs, c.method(req.Method))
attrs = append(attrs, c.scheme(req.TLS != nil))
attrs = append(attrs, c.NetConv.HostName(host))
if hostPort > 0 {
attrs = append(attrs, c.NetConv.HostPort(hostPort))
}
if 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, c.NetConv.SockPeerAddr(peer))
if peerPort > 0 {
attrs = append(attrs, c.NetConv.SockPeerPort(peerPort))
}
}
if useragent != "" {
attrs = append(attrs, c.UserAgentOriginalKey.String(useragent))
}
if clientIP != "" {
attrs = append(attrs, c.HTTPClientIPKey.String(clientIP))
}
if target != "" {
attrs = append(attrs, c.HTTPTargetKey.String(target))
}
if protoName != "" && protoName != "http" {
attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName))
}
if protoVersion != "" {
attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion))
}
return attrs
}
// ServerRequestMetrics returns metric 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.
//
// The following attributes are always returned: "http.method", "http.scheme",
// "net.host.name". The following attributes are returned if they related
// values are defined in req: "net.host.port".
func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue {
/* The following semantic conventions are returned if present:
http.scheme string
http.route string
http.method string
http.status_code int
net.host.name string
net.host.port int
net.protocol.name string Note: not set if the value is "http".
net.protocol.version string
*/
n := 3 // Method, scheme, and host name.
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 {
_, p = splitHostPort(req.Host)
}
}
hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
n++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" {
n++
}
if protoVersion != "" {
n++
}
attrs := make([]attribute.KeyValue, 0, n)
attrs = append(attrs, c.methodMetric(req.Method))
attrs = append(attrs, c.scheme(req.TLS != nil))
attrs = append(attrs, c.NetConv.HostName(host))
if hostPort > 0 {
attrs = append(attrs, c.NetConv.HostPort(hostPort))
}
if protoName != "" {
attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName))
}
if protoVersion != "" {
attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion))
}
return attrs
}
func (c *httpConv) method(method string) attribute.KeyValue {
if method == "" {
return c.HTTPMethodKey.String(http.MethodGet)
}
return c.HTTPMethodKey.String(method)
}
func (c *httpConv) methodMetric(method string) attribute.KeyValue {
method = strings.ToUpper(method)
switch method {
case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace:
default:
method = "_OTHER"
}
return c.HTTPMethodKey.String(method)
}
func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive
if https {
return c.HTTPSchemeHTTPS
}
return c.HTTPSchemeHTTP
}
func serverClientIP(xForwardedFor string) string {
if idx := strings.Index(xForwardedFor, ","); idx >= 0 {
xForwardedFor = xForwardedFor[:idx]
}
return xForwardedFor
}
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
}
// Return the request host and port from the first non-empty source.
func firstHostPort(source ...string) (host string, port int) {
for _, hostport := range source {
host, port = splitHostPort(hostport)
if host != "" || port > 0 {
break
}
}
return
}
// ClientStatus returns a span status code and message for an HTTP status code
// value received by a client.
func (c *httpConv) ClientStatus(code int) (codes.Code, string) {
if code < 100 || code >= 600 {
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
}
if code >= 400 {
return codes.Error, ""
}
return codes.Unset, ""
}
// ServerStatus returns a span status code and message for an HTTP status code
// value returned by a server. Status codes in the 400-499 range are not
// returned as errors.
func (c *httpConv) ServerStatus(code int) (codes.Code, string) {
if code < 100 || code >= 600 {
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
}
if code >= 500 {
return codes.Error, ""
}
return codes.Unset, ""
}

View File

@@ -0,0 +1,214 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconvutil/netconv.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
import (
"net"
"strconv"
"strings"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
)
// NetTransport returns a trace attribute describing the transport protocol of the
// passed network. See the net.Dial for information about acceptable network
// values.
func NetTransport(network string) attribute.KeyValue {
return nc.Transport(network)
}
// netConv are the network semantic convention attributes defined for a version
// of the OpenTelemetry specification.
type netConv struct {
NetHostNameKey attribute.Key
NetHostPortKey attribute.Key
NetPeerNameKey attribute.Key
NetPeerPortKey attribute.Key
NetProtocolName attribute.Key
NetProtocolVersion attribute.Key
NetSockFamilyKey attribute.Key
NetSockPeerAddrKey attribute.Key
NetSockPeerPortKey attribute.Key
NetSockHostAddrKey attribute.Key
NetSockHostPortKey attribute.Key
NetTransportOther attribute.KeyValue
NetTransportTCP attribute.KeyValue
NetTransportUDP attribute.KeyValue
NetTransportInProc attribute.KeyValue
}
var nc = &netConv{
NetHostNameKey: semconv.NetHostNameKey,
NetHostPortKey: semconv.NetHostPortKey,
NetPeerNameKey: semconv.NetPeerNameKey,
NetPeerPortKey: semconv.NetPeerPortKey,
NetProtocolName: semconv.NetProtocolNameKey,
NetProtocolVersion: semconv.NetProtocolVersionKey,
NetSockFamilyKey: semconv.NetSockFamilyKey,
NetSockPeerAddrKey: semconv.NetSockPeerAddrKey,
NetSockPeerPortKey: semconv.NetSockPeerPortKey,
NetSockHostAddrKey: semconv.NetSockHostAddrKey,
NetSockHostPortKey: semconv.NetSockHostPortKey,
NetTransportOther: semconv.NetTransportOther,
NetTransportTCP: semconv.NetTransportTCP,
NetTransportUDP: semconv.NetTransportUDP,
NetTransportInProc: semconv.NetTransportInProc,
}
func (c *netConv) Transport(network string) attribute.KeyValue {
switch network {
case "tcp", "tcp4", "tcp6":
return c.NetTransportTCP
case "udp", "udp4", "udp6":
return c.NetTransportUDP
case "unix", "unixgram", "unixpacket":
return c.NetTransportInProc
default:
// "ip:*", "ip4:*", and "ip6:*" all are considered other.
return c.NetTransportOther
}
}
// Host returns attributes for a network host address.
func (c *netConv) Host(address string) []attribute.KeyValue {
h, p := splitHostPort(address)
var n int
if h != "" {
n++
if p > 0 {
n++
}
}
if n == 0 {
return nil
}
attrs := make([]attribute.KeyValue, 0, n)
attrs = append(attrs, c.HostName(h))
if p > 0 {
attrs = append(attrs, c.HostPort(p))
}
return attrs
}
func (c *netConv) HostName(name string) attribute.KeyValue {
return c.NetHostNameKey.String(name)
}
func (c *netConv) HostPort(port int) attribute.KeyValue {
return c.NetHostPortKey.Int(port)
}
func family(network, address string) string {
switch network {
case "unix", "unixgram", "unixpacket":
return "unix"
default:
if ip := net.ParseIP(address); ip != nil {
if ip.To4() == nil {
return "inet6"
}
return "inet"
}
}
return ""
}
// Peer returns attributes for a network peer address.
func (c *netConv) Peer(address string) []attribute.KeyValue {
h, p := splitHostPort(address)
var n int
if h != "" {
n++
if p > 0 {
n++
}
}
if n == 0 {
return nil
}
attrs := make([]attribute.KeyValue, 0, n)
attrs = append(attrs, c.PeerName(h))
if p > 0 {
attrs = append(attrs, c.PeerPort(p))
}
return attrs
}
func (c *netConv) PeerName(name string) attribute.KeyValue {
return c.NetPeerNameKey.String(name)
}
func (c *netConv) PeerPort(port int) attribute.KeyValue {
return c.NetPeerPortKey.Int(port)
}
func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue {
return c.NetSockPeerAddrKey.String(addr)
}
func (c *netConv) SockPeerPort(port int) attribute.KeyValue {
return c.NetSockPeerPortKey.Int(port)
}
// splitHostPort splits a network address hostport of the form "host",
// "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port",
// "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and
// port.
//
// An empty host is returned if it is not provided or unparsable. A negative
// port is returned if it is not provided or unparsable.
func splitHostPort(hostport string) (host string, port int) {
port = -1
if strings.HasPrefix(hostport, "[") {
addrEnd := strings.LastIndex(hostport, "]")
if addrEnd < 0 {
// Invalid hostport.
return
}
if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 {
host = hostport[1:addrEnd]
return
}
} else {
if i := strings.LastIndex(hostport, ":"); i < 0 {
host = hostport
return
}
}
host, pStr, err := net.SplitHostPort(hostport)
if err != nil {
return
}
p, err := strconv.ParseUint(pStr, 10, 16)
if err != nil {
return
}
return host, int(p) // nolint: gosec // Bitsize checked to be 16 above.
}
func netProtocol(proto string) (name string, version string) {
name, version, _ = strings.Cut(proto, "/")
switch name {
case "HTTP":
name = "http"
case "QUIC":
name = "quic"
case "SPDY":
name = "spdy"
default:
name = strings.ToLower(name)
}
return name, version
}