diff --git a/integration/tracing_test.go b/integration/tracing_test.go index 9b9fa0faf..71e0b1b36 100644 --- a/integration/tracing_test.go +++ b/integration/tracing_test.go @@ -110,7 +110,7 @@ func (s *TracingSuite) TestOpenTelemetryBasic_HTTP_router_minimalVerbosity() { "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"server.port\").value.intValue": "80", "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.response.status_code\").value.intValue": "200", - "batches.0.scopeSpans.0.spans.1.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.1.name": "GET", "batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"entry_point\").value.stringValue": "web", "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.request.method\").value.stringValue": "GET", @@ -159,7 +159,7 @@ func (s *TracingSuite) TestOpenTelemetryBasic_HTTP_entrypoint_minimalVerbosity() "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"server.port\").value.intValue": "80", "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.response.status_code\").value.intValue": "200", - "batches.0.scopeSpans.0.spans.1.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.1.name": "GET", "batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"entry_point\").value.stringValue": "web-minimal", "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.request.method\").value.stringValue": "GET", @@ -226,7 +226,7 @@ func (s *TracingSuite) TestOpenTelemetryBasic_HTTP() { "batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint", - "batches.0.scopeSpans.0.spans.5.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.5.name": "GET", "batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.5.attributes.#(key=\"entry_point\").value.stringValue": "web", "batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.request.method\").value.stringValue": "GET", @@ -356,7 +356,7 @@ func (s *TracingSuite) TestOpenTelemetryRateLimit() { "batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint", - "batches.0.scopeSpans.0.spans.3.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.3.name": "GET", "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"entry_point\").value.stringValue": "web", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.method\").value.stringValue": "GET", @@ -404,7 +404,7 @@ func (s *TracingSuite) TestOpenTelemetryRateLimit() { "batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint", - "batches.0.scopeSpans.0.spans.6.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.6.name": "GET", "batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.6.attributes.#(key=\"entry_point\").value.stringValue": "web", "batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.request.method\").value.stringValue": "GET", @@ -527,7 +527,7 @@ func (s *TracingSuite) TestOpenTelemetryRetry() { "batches.0.scopeSpans.0.spans.13.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.13.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint", - "batches.0.scopeSpans.0.spans.14.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.14.name": "GET", "batches.0.scopeSpans.0.spans.14.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.14.attributes.#(key=\"entry_point\").value.stringValue": "web", "batches.0.scopeSpans.0.spans.14.attributes.#(key=\"http.request.method\").value.stringValue": "GET", @@ -580,7 +580,7 @@ func (s *TracingSuite) TestOpenTelemetryAuth() { "batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint", - "batches.0.scopeSpans.0.spans.3.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.3.name": "GET", "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"entry_point\").value.stringValue": "web", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.method\").value.stringValue": "GET", @@ -637,7 +637,7 @@ func (s *TracingSuite) TestOpenTelemetryAuthWithRetry() { "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint", - "batches.0.scopeSpans.0.spans.4.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.4.name": "GET", "batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"entry_point\").value.stringValue": "web", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.request.method\").value.stringValue": "GET", @@ -706,7 +706,7 @@ func (s *TracingSuite) TestOpenTelemetrySafeURL() { "batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint", - "batches.0.scopeSpans.0.spans.6.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.6.name": "GET", "batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.6.attributes.#(key=\"entry_point\").value.stringValue": "web", "batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.request.method\").value.stringValue": "GET", diff --git a/pkg/middlewares/observability/entrypoint.go b/pkg/middlewares/observability/entrypoint.go index e9c77c160..fead7ec43 100644 --- a/pkg/middlewares/observability/entrypoint.go +++ b/pkg/middlewares/observability/entrypoint.go @@ -55,7 +55,10 @@ func (e *entryPointTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) tracingCtx := tracing.ExtractCarrierIntoContext(req.Context(), req.Header) start := time.Now() - tracingCtx, span := e.tracer.Start(tracingCtx, "EntryPoint", trace.WithSpanKind(trace.SpanKindServer), trace.WithTimestamp(start)) + + // Follow semantic conventions defined by OTEL: https://opentelemetry.io/docs/specs/semconv/http/http-spans/#name + // At the moment this implementation only gets the {method} as there is no guarantee {target} is low cardinality. + tracingCtx, span := e.tracer.Start(tracingCtx, req.Method, trace.WithSpanKind(trace.SpanKindServer), trace.WithTimestamp(start)) // Associate the request context with the logger. // This allows the logger to be aware of the tracing context and log accordingly (TraceID, SpanID, etc.). diff --git a/pkg/middlewares/observability/entrypoint_test.go b/pkg/middlewares/observability/entrypoint_test.go index bbcd57f98..384d11a4c 100644 --- a/pkg/middlewares/observability/entrypoint_test.go +++ b/pkg/middlewares/observability/entrypoint_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/tracing" "go.opentelemetry.io/otel/attribute" ) @@ -19,13 +20,15 @@ func TestEntryPointMiddleware_tracing(t *testing.T) { testCases := []struct { desc string entryPoint string + method string expected expected }{ { - desc: "basic test", + desc: "GET", entryPoint: "test", + method: http.MethodGet, expected: expected{ - name: "EntryPoint", + name: "GET", attributes: []attribute.KeyValue{ attribute.String("span.kind", "server"), attribute.String("entry_point", "test"), @@ -47,11 +50,38 @@ func TestEntryPointMiddleware_tracing(t *testing.T) { }, }, }, + { + desc: "POST", + entryPoint: "test", + method: http.MethodPost, + expected: expected{ + name: "POST", + attributes: []attribute.KeyValue{ + attribute.String("span.kind", "server"), + attribute.String("entry_point", "test"), + attribute.String("http.request.method", "POST"), + attribute.String("network.protocol.version", "1.1"), + attribute.Int64("http.request.body.size", int64(0)), + attribute.String("url.path", "/search"), + attribute.String("url.query", "q=Opentelemetry&token=REDACTED"), + attribute.String("url.scheme", "http"), + attribute.String("user_agent.original", "entrypoint-test"), + attribute.String("server.address", "www.test.com"), + attribute.String("network.peer.address", "10.0.0.1"), + attribute.String("client.address", "10.0.0.1"), + attribute.Int64("client.port", int64(1234)), + attribute.Int64("network.peer.port", int64(1234)), + attribute.StringSlice("http.request.header.x-foo", []string{"foo", "bar"}), + attribute.Int64("http.response.status_code", int64(404)), + attribute.StringSlice("http.response.header.x-bar", []string{"foo", "bar"}), + }, + }, + }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "http://www.test.com/search?q=Opentelemetry&token=123", nil) + req := httptest.NewRequest(test.method, "http://www.test.com/search?q=Opentelemetry&token=123", nil) rw := httptest.NewRecorder() req.RemoteAddr = "10.0.0.1:1234" req.Header.Set("User-Agent", "entrypoint-test") @@ -67,13 +97,17 @@ func TestEntryPointMiddleware_tracing(t *testing.T) { tracer := &mockTracer{} + // Injection of the observability variables in the request context. + req = req.WithContext(WithObservability(req.Context(), Observability{ + TracingEnabled: true, + })) + handler := newEntryPoint(t.Context(), tracing.NewTracer(tracer, []string{"X-Foo"}, []string{"X-Bar"}, []string{"q"}), test.entryPoint, next) handler.ServeHTTP(rw, req) - for _, span := range tracer.spans { - assert.Equal(t, test.expected.name, span.name) - assert.Equal(t, test.expected.attributes, span.attributes) - } + require.Len(t, tracer.spans, 1) + assert.Equal(t, test.expected.name, tracer.spans[0].name) + assert.Equal(t, test.expected.attributes, tracer.spans[0].attributes) }) } } diff --git a/pkg/middlewares/observability/mock_tracing_test.go b/pkg/middlewares/observability/mock_tracing_test.go index 4a70b77bc..3a2ffd520 100644 --- a/pkg/middlewares/observability/mock_tracing_test.go +++ b/pkg/middlewares/observability/mock_tracing_test.go @@ -37,7 +37,7 @@ func (t *mockTracer) Start(ctx context.Context, name string, opts ...trace.SpanS return trace.ContextWithSpan(ctx, span), span } -// mockSpan is an implementation of Span that preforms no operations. +// mockSpan is an implementation of Span that performs no operations. type mockSpan struct { embedded.Span diff --git a/pkg/middlewares/observability/router_test.go b/pkg/middlewares/observability/router_test.go index 0ee0835cb..b519e6172 100644 --- a/pkg/middlewares/observability/router_test.go +++ b/pkg/middlewares/observability/router_test.go @@ -30,7 +30,7 @@ func TestNewRouter(t *testing.T) { routerRule: "Path(`/`)", expected: []expected{ { - name: "EntryPoint", + name: "GET", attributes: []attribute.KeyValue{ attribute.String("span.kind", "server"), }, @@ -63,7 +63,7 @@ func TestNewRouter(t *testing.T) { req.Header.Set("User-Agent", "router-test") tracer := &mockTracer{} - tracingCtx, entryPointSpan := tracer.Start(req.Context(), "EntryPoint", trace.WithSpanKind(trace.SpanKindServer)) + tracingCtx, entryPointSpan := tracer.Start(req.Context(), http.MethodGet, trace.WithSpanKind(trace.SpanKindServer)) defer entryPointSpan.End() req = req.WithContext(tracingCtx) diff --git a/pkg/middlewares/observability/service_test.go b/pkg/middlewares/observability/service_test.go index 623ed214c..99197ed9b 100644 --- a/pkg/middlewares/observability/service_test.go +++ b/pkg/middlewares/observability/service_test.go @@ -26,7 +26,7 @@ func TestNewService(t *testing.T) { service: "myService", expected: []expected{ { - name: "EntryPoint", + name: "GET", attributes: []attribute.KeyValue{ attribute.String("span.kind", "server"), }, @@ -57,7 +57,7 @@ func TestNewService(t *testing.T) { req.Header.Set("User-Agent", "service-test") tracer := &mockTracer{} - tracingCtx, entryPointSpan := tracer.Start(req.Context(), "EntryPoint", trace.WithSpanKind(trace.SpanKindServer)) + tracingCtx, entryPointSpan := tracer.Start(req.Context(), http.MethodGet, trace.WithSpanKind(trace.SpanKindServer)) defer entryPointSpan.End() req = req.WithContext(tracingCtx)