// Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Based on https://github.com/DataDog/dd-trace-go/blob/8fb554ff7cf694267f9077ae35e27ce4689ed8b6/contrib/gin-gonic/gin/gintrace_test.go package echo import ( "context" "errors" "net/http" "net/http/httptest" "testing" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" mocktrace "go.opentelemetry.io/contrib/internal/trace" otelglobal "go.opentelemetry.io/otel/api/global" "go.opentelemetry.io/otel/api/kv" otelpropagation "go.opentelemetry.io/otel/api/propagation" oteltrace "go.opentelemetry.io/otel/api/trace" ) func TestChildSpanFromGlobalTracer(t *testing.T) { otelglobal.SetTraceProvider(&mocktrace.Provider{}) router := echo.New() router.Use(Middleware("foobar")) router.GET("/user/:id", func(c echo.Context) error { span := oteltrace.SpanFromContext(c.Request().Context()) _, ok := span.(*mocktrace.Span) assert.True(t, ok) spanTracer := span.Tracer() mockTracer, ok := spanTracer.(*mocktrace.Tracer) require.True(t, ok) assert.Equal(t, "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo", mockTracer.Name) return c.NoContent(200) }) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) } func TestChildSpanFromCustomTracer(t *testing.T) { tracer := mocktrace.NewTracer("test-tracer") router := echo.New() router.Use(Middleware("foobar", WithTracer(tracer))) router.GET("/user/:id", func(c echo.Context) error { span := oteltrace.SpanFromContext(c.Request().Context()) _, ok := span.(*mocktrace.Span) assert.True(t, ok) spanTracer := span.Tracer() mockTracer, ok := spanTracer.(*mocktrace.Tracer) require.True(t, ok) assert.Equal(t, "test-tracer", mockTracer.Name) return c.NoContent(200) }) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) } func TestTrace200(t *testing.T) { tracer := mocktrace.NewTracer("test-tracer") router := echo.New() router.Use(Middleware("foobar", WithTracer(tracer))) router.GET("/user/:id", func(c echo.Context) error { span := oteltrace.SpanFromContext(c.Request().Context()) mspan, ok := span.(*mocktrace.Span) require.True(t, ok) assert.Equal(t, kv.StringValue("foobar"), mspan.Attributes["http.server_name"]) id := c.Param("id") return c.String(200, id) }) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() // do and verify the request router.ServeHTTP(w, r) response := w.Result() require.Equal(t, http.StatusOK, response.StatusCode) // verify traces look good spans := tracer.EndedSpans() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "/user/:id", span.Name) assert.Equal(t, oteltrace.SpanKindServer, span.Kind) assert.Equal(t, kv.StringValue("foobar"), span.Attributes["http.server_name"]) assert.Equal(t, kv.IntValue(http.StatusOK), span.Attributes["http.status_code"]) assert.Equal(t, kv.StringValue("GET"), span.Attributes["http.method"]) assert.Equal(t, kv.StringValue("/user/123"), span.Attributes["http.target"]) assert.Equal(t, kv.StringValue("/user/:id"), span.Attributes["http.route"]) } func TestError(t *testing.T) { tracer := mocktrace.NewTracer("test-tracer") // setup router := echo.New() router.Use(Middleware("foobar", WithTracer(tracer))) wantErr := errors.New("oh no") // configure a handler that returns an error and 5xx status // code router.GET("/server_err", func(c echo.Context) error { return wantErr }) r := httptest.NewRequest("GET", "/server_err", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) response := w.Result() assert.Equal(t, http.StatusInternalServerError, response.StatusCode) // verify the errors and status are correct spans := tracer.EndedSpans() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "/server_err", span.Name) assert.Equal(t, kv.StringValue("foobar"), span.Attributes["http.server_name"]) assert.Equal(t, kv.IntValue(http.StatusInternalServerError), span.Attributes["http.status_code"]) assert.Equal(t, kv.StringValue("oh no"), span.Attributes["echo.error"]) // server errors set the status assert.Equal(t, codes.Internal, span.Status) } func TestGetSpanNotInstrumented(t *testing.T) { router := echo.New() router.GET("/ping", func(c echo.Context) error { // Assert we don't have a span on the context. span := oteltrace.SpanFromContext(c.Request().Context()) _, ok := span.(oteltrace.NoopSpan) assert.True(t, ok) return c.String(200, "ok") }) r := httptest.NewRequest("GET", "/ping", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) response := w.Result() assert.Equal(t, http.StatusOK, response.StatusCode) } func TestPropagationWithGlobalPropagators(t *testing.T) { tracer := mocktrace.NewTracer("test-tracer") r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() ctx, pspan := tracer.Start(context.Background(), "test") otelpropagation.InjectHTTP(ctx, otelglobal.Propagators(), r.Header) router := echo.New() router.Use(Middleware("foobar", WithTracer(tracer))) router.GET("/user/:id", func(c echo.Context) error { span := oteltrace.SpanFromContext(c.Request().Context()) mspan, ok := span.(*mocktrace.Span) require.True(t, ok) assert.Equal(t, pspan.SpanContext().TraceID, mspan.SpanContext().TraceID) assert.Equal(t, pspan.SpanContext().SpanID, mspan.ParentSpanID) return c.NoContent(200) }) router.ServeHTTP(w, r) } func TestPropagationWithCustomPropagators(t *testing.T) { tracer := mocktrace.NewTracer("test-tracer") b3 := oteltrace.B3{} props := otelpropagation.New( otelpropagation.WithExtractors(b3), otelpropagation.WithInjectors(b3), ) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() ctx, pspan := tracer.Start(context.Background(), "test") otelpropagation.InjectHTTP(ctx, props, r.Header) router := echo.New() router.Use(Middleware("foobar", WithTracer(tracer), WithPropagators(props))) router.GET("/user/:id", func(c echo.Context) error { span := oteltrace.SpanFromContext(c.Request().Context()) mspan, ok := span.(*mocktrace.Span) require.True(t, ok) assert.Equal(t, pspan.SpanContext().TraceID, mspan.SpanContext().TraceID) assert.Equal(t, pspan.SpanContext().SpanID, mspan.ParentSpanID) return c.NoContent(200) }) router.ServeHTTP(w, r) }