Skip to content

Commit bf749b8

Browse files
flc1125dmathieupellared
authored
fix(otelecho): comply with span naming semconv (#6365)
from: #6363 This change adjusts the default span name and does not support backward compatibility. --------- Co-authored-by: Damien Mathieu <[email protected]> Co-authored-by: Robert Pająk <[email protected]>
1 parent ba2df8c commit bf749b8

File tree

4 files changed

+86
-15
lines changed

4 files changed

+86
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1515

1616
### Changed
1717

18+
- Change the span name to be `GET /path` so it complies with the OTel HTTP semantic conventions in `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho`. (#6365)
1819
- Record errors instead of setting the `gin.errors` attribute in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6346)
1920

2021
### Fixed

instrumentation/github.com/labstack/echo/otelecho/echo.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44
package otelecho // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho"
55

66
import (
7-
"fmt"
7+
"net/http"
8+
"slices"
9+
"strings"
810

911
"github.com/labstack/echo/v4"
1012
"github.com/labstack/echo/v4/middleware"
1113

12-
"go.opentelemetry.io/otel"
13-
1414
"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil"
15+
"go.opentelemetry.io/otel"
1516
"go.opentelemetry.io/otel/attribute"
1617
"go.opentelemetry.io/otel/propagation"
1718
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
@@ -67,10 +68,7 @@ func Middleware(service string, opts ...Option) echo.MiddlewareFunc {
6768
rAttr := semconv.HTTPRoute(path)
6869
opts = append(opts, oteltrace.WithAttributes(rAttr))
6970
}
70-
spanName := c.Path()
71-
if spanName == "" {
72-
spanName = fmt.Sprintf("HTTP %s route not found", request.Method)
73-
}
71+
spanName := spanNameFormatter(c)
7472

7573
ctx, span := tracer.Start(ctx, spanName, opts...)
7674
defer span.End()
@@ -96,3 +94,22 @@ func Middleware(service string, opts ...Option) echo.MiddlewareFunc {
9694
}
9795
}
9896
}
97+
98+
func spanNameFormatter(c echo.Context) string {
99+
method, path := strings.ToUpper(c.Request().Method), c.Path()
100+
if !slices.Contains([]string{
101+
http.MethodGet, http.MethodHead,
102+
http.MethodPost, http.MethodPut,
103+
http.MethodPatch, http.MethodDelete,
104+
http.MethodConnect, http.MethodOptions,
105+
http.MethodTrace,
106+
}, method) {
107+
method = "HTTP"
108+
}
109+
110+
if path != "" {
111+
return method + " " + path
112+
}
113+
114+
return method
115+
}

instrumentation/github.com/labstack/echo/otelecho/echo_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@ import (
1414
"github.com/labstack/echo/v4"
1515
"github.com/stretchr/testify/assert"
1616

17+
b3prop "go.opentelemetry.io/contrib/propagators/b3"
1718
"go.opentelemetry.io/otel"
1819
"go.opentelemetry.io/otel/propagation"
1920
"go.opentelemetry.io/otel/trace"
2021
"go.opentelemetry.io/otel/trace/noop"
21-
22-
b3prop "go.opentelemetry.io/contrib/propagators/b3"
2322
)
2423

2524
func TestGetSpanNotInstrumented(t *testing.T) {

instrumentation/github.com/labstack/echo/otelecho/test/echo_test.go

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,15 @@ import (
1212
"testing"
1313

1414
"github.com/labstack/echo/v4"
15-
1615
"github.com/stretchr/testify/assert"
1716
"github.com/stretchr/testify/require"
1817

1918
"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho"
2019
"go.opentelemetry.io/otel"
20+
"go.opentelemetry.io/otel/attribute"
2121
"go.opentelemetry.io/otel/codes"
2222
"go.opentelemetry.io/otel/sdk/trace"
2323
"go.opentelemetry.io/otel/sdk/trace/tracetest"
24-
25-
"go.opentelemetry.io/otel/attribute"
2624
oteltrace "go.opentelemetry.io/otel/trace"
2725
)
2826

@@ -86,7 +84,7 @@ func TestTrace200(t *testing.T) {
8684
spans := sr.Ended()
8785
require.Len(t, spans, 1)
8886
span := spans[0]
89-
assert.Equal(t, "/user/:id", span.Name())
87+
assert.Equal(t, "GET /user/:id", span.Name())
9088
assert.Equal(t, oteltrace.SpanKindServer, span.SpanKind())
9189
attrs := span.Attributes()
9290
assert.Contains(t, attrs, attribute.String("net.host.name", "foobar"))
@@ -118,7 +116,7 @@ func TestError(t *testing.T) {
118116
spans := sr.Ended()
119117
require.Len(t, spans, 1)
120118
span := spans[0]
121-
assert.Equal(t, "/server_err", span.Name())
119+
assert.Equal(t, "GET /server_err", span.Name())
122120
attrs := span.Attributes()
123121
assert.Contains(t, attrs, attribute.String("net.host.name", "foobar"))
124122
assert.Contains(t, attrs, attribute.Int("http.status_code", http.StatusInternalServerError))
@@ -177,7 +175,7 @@ func TestStatusError(t *testing.T) {
177175
spans := sr.Ended()
178176
require.Len(t, spans, 1)
179177
span := spans[0]
180-
assert.Equal(t, "/err", span.Name())
178+
assert.Equal(t, "GET /err", span.Name())
181179
assert.Equal(t, tc.spanCode, span.Status().Code)
182180

183181
attrs := span.Attributes()
@@ -202,3 +200,59 @@ func TestErrorNotSwallowedByMiddleware(t *testing.T) {
202200
err := h(c)
203201
assert.Equal(t, assert.AnError, err)
204202
}
203+
204+
func TestSpanNameFormatter(t *testing.T) {
205+
imsb := tracetest.NewInMemoryExporter()
206+
provider := trace.NewTracerProvider(trace.WithSyncer(imsb))
207+
208+
tests := []struct {
209+
name string
210+
method string
211+
path string
212+
url string
213+
expected string
214+
}{
215+
// Test for standard methods
216+
{"standard method of GET", http.MethodGet, "/user/:id", "/user/123", "GET /user/:id"},
217+
{"standard method of HEAD", http.MethodHead, "/user/:id", "/user/123", "HEAD /user/:id"},
218+
{"standard method of POST", http.MethodPost, "/user/:id", "/user/123", "POST /user/:id"},
219+
{"standard method of PUT", http.MethodPut, "/user/:id", "/user/123", "PUT /user/:id"},
220+
{"standard method of PATCH", http.MethodPatch, "/user/:id", "/user/123", "PATCH /user/:id"},
221+
{"standard method of DELETE", http.MethodDelete, "/user/:id", "/user/123", "DELETE /user/:id"},
222+
{"standard method of CONNECT", http.MethodConnect, "/user/:id", "/user/123", "CONNECT /user/:id"},
223+
{"standard method of OPTIONS", http.MethodOptions, "/user/:id", "/user/123", "OPTIONS /user/:id"},
224+
{"standard method of TRACE", http.MethodTrace, "/user/:id", "/user/123", "TRACE /user/:id"},
225+
{"standard method of GET, but it's another route.", http.MethodGet, "/", "/", "GET /"},
226+
227+
// Test for no route
228+
{"no route", http.MethodGet, "/", "/user/id", "GET"},
229+
230+
// Test for case-insensitive method
231+
{"all lowercase", "get", "/user/123", "/user/123", "GET /user/123"},
232+
{"partial capitalization", "Get", "/user/123", "/user/123", "GET /user/123"},
233+
{"full capitalization", "GET", "/user/:id", "/user/123", "GET /user/:id"},
234+
235+
// Test for invalid method
236+
{"invalid method", "INVALID", "/user/123", "/user/123", "HTTP /user/123"},
237+
}
238+
239+
for _, test := range tests {
240+
t.Run(test.name, func(t *testing.T) {
241+
defer imsb.Reset()
242+
243+
router := echo.New()
244+
router.Use(otelecho.Middleware("foobar", otelecho.WithTracerProvider(provider)))
245+
router.Add(test.method, test.path, func(c echo.Context) error {
246+
return c.NoContent(http.StatusOK)
247+
})
248+
249+
r := httptest.NewRequest(test.method, test.url, nil)
250+
w := httptest.NewRecorder()
251+
router.ServeHTTP(w, r)
252+
253+
spans := imsb.GetSpans()
254+
require.Len(t, spans, 1)
255+
assert.Equal(t, test.expected, spans[0].Name)
256+
})
257+
}
258+
}

0 commit comments

Comments
 (0)