Skip to content

Commit a97bb8b

Browse files
authored
fix(otel): Better handling for HTTP span attributes (#610)
1 parent 8fdc323 commit a97bb8b

File tree

2 files changed

+99
-16
lines changed

2 files changed

+99
-16
lines changed

otel/internal/utils/spanattributes.go

+25-16
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package utils
44

55
import (
66
"fmt"
7-
"strings"
7+
"net/url"
88

99
"github.com/getsentry/sentry-go"
1010
otelSdkTrace "go.opentelemetry.io/otel/sdk/trace"
@@ -77,44 +77,53 @@ func descriptionForDbSystem(s otelSdkTrace.ReadOnlySpan) SpanAttributes {
7777
}
7878

7979
func descriptionForHttpMethod(s otelSdkTrace.ReadOnlySpan) SpanAttributes {
80-
opParts := []string{"http"}
81-
80+
op := "http"
8281
switch s.SpanKind() {
8382
case otelTrace.SpanKindClient:
84-
opParts = append(opParts, "client")
83+
op = "http.client"
8584
case otelTrace.SpanKindServer:
86-
opParts = append(opParts, "server")
85+
op = "http.server"
8786
}
8887

8988
var httpTarget string
9089
var httpRoute string
9190
var httpMethod string
91+
var httpUrl string
9292

9393
for _, attribute := range s.Attributes() {
94-
if attribute.Key == semconv.HTTPTargetKey {
94+
switch attribute.Key {
95+
case semconv.HTTPTargetKey:
9596
httpTarget = attribute.Value.AsString()
96-
break
97-
}
98-
if attribute.Key == semconv.HTTPRouteKey {
97+
case semconv.HTTPRouteKey:
9998
httpRoute = attribute.Value.AsString()
100-
break
101-
}
102-
if attribute.Key == semconv.HTTPMethodKey {
99+
case semconv.HTTPMethodKey:
103100
httpMethod = attribute.Value.AsString()
104-
break
101+
case semconv.HTTPURLKey:
102+
httpUrl = attribute.Value.AsString()
105103
}
106104
}
107105

108106
var httpPath string
109107
if httpTarget != "" {
110-
httpPath = httpTarget
108+
if parsedUrl, err := url.Parse(httpTarget); err == nil {
109+
// Do not include the query and fragment parts
110+
httpPath = parsedUrl.Path
111+
} else {
112+
httpPath = httpTarget
113+
}
111114
} else if httpRoute != "" {
112115
httpPath = httpRoute
116+
} else if httpUrl != "" {
117+
// This is normally the HTTP-client case
118+
if parsedUrl, err := url.Parse(httpUrl); err == nil {
119+
// Do not include the query and fragment parts
120+
httpPath = fmt.Sprintf("%s://%s%s", parsedUrl.Scheme, parsedUrl.Host, parsedUrl.Path)
121+
}
113122
}
114123

115124
if httpPath == "" {
116125
return SpanAttributes{
117-
Op: strings.Join(opParts[:], "."),
126+
Op: op,
118127
Description: s.Name(),
119128
Source: sentry.SourceCustom,
120129
}
@@ -132,7 +141,7 @@ func descriptionForHttpMethod(s otelSdkTrace.ReadOnlySpan) SpanAttributes {
132141
}
133142

134143
return SpanAttributes{
135-
Op: strings.Join(opParts[:], "."),
144+
Op: op,
136145
Description: description,
137146
Source: source,
138147
}

otel/span_processor_test.go

+74
Original file line numberDiff line numberDiff line change
@@ -327,3 +327,77 @@ func TestOnEndDoesNotFinishSentryRequests(t *testing.T) {
327327
events := sentryTransport.Events()
328328
assertEqual(t, len(events), 0)
329329
}
330+
331+
func TestParseSpanAttributesHttpClient(t *testing.T) {
332+
_, _, tracer := setupSpanProcessorTest()
333+
ctx, otelRootSpan := tracer.Start(
334+
emptyContextWithSentry(),
335+
"rootSpan",
336+
trace.WithSpanKind(trace.SpanKindClient),
337+
trace.WithAttributes(attribute.String("http.method", "GET")),
338+
trace.WithAttributes(attribute.String("http.url", "http://localhost:1234/api/checkout1?q1=q2#fragment")),
339+
)
340+
_, otelChildSpan := tracer.Start(ctx,
341+
"childSpan",
342+
trace.WithSpanKind(trace.SpanKindClient),
343+
trace.WithAttributes(attribute.String("http.method", "POST")),
344+
trace.WithAttributes(attribute.String("http.url", "http://localhost:2345/api/checkout2?q1=q2#fragment")),
345+
)
346+
347+
sentryTransaction, _ := sentrySpanMap.Get(otelRootSpan.SpanContext().SpanID())
348+
sentrySpan, _ := sentrySpanMap.Get(otelChildSpan.SpanContext().SpanID())
349+
350+
otelChildSpan.End()
351+
otelRootSpan.End()
352+
353+
// Transaction
354+
assertEqual(t, sentryTransaction.Name, "GET http://localhost:1234/api/checkout1")
355+
assertEqual(t, sentryTransaction.Description, "")
356+
assertEqual(t, sentryTransaction.Op, "http.client")
357+
assertEqual(t, sentryTransaction.Source, sentry.TransactionSource("url"))
358+
359+
// Span
360+
assertEqual(t, sentrySpan.Name, "")
361+
assertEqual(t, sentrySpan.Description, "POST http://localhost:2345/api/checkout2")
362+
assertEqual(t, sentrySpan.Op, "http.client")
363+
assertEqual(t, sentrySpan.Source, sentry.TransactionSource(""))
364+
}
365+
366+
func TestParseSpanAttributesHttpServer(t *testing.T) {
367+
_, _, tracer := setupSpanProcessorTest()
368+
ctx, otelRootSpan := tracer.Start(
369+
emptyContextWithSentry(),
370+
"rootSpan",
371+
trace.WithSpanKind(trace.SpanKindServer),
372+
trace.WithAttributes(attribute.String("http.method", "GET")),
373+
trace.WithAttributes(attribute.String("http.target", "/api/checkout1?k=v")),
374+
// We ignore "http.url" if "http.target" is present
375+
trace.WithAttributes(attribute.String("http.url", "http://localhost:1234/api/checkout?q1=q2#fragment")),
376+
)
377+
_, otelChildSpan := tracer.Start(
378+
ctx,
379+
"span name",
380+
trace.WithSpanKind(trace.SpanKindServer),
381+
trace.WithAttributes(attribute.String("http.method", "POST")),
382+
trace.WithAttributes(attribute.String("http.target", "/api/checkout2?k=v")),
383+
// We ignore "http.url" if "http.target" is present
384+
trace.WithAttributes(attribute.String("http.url", "http://localhost:2345/api/checkout?q1=q2#fragment")),
385+
)
386+
sentryTransaction, _ := sentrySpanMap.Get(otelRootSpan.SpanContext().SpanID())
387+
sentrySpan, _ := sentrySpanMap.Get(otelChildSpan.SpanContext().SpanID())
388+
389+
otelChildSpan.End()
390+
otelRootSpan.End()
391+
392+
// Transaction
393+
assertEqual(t, sentryTransaction.Name, "GET /api/checkout1")
394+
assertEqual(t, sentryTransaction.Description, "")
395+
assertEqual(t, sentryTransaction.Op, "http.server")
396+
assertEqual(t, sentryTransaction.Source, sentry.TransactionSource("url"))
397+
398+
// Span
399+
assertEqual(t, sentrySpan.Name, "")
400+
assertEqual(t, sentrySpan.Description, "POST /api/checkout2")
401+
assertEqual(t, sentrySpan.Op, "http.server")
402+
assertEqual(t, sentrySpan.Source, sentry.TransactionSource(""))
403+
}

0 commit comments

Comments
 (0)