Skip to content

Commit 4a37179

Browse files
axwcodeboten
andauthored
confighttp: make server span naming spec-compliant (#13088)
#### Description Update the confighttp server span names to use the low-cardnality request pattern, rather than the full client-specified path, as described by the specification: https://opentelemetry.io/docs/specs/semconv/http/http-spans/#name. Requires open-telemetry/opentelemetry-go-contrib#7192 #### Link to tracking issue Fixes #12468 #### Testing Added a unit test. #### Documentation None --------- Co-authored-by: Alex Boten <[email protected]>
1 parent 279ccad commit 4a37179

File tree

3 files changed

+96
-1
lines changed

3 files changed

+96
-1
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
7+
component: confighttp
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Update the HTTP server span naming to use the HTTP method and route pattern instead of the path.
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [12468]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: |
19+
The HTTP server span name will now be formatted as `<http.request.method> <http.route>`.
20+
If a route pattern is not available, it will fall back to `<http.request.method>`.
21+
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: [user]

config/confighttp/confighttp.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,17 @@ func (hss *ServerConfig) ToServer(ctx context.Context, host component.Host, sett
517517
otelhttp.WithTracerProvider(settings.TracerProvider),
518518
otelhttp.WithPropagators(otel.GetTextMapPropagator()),
519519
otelhttp.WithSpanNameFormatter(func(_ string, r *http.Request) string {
520-
return r.URL.Path
520+
// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#name:
521+
//
522+
// "HTTP span names SHOULD be {method} {target} if there is a (low-cardinality) target available.
523+
// If there is no (low-cardinality) {target} available, HTTP span names SHOULD be {method}.
524+
// ...
525+
// Instrumentation MUST NOT default to using URI path as a {target}."
526+
//
527+
if r.Pattern != "" {
528+
return r.Method + " " + r.Pattern
529+
}
530+
return r.Method
521531
}),
522532
otelhttp.WithMeterProvider(settings.MeterProvider),
523533
},

config/confighttp/confighttp_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,3 +1575,61 @@ func TestDefaultHTTPServerSettings(t *testing.T) {
15751575
assert.Equal(t, time.Duration(0), httpServerSettings.ReadTimeout)
15761576
assert.Equal(t, 1*time.Minute, httpServerSettings.ReadHeaderTimeout)
15771577
}
1578+
1579+
func TestHTTPServerTelemetry_Tracing(t *testing.T) {
1580+
// Create a pattern route. The server name the span after the
1581+
// pattern rather than the client-specified path.
1582+
mux := http.NewServeMux()
1583+
mux.HandleFunc("/b/{bucket}/o/{objectname...}", func(http.ResponseWriter, *http.Request) {})
1584+
1585+
type testcase struct {
1586+
handler http.Handler
1587+
expectedSpanName string
1588+
}
1589+
1590+
for name, testcase := range map[string]testcase{
1591+
"pattern": {
1592+
handler: mux,
1593+
expectedSpanName: "GET /b/{bucket}/o/{objectname...}",
1594+
},
1595+
"no_pattern": {
1596+
handler: http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}),
1597+
expectedSpanName: "GET",
1598+
},
1599+
} {
1600+
t.Run(name, func(t *testing.T) {
1601+
telemetry := componenttest.NewTelemetry()
1602+
config := NewDefaultServerConfig()
1603+
config.Endpoint = "localhost:0"
1604+
config.TLSSetting = nil
1605+
srv, err := config.ToServer(
1606+
context.Background(),
1607+
componenttest.NewNopHost(),
1608+
telemetry.NewTelemetrySettings(),
1609+
testcase.handler,
1610+
)
1611+
require.NoError(t, err)
1612+
1613+
done := make(chan struct{})
1614+
lis, err := config.ToListener(context.Background())
1615+
require.NoError(t, err)
1616+
go func() {
1617+
defer close(done)
1618+
_ = srv.Serve(lis)
1619+
}()
1620+
defer func() {
1621+
assert.NoError(t, srv.Close())
1622+
<-done
1623+
}()
1624+
1625+
resp, err := http.Get(fmt.Sprintf("http://%s/b/bucket123/o/object456/segment", lis.Addr()))
1626+
require.NoError(t, err)
1627+
require.Equal(t, http.StatusOK, resp.StatusCode)
1628+
resp.Body.Close()
1629+
1630+
spans := telemetry.SpanRecorder.Ended()
1631+
require.Len(t, spans, 1)
1632+
assert.Equal(t, testcase.expectedSpanName, spans[0].Name())
1633+
})
1634+
}
1635+
}

0 commit comments

Comments
 (0)