Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

contrib/internal/httptrace: add support for inferred proxy spans #3052

Merged
merged 30 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6fb5400
add working draft inferred proxy spans
jordan-wong Dec 19, 2024
9762bfd
remove unnecessary setting of env vars in tests
jordan-wong Dec 19, 2024
63c8418
fix small logic error with env var check
jordan-wong Dec 19, 2024
5f16ca4
clean up print statements
jordan-wong Dec 20, 2024
10ed7f7
change print statements to log lines, make code more readable
jordan-wong Dec 20, 2024
923d28b
More cleanup
jordan-wong Dec 20, 2024
b356a70
add fallback value for service name using tracer ServiceName
jordan-wong Jan 6, 2025
7c93173
rewrite env var check with config
zarirhamza Jan 9, 2025
8da09a0
contrib/internal/httptrace: Update StartRequestSpan to infer AWS APIG…
zarirhamza Jan 10, 2025
ff31ebf
refactor code for readability
zarirhamza Jan 14, 2025
ce40b43
adds span links for inferred spans
zarirhamza Jan 15, 2025
a9aeec2
fix conflicts with spanlinks
zarirhamza Jan 15, 2025
43ff23f
Merge branch 'main' into add-inferred-proxy-spans
zarirhamza Jan 15, 2025
1ec1be9
fix copyright
zarirhamza Jan 15, 2025
92fe98b
compare http and apigw spans in tests
zarirhamza Jan 15, 2025
68be243
fix propagation tests
zarirhamza Jan 15, 2025
8be56b3
refactor
rarguelloF Jan 15, 2025
78c83fa
add new test
rarguelloF Jan 15, 2025
aceef66
Merge branch 'main' into add-inferred-proxy-spans
zarirhamza Jan 15, 2025
5510462
fix errors and refactor
zarirhamza Jan 15, 2025
6122e7d
refactor
zarirhamza Jan 15, 2025
a4d8959
address changes
zarirhamza Jan 19, 2025
2bb7737
Merge branch 'main' into add-inferred-proxy-spans
zarirhamza Jan 19, 2025
a7641c7
Merge branch 'main' into add-inferred-proxy-spans
rarguelloF Jan 21, 2025
16d7388
removes unnnecessary errors
zarirhamza Jan 21, 2025
4394d26
fix errors
zarirhamza Jan 21, 2025
d64e3f8
fix errors again
zarirhamza Jan 21, 2025
4a22660
adds assertions to test service and errors
zarirhamza Jan 21, 2025
78b3d4b
improve performance by isolating inferred span operations
zarirhamza Jan 22, 2025
39087f1
Merge branch 'main' into add-inferred-proxy-spans
zarirhamza Jan 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions contrib/emicklei/go-restful.v3/restful.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ func FilterFunc(configOpts ...Option) restful.FilterFunction {
spanOpts = append(spanOpts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate))
}
spanOpts = append(spanOpts, httptrace.HeaderTagsFromRequest(req.Request, cfg.headerTags))
span, ctx := httptrace.StartRequestSpan(req.Request, spanOpts...)
_, ctx, finishSpans := httptrace.StartRequestSpan(req.Request, spanOpts...)
defer func() {
httptrace.FinishRequestSpan(span, resp.StatusCode(), nil, tracer.WithError(resp.Error()))
finishSpans(resp.StatusCode(), nil, tracer.WithError(resp.Error()))
}()

// pass the span through the request context
Expand Down
8 changes: 4 additions & 4 deletions contrib/emicklei/go-restful/restful.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ func FilterFunc(configOpts ...Option) restful.FilterFunction {
spanOpts = append(spanOpts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate))
}
spanOpts = append(spanOpts, httptrace.HeaderTagsFromRequest(req.Request, cfg.headerTags))
span, ctx := httptrace.StartRequestSpan(req.Request, spanOpts...)
_, ctx, finishSpans := httptrace.StartRequestSpan(req.Request, spanOpts...)
defer func() {
httptrace.FinishRequestSpan(span, resp.StatusCode(), nil, tracer.WithError(resp.Error()))
finishSpans(resp.StatusCode(), nil, tracer.WithError(resp.Error()))
}()

// pass the span through the request context
Expand All @@ -59,9 +59,9 @@ func FilterFunc(configOpts ...Option) restful.FilterFunction {

// Filter is deprecated. Please use FilterFunc.
func Filter(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
span, ctx := httptrace.StartRequestSpan(req.Request, tracer.ResourceName(req.SelectedRoutePath()))
_, ctx, finishSpans := httptrace.StartRequestSpan(req.Request, tracer.ResourceName(req.SelectedRoutePath()))
defer func() {
httptrace.FinishRequestSpan(span, resp.StatusCode(), nil, tracer.WithError(resp.Error()))
finishSpans(resp.StatusCode(), nil, tracer.WithError(resp.Error()))
}()

// pass the span through the request context
Expand Down
4 changes: 2 additions & 2 deletions contrib/gin-gonic/gin/gintrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc {
}
opts = append(opts, tracer.Tag(ext.HTTPRoute, c.FullPath()))
opts = append(opts, httptrace.HeaderTagsFromRequest(c.Request, cfg.headerTags))
span, ctx := httptrace.StartRequestSpan(c.Request, opts...)
span, ctx, finishSpans := httptrace.StartRequestSpan(c.Request, opts...)
defer func() {
httptrace.FinishRequestSpan(span, c.Writer.Status(), nil)
finishSpans(c.Writer.Status(), nil)
}()

// pass the span through the request context
Expand Down
4 changes: 2 additions & 2 deletions contrib/go-chi/chi.v5/chi.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler {
opts = append(opts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate))
}
opts = append(opts, httptrace.HeaderTagsFromRequest(r, cfg.headerTags))
span, ctx := httptrace.StartRequestSpan(r, opts...)
span, ctx, finishSpans := httptrace.StartRequestSpan(r, opts...)
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
defer func() {
status := ww.Status()
httptrace.FinishRequestSpan(span, status, cfg.isStatusError)
finishSpans(status, cfg.isStatusError)
}()

// pass the span through the request context
Expand Down
4 changes: 2 additions & 2 deletions contrib/go-chi/chi/chi.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler {
opts = append(opts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate))
}
opts = append(opts, httptrace.HeaderTagsFromRequest(r, cfg.headerTags))
span, ctx := httptrace.StartRequestSpan(r, opts...)
span, ctx, finishSpans := httptrace.StartRequestSpan(r, opts...)
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
defer func() {
status := ww.Status()
httptrace.FinishRequestSpan(span, status, cfg.isStatusError)
finishSpans(status, cfg.isStatusError)
}()

// pass the span through the request context
Expand Down
4 changes: 2 additions & 2 deletions contrib/internal/httptrace/before_handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ func BeforeHandle(cfg *ServeConfig, w http.ResponseWriter, r *http.Request) (htt
if cfg.Route != "" {
opts = append(opts, tracer.Tag(ext.HTTPRoute, cfg.Route))
}
span, ctx := StartRequestSpan(r, opts...)
span, ctx, finishSpans := StartRequestSpan(r, opts...)
rw, ddrw := wrapResponseWriter(w)
rt := r.WithContext(ctx)
closeSpan := func() {
FinishRequestSpan(span, ddrw.status, cfg.IsStatusError, cfg.FinishOpts...)
finishSpans(ddrw.status, cfg.IsStatusError, cfg.FinishOpts...)
}
afterHandle := closeSpan
handled := false
Expand Down
20 changes: 12 additions & 8 deletions contrib/internal/httptrace/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,19 @@ const (
envTraceClientIPEnabled = "DD_TRACE_CLIENT_IP_ENABLED"
// envServerErrorStatuses is the name of the env var used to specify error status codes on http server spans
envServerErrorStatuses = "DD_TRACE_HTTP_SERVER_ERROR_STATUSES"
// envInferredProxyServicesEnabled is the name of the env var used for enabling inferred span tracing
envInferredProxyServicesEnabled = "DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED"
)

// defaultQueryStringRegexp is the regexp used for query string obfuscation if `envQueryStringRegexp` is empty.
var defaultQueryStringRegexp = regexp.MustCompile("(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:(?:\\s|%20)*(?:=|%3D)[^&]+|(?:\"|%22)(?:\\s|%20)*(?::|%3A)(?:\\s|%20)*(?:\"|%22)(?:%2[^2]|%[^2]|[^\"%])+(?:\"|%22))|bearer(?:\\s|%20)+[a-z0-9\\._\\-]|token(?::|%3A)[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L](?:[\\w=-]|%3D)+\\.ey[I-L](?:[\\w=-]|%3D)+(?:\\.(?:[\\w.+\\/=-]|%3D|%2F|%2B)+)?|[\\-]{5}BEGIN(?:[a-z\\s]|%20)+PRIVATE(?:\\s|%20)KEY[\\-]{5}[^\\-]+[\\-]{5}END(?:[a-z\\s]|%20)+PRIVATE(?:\\s|%20)KEY|ssh-rsa(?:\\s|%20)*(?:[a-z0-9\\/\\.+]|%2F|%5C|%2B){100,}")

type config struct {
queryStringRegexp *regexp.Regexp // specifies the regexp to use for query string obfuscation.
queryString bool // reports whether the query string should be included in the URL span tag.
traceClientIP bool
isStatusError func(statusCode int) bool
queryStringRegexp *regexp.Regexp // specifies the regexp to use for query string obfuscation.
queryString bool // reports whether the query string should be included in the URL span tag.
traceClientIP bool
isStatusError func(statusCode int) bool
inferredProxyServicesEnabled bool
}

// ResetCfg sets local variable cfg back to its defaults (mainly useful for testing)
Expand All @@ -45,10 +48,11 @@ func ResetCfg() {

func newConfig() config {
c := config{
queryString: !internal.BoolEnv(envQueryStringDisabled, false),
queryStringRegexp: defaultQueryStringRegexp,
traceClientIP: internal.BoolEnv(envTraceClientIPEnabled, false),
isStatusError: isServerError,
queryString: !internal.BoolEnv(envQueryStringDisabled, false),
queryStringRegexp: defaultQueryStringRegexp,
traceClientIP: internal.BoolEnv(envTraceClientIPEnabled, false),
isStatusError: isServerError,
inferredProxyServicesEnabled: internal.BoolEnv(envInferredProxyServicesEnabled, false),
}
v := os.Getenv(envServerErrorStatuses)
if fn := GetErrorCodesFromInput(v); fn != nil {
Expand Down
65 changes: 58 additions & 7 deletions contrib/internal/httptrace/httptrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,54 @@ var (
cfg = newConfig()
)

type inferredSpanCreatedCtxKey struct{}

type FinishSpanFunc = func(status int, errorFn func(int) bool, opts ...tracer.FinishOption)

// StartRequestSpan starts an HTTP request span with the standard list of HTTP request span tags (http.method, http.url,
// http.useragent). Any further span start option can be added with opts.
func StartRequestSpan(r *http.Request, opts ...ddtrace.StartSpanOption) (tracer.Span, context.Context) {
func StartRequestSpan(r *http.Request, opts ...ddtrace.StartSpanOption) (tracer.Span, context.Context, FinishSpanFunc) {
// Append our span options before the given ones so that the caller can "overwrite" them.
// TODO(): rework span start option handling (https://github.com/DataDog/dd-trace-go/issues/1352)

var ipTags map[string]string
if cfg.traceClientIP {
ipTags, _ = httpsec.ClientIPTags(r.Header, true, r.RemoteAddr)
}

nopts := make([]ddtrace.StartSpanOption, 0, len(opts)+1+len(ipTags))
inferredStartSpanOpts := make([]ddtrace.StartSpanOption, 0, 1)

spanParentCtx, spanParentErr := tracer.Extract(tracer.HTTPHeadersCarrier(r.Header))
var spanLinksCtx ddtrace.SpanContextWithLinks

if spanParentErr == nil {
if linksCtx, ok := spanParentCtx.(ddtrace.SpanContextWithLinks); ok {
spanLinksCtx = linksCtx
inferredStartSpanOpts = append(inferredStartSpanOpts, tracer.WithSpanLinks(spanLinksCtx.SpanLinks()))
}
}

var inferredProxySpan tracer.Span
inferredProxySpanCreated := false

if created, ok := r.Context().Value(inferredSpanCreatedCtxKey{}).(bool); ok {
inferredProxySpanCreated = created
}

if cfg.inferredProxyServicesEnabled && !inferredProxySpanCreated {
if inferredProxySpan = tryCreateInferredProxySpan(r.Header, spanParentCtx, inferredStartSpanOpts...); inferredProxySpan != nil {
inferredProxySpanCreated = true
spanParentCtx = inferredProxySpan.Context()
}
}

nopts = append(nopts,
func(cfg *ddtrace.StartSpanConfig) {
if cfg.Tags == nil {
cfg.Tags = make(map[string]interface{})
}

cfg.Tags[ext.SpanType] = ext.SpanTypeWeb
cfg.Tags[ext.HTTPMethod] = r.Method
cfg.Tags[ext.HTTPURL] = urlFromRequest(r)
Expand All @@ -50,19 +82,38 @@ func StartRequestSpan(r *http.Request, opts ...ddtrace.StartSpanOption) (tracer.
if r.Host != "" {
cfg.Tags["http.host"] = r.Host
}
if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(r.Header)); err == nil {
// If there are span links as a result of context extraction, add them as a StartSpanOption
if linksCtx, ok := spanctx.(ddtrace.SpanContextWithLinks); ok && linksCtx.SpanLinks() != nil {
tracer.WithSpanLinks(linksCtx.SpanLinks())(cfg)
}

if inferredProxySpanCreated {
tracer.ChildOf(spanParentCtx)(cfg)
} else if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(r.Header)); err == nil {
tracer.ChildOf(spanctx)(cfg)
}

if spanParentErr != nil && !inferredProxySpanCreated && spanLinksCtx != nil {
tracer.WithSpanLinks(spanLinksCtx.SpanLinks())(cfg)
}

for k, v := range ipTags {
cfg.Tags[k] = v
}
})

nopts = append(nopts, opts...)
return tracer.StartSpanFromContext(r.Context(), namingschema.OpName(namingschema.HTTPServer), nopts...)

var requestContext context.Context
if inferredProxySpan != nil {
requestContext = context.WithValue(r.Context(), inferredSpanCreatedCtxKey{}, true)
} else {
requestContext = context.WithValue(r.Context(), inferredSpanCreatedCtxKey{}, false)
}

span, ctx := tracer.StartSpanFromContext(requestContext, namingschema.OpName(namingschema.HTTPServer), nopts...)
return span, ctx, func(status int, errorFn func(int) bool, opts ...tracer.FinishOption) {
FinishRequestSpan(span, status, errorFn, opts...)
if inferredProxySpanCreated && inferredProxySpan != nil {
FinishRequestSpan(inferredProxySpan, status, errorFn, opts...)
}
}
}

// FinishRequestSpan finishes the given HTTP request span and sets the expected response-related tags such as the status
Expand Down
Loading
Loading