diff --git a/instrumentation/net/http/otelhttp/go.mod b/instrumentation/net/http/otelhttp/go.mod index 636ea65859b..74f122f8612 100644 --- a/instrumentation/net/http/otelhttp/go.mod +++ b/instrumentation/net/http/otelhttp/go.mod @@ -7,6 +7,7 @@ require ( github.com/stretchr/testify v1.8.4 go.opentelemetry.io/otel v1.19.0 go.opentelemetry.io/otel/metric v1.19.0 + go.opentelemetry.io/otel/sdk/metric v1.19.0 go.opentelemetry.io/otel/trace v1.19.0 ) @@ -15,5 +16,7 @@ require ( github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + go.opentelemetry.io/otel/sdk v1.20.0 // indirect + golang.org/x/sys v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/instrumentation/net/http/otelhttp/go.sum b/instrumentation/net/http/otelhttp/go.sum index 33bc1422b4b..86817b4ce1f 100644 --- a/instrumentation/net/http/otelhttp/go.sum +++ b/instrumentation/net/http/otelhttp/go.sum @@ -7,17 +7,24 @@ github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= -go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= -go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= -go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= -go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc= +go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= +go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA= +go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM= +go.opentelemetry.io/otel/sdk v1.20.0 h1:5Jf6imeFZlZtKv9Qbo6qt2ZkmWtdWx/wzcCbNUlAWGM= +go.opentelemetry.io/otel/sdk v1.20.0/go.mod h1:rmkSx1cZCm/tn16iWDn1GQbLtsW/LvsdEEFzCSRM6V0= +go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= +go.opentelemetry.io/otel/sdk/metric v1.20.0 h1:5eD40l/H2CqdKmbSV7iht2KMK0faAIL2pVYzJOWobGk= +go.opentelemetry.io/otel/sdk/metric v1.20.0/go.mod h1:AGvpC+YF/jblITiafMTYgvRBUiwi9hZf0EYE2E5XlS8= +go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ= +go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/instrumentation/net/http/otelhttp/handler.go b/instrumentation/net/http/otelhttp/handler.go index 8bc1c00f5be..b22105e2d2a 100644 --- a/instrumentation/net/http/otelhttp/handler.go +++ b/instrumentation/net/http/otelhttp/handler.go @@ -143,6 +143,14 @@ func (h *middleware) createMeasures() { h.valueRecorders[ServerLatency] = serverLatencyMeasure } +func httpSchemeFromRequest(r *http.Request) attribute.KeyValue { + if r.TLS != nil { + return semconv.HTTPSchemeHTTPS + } else { + return semconv.HTTPSchemeHTTP + } +} + // serveHTTP sets up tracing and calls the given next http.Handler with the span // context injected into the request context. func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http.Handler) { @@ -236,17 +244,16 @@ func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http labeler := &Labeler{} ctx = injectLabeler(ctx, labeler) - reqAttrs := semconvutil.HTTPServerRequestMetrics(h.server, r) - ao := metric.WithAttributes(reqAttrs...) - h.upDownCounters[ActiveRequests].Add(ctx, 1, ao) - defer h.upDownCounters[ActiveRequests].Add(ctx, -1, ao) + attrs := metric.WithAttributes(httpSchemeFromRequest(r), semconv.HTTPMethod(r.Method)) + h.upDownCounters[ActiveRequests].Add(ctx, 1, attrs) + defer h.upDownCounters[ActiveRequests].Add(ctx, -1, attrs) next.ServeHTTP(w, r.WithContext(ctx)) setAfterServeAttributes(span, bw.read, rww.written, rww.statusCode, bw.err, rww.err) // Add metrics - attributes := append(labeler.Get(), reqAttrs...) + attributes := append(labeler.Get(), semconvutil.HTTPServerRequestMetrics(h.server, r)...) if rww.statusCode > 0 { attributes = append(attributes, semconv.HTTPStatusCode(rww.statusCode)) } diff --git a/instrumentation/net/http/otelhttp/handler_test.go b/instrumentation/net/http/otelhttp/handler_test.go index 7d5c6721222..7b127c497c7 100644 --- a/instrumentation/net/http/otelhttp/handler_test.go +++ b/instrumentation/net/http/otelhttp/handler_test.go @@ -15,10 +15,18 @@ package otelhttp_test import ( + "context" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" "io" "net/http" "net/http/httptest" + "slices" "strings" + "sync" "testing" "github.com/stretchr/testify/assert" @@ -27,6 +35,90 @@ import ( "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) +func assertActiveMetric(t *testing.T, ctx context.Context, reader *metric.ManualReader, actual metricdata.Aggregation, opts ...metricdatatest.Option) bool { + var rm metricdata.ResourceMetrics + err := reader.Collect(ctx, &rm) + + if !assert.NoError(t, err, "failed to read the metric reader") { + return false + } + + if !assert.Len(t, rm.ScopeMetrics, 1, "too many metrics") { + return false + } + + metrics := rm.ScopeMetrics[0].Metrics + m := metrics[slices.IndexFunc(metrics, func(m metricdata.Metrics) bool { + return m.Name == otelhttp.ActiveRequests + })] + return metricdatatest.AssertAggregationsEqual(t, actual, m.Data, opts...) +} + +func activeMetric(val int64) metricdata.Sum[int64] { + return metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: false, + DataPoints: []metricdata.DataPoint[int64]{ + { + Value: val, + Attributes: attribute.NewSet( + semconv.HTTPMethod("GET"), + semconv.HTTPScheme("http"), + ), + }, + }, + } +} + +func TestActiveMetrics(t *testing.T) { + ctx, done := context.WithCancel(context.Background()) + defer done() + + reader := metric.NewManualReader() + var g sync.WaitGroup + var ag sync.WaitGroup + var l sync.Mutex + + handler := otelhttp.NewHandler(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + g.Done() + l.Lock() + defer l.Unlock() + ag.Done() + }), "test", otelhttp.WithMeterProvider(metric.NewMeterProvider(metric.WithReader(reader)))) + + g.Add(5) + ag.Add(5) + l.Lock() + + for i := 0; i < 5; i++ { + go handler.ServeHTTP( + httptest.NewRecorder(), + httptest.NewRequest("GET", "/foo/bar", nil), + ) + } + + g.Wait() + + assertActiveMetric(t, ctx, reader, activeMetric(5), metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars()) + + g.Add(5) + ag.Add(5) + for i := 0; i < 5; i++ { + go handler.ServeHTTP( + httptest.NewRecorder(), + httptest.NewRequest("GET", "/foo/bar", nil), + ) + } + g.Wait() + + assertActiveMetric(t, ctx, reader, activeMetric(10), metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars()) + + l.Unlock() + ag.Wait() + + assertActiveMetric(t, ctx, reader, activeMetric(0), metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars()) +} + func TestHandler(t *testing.T) { testCases := []struct { name string