From 87ab562dd9980de518959753ae57007beaed65d4 Mon Sep 17 00:00:00 2001 From: Rodrigo Arguello Date: Wed, 9 Oct 2024 13:41:01 -0400 Subject: [PATCH 1/5] contrib/net/http: refactor tracing --- .../httprouter/internal/tracing/tracing.go | 5 + .../internal/tracing/make_responsewriter.go | 88 ++++++++++++++++++ .../http/internal/tracing/response_writer.go | 44 +++++++++ .../internal/tracing/response_writer_test.go | 31 +++++++ .../http/{ => internal/tracing}/trace_gen.go | 2 +- contrib/net/http/internal/tracing/tracing.go | 60 ++++++++++++ contrib/net/http/trace.go | 93 +------------------ contrib/net/http/trace_test.go | 20 ---- 8 files changed, 234 insertions(+), 109 deletions(-) create mode 100644 contrib/julienschmidt/httprouter/internal/tracing/tracing.go create mode 100644 contrib/net/http/internal/tracing/make_responsewriter.go create mode 100644 contrib/net/http/internal/tracing/response_writer.go create mode 100644 contrib/net/http/internal/tracing/response_writer_test.go rename contrib/net/http/{ => internal/tracing}/trace_gen.go (99%) create mode 100644 contrib/net/http/internal/tracing/tracing.go diff --git a/contrib/julienschmidt/httprouter/internal/tracing/tracing.go b/contrib/julienschmidt/httprouter/internal/tracing/tracing.go new file mode 100644 index 0000000000..53d8189639 --- /dev/null +++ b/contrib/julienschmidt/httprouter/internal/tracing/tracing.go @@ -0,0 +1,5 @@ +package tracing + +func BeforeRequest() { + +} diff --git a/contrib/net/http/internal/tracing/make_responsewriter.go b/contrib/net/http/internal/tracing/make_responsewriter.go new file mode 100644 index 0000000000..cda3d20fc9 --- /dev/null +++ b/contrib/net/http/internal/tracing/make_responsewriter.go @@ -0,0 +1,88 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +//go:build ignore +// +build ignore + +// This program generates wrapper implementations of http.ResponseWriter that +// also satisfy http.Flusher, http.Pusher, http.CloseNotifier and http.Hijacker, +// based on whether or not the passed in http.ResponseWriter also satisfies +// them. + +package main + +import ( + "os" + "text/template" + + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/lists" +) + +func main() { + interfaces := []string{"Flusher", "Pusher", "CloseNotifier", "Hijacker"} + var combos [][][]string + for pick := len(interfaces); pick > 0; pick-- { + combos = append(combos, lists.Combinations(interfaces, pick)) + } + template.Must(template.New("").Parse(tpl)).Execute(os.Stdout, map[string]interface{}{ + "Interfaces": interfaces, + "Combinations": combos, + }) +} + +var tpl = `// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +// Code generated by make_responsewriter.go DO NOT EDIT + +package tracing + +import "net/http" + + +// wrapResponseWriter wraps an underlying http.ResponseWriter so that it can +// trace the http response codes. It also checks for various http interfaces +// (Flusher, Pusher, CloseNotifier, Hijacker) and if the underlying +// http.ResponseWriter implements them it generates an unnamed struct with the +// appropriate fields. +// +// This code is generated because we have to account for all the permutations +// of the interfaces. +// +// In case of any new interfaces or methods we didn't consider here, we also +// implement the rwUnwrapper interface, which is used internally by +// the standard library: https://github.com/golang/go/blob/6d89b38ed86e0bfa0ddaba08dc4071e6bb300eea/src/net/http/responsecontroller.go#L42-L44 +func wrapResponseWriter(w http.ResponseWriter) (http.ResponseWriter, *responseWriter) { +{{- range .Interfaces }} + h{{.}}, ok{{.}} := w.(http.{{.}}) +{{- end }} + + mw := newResponseWriter(w) + type monitoredResponseWriter interface { + http.ResponseWriter + Status() int + Unwrap() http.ResponseWriter + } + switch { +{{- range .Combinations }} + {{- range . }} + case {{ range $i, $v := . }}{{ if gt $i 0 }} && {{ end }}ok{{ $v }}{{ end }}: + w = struct { + monitoredResponseWriter + {{- range . }} + http.{{.}} + {{- end }} + }{mw{{ range . }}, h{{.}}{{ end }}} + {{- end }} +{{- end }} + default: + w = mw + } + + return w, mw +} +` diff --git a/contrib/net/http/internal/tracing/response_writer.go b/contrib/net/http/internal/tracing/response_writer.go new file mode 100644 index 0000000000..52a1cab754 --- /dev/null +++ b/contrib/net/http/internal/tracing/response_writer.go @@ -0,0 +1,44 @@ +package tracing + +import "net/http" + +// responseWriter is a small wrapper around an http response writer that will +// intercept and store the status of a request. +type responseWriter struct { + http.ResponseWriter + status int +} + +func newResponseWriter(w http.ResponseWriter) *responseWriter { + return &responseWriter{w, 0} +} + +// Status returns the status code that was monitored. +func (w *responseWriter) Status() int { + return w.status +} + +// Write writes the data to the connection as part of an HTTP reply. +// We explicitly call WriteHeader with the 200 status code +// in order to get it reported into the span. +func (w *responseWriter) Write(b []byte) (int, error) { + if w.status == 0 { + w.WriteHeader(http.StatusOK) + } + return w.ResponseWriter.Write(b) +} + +// WriteHeader sends an HTTP response header with status code. +// It also sets the status code to the span. +func (w *responseWriter) WriteHeader(status int) { + if w.status != 0 { + return + } + w.ResponseWriter.WriteHeader(status) + w.status = status +} + +// Unwrap returns the underlying wrapped http.ResponseWriter. +func (w *responseWriter) Unwrap() http.ResponseWriter { + return w.ResponseWriter +} diff --git a/contrib/net/http/internal/tracing/response_writer_test.go b/contrib/net/http/internal/tracing/response_writer_test.go new file mode 100644 index 0000000000..8c19df1ffe --- /dev/null +++ b/contrib/net/http/internal/tracing/response_writer_test.go @@ -0,0 +1,31 @@ +package tracing + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_wrapResponseWriter(t *testing.T) { + // there doesn't appear to be an easy way to test http.Pusher support via an http request + // so we'll just confirm wrapResponseWriter preserves it + t.Run("Pusher", func(t *testing.T) { + var i struct { + http.ResponseWriter + http.Pusher + } + var w http.ResponseWriter = i + _, ok := w.(http.ResponseWriter) + assert.True(t, ok) + _, ok = w.(http.Pusher) + assert.True(t, ok) + + w, _ = wrapResponseWriter(w) + _, ok = w.(http.ResponseWriter) + assert.True(t, ok) + _, ok = w.(http.Pusher) + assert.True(t, ok) + }) + +} diff --git a/contrib/net/http/trace_gen.go b/contrib/net/http/internal/tracing/trace_gen.go similarity index 99% rename from contrib/net/http/trace_gen.go rename to contrib/net/http/internal/tracing/trace_gen.go index db04144454..773e7d43b6 100644 --- a/contrib/net/http/trace_gen.go +++ b/contrib/net/http/internal/tracing/trace_gen.go @@ -5,7 +5,7 @@ // Code generated by make_responsewriter.go DO NOT EDIT -package http +package tracing import "net/http" diff --git a/contrib/net/http/internal/tracing/tracing.go b/contrib/net/http/internal/tracing/tracing.go new file mode 100644 index 0000000000..7307eb2ca3 --- /dev/null +++ b/contrib/net/http/internal/tracing/tracing.go @@ -0,0 +1,60 @@ +package tracing + +//go:generate sh -c "go run make_responsewriter.go | gofmt > trace_gen.go" + +import ( + "net/http" + + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/options" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" +) + +// ServeConfig specifies the tracing configuration when using TraceAndServe. +type ServeConfig struct { + // Service specifies the service name to use. If left blank, the global service name + // will be inherited. + Service string + // Resource optionally specifies the resource name for this request. + Resource string + // QueryParams should be true in order to append the URL query values to the "http.url" tag. + QueryParams bool + // Route is the request matched route if any, or is empty otherwise + Route string + // RouteParams specifies framework-specific route parameters (e.g. for route /user/:id coming + // in as /user/123 we'll have {"id": "123"}). This field is optional and is used for monitoring + // by AppSec. It is only taken into account when AppSec is enabled. + RouteParams map[string]string + // FinishOpts specifies any options to be used when finishing the request span. + FinishOpts []ddtrace.FinishOption + // SpanOpts specifies any options to be applied to the request starting span. + SpanOpts []ddtrace.StartSpanOption +} + +func BeforeHandle(cfg *ServeConfig, w http.ResponseWriter, r *http.Request) (http.ResponseWriter, *http.Request, func()) { + if cfg == nil { + cfg = new(ServeConfig) + } + opts := options.Copy(cfg.SpanOpts...) // make a copy of cfg.SpanOpts to avoid races + if cfg.Service != "" { + opts = append(opts, tracer.ServiceName(cfg.Service)) + } + if cfg.Resource != "" { + opts = append(opts, tracer.ResourceName(cfg.Resource)) + } + if cfg.Route != "" { + opts = append(opts, tracer.Tag(ext.HTTPRoute, cfg.Route)) + } + span, ctx := httptrace.StartRequestSpan(r, opts...) + rw, ddrw := wrapResponseWriter(w) + afterHandle := func() { + httptrace.FinishRequestSpan(span, ddrw.status, cfg.FinishOpts...) + } + + //if appsec.Enabled() { + // h = httpsec.WrapHandler(h, span, cfg.RouteParams, nil) + //} + return rw, r.WithContext(ctx), afterHandle +} diff --git a/contrib/net/http/trace.go b/contrib/net/http/trace.go index b04867c931..4aa6a301ec 100644 --- a/contrib/net/http/trace.go +++ b/contrib/net/http/trace.go @@ -10,13 +10,8 @@ package http // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http" import ( "net/http" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/options" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http/internal/tracing" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" - "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" - "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/httpsec" "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" ) @@ -28,91 +23,13 @@ func init() { } // ServeConfig specifies the tracing configuration when using TraceAndServe. -type ServeConfig struct { - // Service specifies the service name to use. If left blank, the global service name - // will be inherited. - Service string - // Resource optionally specifies the resource name for this request. - Resource string - // QueryParams should be true in order to append the URL query values to the "http.url" tag. - QueryParams bool - // Route is the request matched route if any, or is empty otherwise - Route string - // RouteParams specifies framework-specific route parameters (e.g. for route /user/:id coming - // in as /user/123 we'll have {"id": "123"}). This field is optional and is used for monitoring - // by AppSec. It is only taken into account when AppSec is enabled. - RouteParams map[string]string - // FinishOpts specifies any options to be used when finishing the request span. - FinishOpts []ddtrace.FinishOption - // SpanOpts specifies any options to be applied to the request starting span. - SpanOpts []ddtrace.StartSpanOption -} +type ServeConfig = tracing.ServeConfig // TraceAndServe serves the handler h using the given ResponseWriter and Request, applying tracing // according to the specified config. func TraceAndServe(h http.Handler, w http.ResponseWriter, r *http.Request, cfg *ServeConfig) { - if cfg == nil { - cfg = new(ServeConfig) - } - opts := options.Copy(cfg.SpanOpts...) // make a copy of cfg.SpanOpts to avoid races - if cfg.Service != "" { - opts = append(opts, tracer.ServiceName(cfg.Service)) - } - if cfg.Resource != "" { - opts = append(opts, tracer.ResourceName(cfg.Resource)) - } - if cfg.Route != "" { - opts = append(opts, tracer.Tag(ext.HTTPRoute, cfg.Route)) - } - span, ctx := httptrace.StartRequestSpan(r, opts...) - rw, ddrw := wrapResponseWriter(w) - defer func() { - httptrace.FinishRequestSpan(span, ddrw.status, cfg.FinishOpts...) - }() - - if appsec.Enabled() { - h = httpsec.WrapHandler(h, span, cfg.RouteParams, nil) - } - h.ServeHTTP(rw, r.WithContext(ctx)) -} - -// responseWriter is a small wrapper around an http response writer that will -// intercept and store the status of a request. -type responseWriter struct { - http.ResponseWriter - status int -} - -func newResponseWriter(w http.ResponseWriter) *responseWriter { - return &responseWriter{w, 0} -} - -// Status returns the status code that was monitored. -func (w *responseWriter) Status() int { - return w.status -} - -// Write writes the data to the connection as part of an HTTP reply. -// We explicitly call WriteHeader with the 200 status code -// in order to get it reported into the span. -func (w *responseWriter) Write(b []byte) (int, error) { - if w.status == 0 { - w.WriteHeader(http.StatusOK) - } - return w.ResponseWriter.Write(b) -} - -// WriteHeader sends an HTTP response header with status code. -// It also sets the status code to the span. -func (w *responseWriter) WriteHeader(status int) { - if w.status != 0 { - return - } - w.ResponseWriter.WriteHeader(status) - w.status = status -} + tw, tr, afterHandle := tracing.BeforeHandle(cfg, w, r) + defer afterHandle() -// Unwrap returns the underlying wrapped http.ResponseWriter. -func (w *responseWriter) Unwrap() http.ResponseWriter { - return w.ResponseWriter + h.ServeHTTP(tw, tr) } diff --git a/contrib/net/http/trace_test.go b/contrib/net/http/trace_test.go index fe8e062973..e00aaa95fe 100644 --- a/contrib/net/http/trace_test.go +++ b/contrib/net/http/trace_test.go @@ -146,26 +146,6 @@ func TestTraceAndServe(t *testing.T) { assert.Equal("Hello, world!\n", string(slurp)) }) - // there doesn't appear to be an easy way to test http.Pusher support via an http request - // so we'll just confirm wrapResponseWriter preserves it - t.Run("Pusher", func(t *testing.T) { - var i struct { - http.ResponseWriter - http.Pusher - } - var w http.ResponseWriter = i - _, ok := w.(http.ResponseWriter) - assert.True(t, ok) - _, ok = w.(http.Pusher) - assert.True(t, ok) - - w, _ = wrapResponseWriter(w) - _, ok = w.(http.ResponseWriter) - assert.True(t, ok) - _, ok = w.(http.Pusher) - assert.True(t, ok) - }) - t.Run("distributed", func(t *testing.T) { mt := mocktracer.Start() assert := assert.New(t) From 61beec7f2d59e9b8dc7e44a2cda86ac88a98994a Mon Sep 17 00:00:00 2001 From: Rodrigo Arguello Date: Wed, 9 Oct 2024 15:13:19 -0400 Subject: [PATCH 2/5] refactor --- .../httptrace/before_handle.go} | 10 +- .../httptrace}/make_responsewriter.go | 2 +- .../httptrace}/response_writer.go | 2 +- .../httptrace}/response_writer_test.go | 2 +- .../httptrace}/trace_gen.go | 2 +- .../julienschmidt/httprouter/example_test.go | 2 + .../julienschmidt/httprouter/httprouter.go | 83 ++++++++--------- .../httprouter/internal/tracing/config.go | 92 +++++++++++++++++++ .../httprouter/internal/tracing/tracing.go | 45 ++++++++- contrib/julienschmidt/httprouter/option.go | 57 ++---------- contrib/net/http/trace.go | 8 +- 11 files changed, 195 insertions(+), 110 deletions(-) rename contrib/{net/http/internal/tracing/tracing.go => internal/httptrace/before_handle.go} (87%) rename contrib/{net/http/internal/tracing => internal/httptrace}/make_responsewriter.go (99%) rename contrib/{net/http/internal/tracing => internal/httptrace}/response_writer.go (98%) rename contrib/{net/http/internal/tracing => internal/httptrace}/response_writer_test.go (97%) rename contrib/{net/http/internal/tracing => internal/httptrace}/trace_gen.go (99%) create mode 100644 contrib/julienschmidt/httprouter/internal/tracing/config.go diff --git a/contrib/net/http/internal/tracing/tracing.go b/contrib/internal/httptrace/before_handle.go similarity index 87% rename from contrib/net/http/internal/tracing/tracing.go rename to contrib/internal/httptrace/before_handle.go index 7307eb2ca3..97cc2dd452 100644 --- a/contrib/net/http/internal/tracing/tracing.go +++ b/contrib/internal/httptrace/before_handle.go @@ -1,11 +1,8 @@ -package tracing - -//go:generate sh -c "go run make_responsewriter.go | gofmt > trace_gen.go" +package httptrace import ( "net/http" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/options" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" @@ -47,12 +44,13 @@ 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 := httptrace.StartRequestSpan(r, opts...) + span, ctx := StartRequestSpan(r, opts...) rw, ddrw := wrapResponseWriter(w) afterHandle := func() { - httptrace.FinishRequestSpan(span, ddrw.status, cfg.FinishOpts...) + FinishRequestSpan(span, ddrw.status, cfg.FinishOpts...) } + // TODO: find a way to run this //if appsec.Enabled() { // h = httpsec.WrapHandler(h, span, cfg.RouteParams, nil) //} diff --git a/contrib/net/http/internal/tracing/make_responsewriter.go b/contrib/internal/httptrace/make_responsewriter.go similarity index 99% rename from contrib/net/http/internal/tracing/make_responsewriter.go rename to contrib/internal/httptrace/make_responsewriter.go index cda3d20fc9..da08b62d99 100644 --- a/contrib/net/http/internal/tracing/make_responsewriter.go +++ b/contrib/internal/httptrace/make_responsewriter.go @@ -39,7 +39,7 @@ var tpl = `// Unless explicitly stated otherwise all files in this repository ar // Code generated by make_responsewriter.go DO NOT EDIT -package tracing +package httptrace import "net/http" diff --git a/contrib/net/http/internal/tracing/response_writer.go b/contrib/internal/httptrace/response_writer.go similarity index 98% rename from contrib/net/http/internal/tracing/response_writer.go rename to contrib/internal/httptrace/response_writer.go index 52a1cab754..6d78a7a0d1 100644 --- a/contrib/net/http/internal/tracing/response_writer.go +++ b/contrib/internal/httptrace/response_writer.go @@ -1,4 +1,4 @@ -package tracing +package httptrace import "net/http" diff --git a/contrib/net/http/internal/tracing/response_writer_test.go b/contrib/internal/httptrace/response_writer_test.go similarity index 97% rename from contrib/net/http/internal/tracing/response_writer_test.go rename to contrib/internal/httptrace/response_writer_test.go index 8c19df1ffe..1bd17205aa 100644 --- a/contrib/net/http/internal/tracing/response_writer_test.go +++ b/contrib/internal/httptrace/response_writer_test.go @@ -1,4 +1,4 @@ -package tracing +package httptrace import ( "net/http" diff --git a/contrib/net/http/internal/tracing/trace_gen.go b/contrib/internal/httptrace/trace_gen.go similarity index 99% rename from contrib/net/http/internal/tracing/trace_gen.go rename to contrib/internal/httptrace/trace_gen.go index 773e7d43b6..24e261838e 100644 --- a/contrib/net/http/internal/tracing/trace_gen.go +++ b/contrib/internal/httptrace/trace_gen.go @@ -5,7 +5,7 @@ // Code generated by make_responsewriter.go DO NOT EDIT -package tracing +package httptrace import "net/http" diff --git a/contrib/julienschmidt/httprouter/example_test.go b/contrib/julienschmidt/httprouter/example_test.go index 497ada0da8..d821c09665 100644 --- a/contrib/julienschmidt/httprouter/example_test.go +++ b/contrib/julienschmidt/httprouter/example_test.go @@ -26,6 +26,8 @@ func Hello(w http.ResponseWriter, _ *http.Request, ps httprouter.Params) { } func Example() { + httprouter.New() + router := httptrace.New() router.GET("/", Index) router.GET("/hello/:name", Hello) diff --git a/contrib/julienschmidt/httprouter/httprouter.go b/contrib/julienschmidt/httprouter/httprouter.go index 12147a213f..286c593ebd 100644 --- a/contrib/julienschmidt/httprouter/httprouter.go +++ b/contrib/julienschmidt/httprouter/httprouter.go @@ -7,68 +7,63 @@ package httprouter // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/julienschmidt/httprouter" import ( - "math" "net/http" - "strings" - - httptraceinternal "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/options" - httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" - "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" "github.com/julienschmidt/httprouter" -) - -const componentName = "julienschmidt/httprouter" -func init() { - telemetry.LoadIntegration(componentName) - tracer.MarkIntegrationImported("github.com/julienschmidt/httprouter") -} + "gopkg.in/DataDog/dd-trace-go.v1/contrib/julienschmidt/httprouter/internal/tracing" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" +) // Router is a traced version of httprouter.Router. type Router struct { *httprouter.Router - config *routerConfig + config *tracing.Config } // New returns a new router augmented with tracing. func New(opts ...RouterOption) *Router { - cfg := new(routerConfig) - defaults(cfg) - for _, fn := range opts { - fn(cfg) - } - if !math.IsNaN(cfg.analyticsRate) { - cfg.spanOpts = append(cfg.spanOpts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) - } - - cfg.spanOpts = append(cfg.spanOpts, tracer.Tag(ext.SpanKind, ext.SpanKindServer)) - cfg.spanOpts = append(cfg.spanOpts, tracer.Tag(ext.Component, componentName)) - + cfg := tracing.NewConfig(opts...) log.Debug("contrib/julienschmidt/httprouter: Configuring Router: %#v", cfg) return &Router{httprouter.New(), cfg} } // ServeHTTP implements http.Handler. func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { - // get the resource associated to this request - route := req.URL.Path - _, ps, _ := r.Router.Lookup(req.Method, route) - for _, param := range ps { - route = strings.Replace(route, param.Value, ":"+param.Key, 1) + tw, treq, afterHandle := tracing.BeforeHandle(r.config, r.Router, wrapRouter, w, req) + defer afterHandle() + r.Router.ServeHTTP(tw, treq) +} + +type wRouter struct { + *httprouter.Router +} + +func wrapRouter(r *httprouter.Router) tracing.Router { + return &wRouter{r} +} + +func (w wRouter) Lookup(method string, path string) (any, []tracing.Param, bool) { + h, params, ok := w.Router.Lookup(method, path) + return h, wrapParams(params), ok +} + +type wParam struct { + httprouter.Param +} + +func wrapParams(params httprouter.Params) []tracing.Param { + wParams := make([]tracing.Param, len(params)) + for i, p := range params { + wParams[i] = wParam{p} } - resource := req.Method + " " + route - spanOpts := options.Copy(r.config.spanOpts...) // spanOpts must be a copy of r.config.spanOpts, locally scoped, to avoid races. - spanOpts = append(spanOpts, httptraceinternal.HeaderTagsFromRequest(req, r.config.headerTags)) + return wParams +} + +func (w wParam) GetKey() string { + return w.Key +} - httptrace.TraceAndServe(r.Router, w, req, &httptrace.ServeConfig{ - Service: r.config.serviceName, - Resource: resource, - SpanOpts: spanOpts, - Route: route, - }) +func (w wParam) GetValue() string { + return w.Value } diff --git a/contrib/julienschmidt/httprouter/internal/tracing/config.go b/contrib/julienschmidt/httprouter/internal/tracing/config.go new file mode 100644 index 0000000000..b116c26054 --- /dev/null +++ b/contrib/julienschmidt/httprouter/internal/tracing/config.go @@ -0,0 +1,92 @@ +package tracing + +import ( + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + "gopkg.in/DataDog/dd-trace-go.v1/internal/namingschema" + "gopkg.in/DataDog/dd-trace-go.v1/internal/normalizer" + "math" +) + +const defaultServiceName = "http.router" + +type Config struct { + serviceName string + spanOpts []ddtrace.StartSpanOption + analyticsRate float64 + headerTags *internal.LockMap +} + +func NewConfig(opts ...Option) *Config { + cfg := new(Config) + if internal.BoolEnv("DD_TRACE_HTTPROUTER_ANALYTICS_ENABLED", false) { + cfg.analyticsRate = 1.0 + } else { + cfg.analyticsRate = globalconfig.AnalyticsRate() + } + cfg.serviceName = namingschema.ServiceName(defaultServiceName) + cfg.headerTags = globalconfig.HeaderTagMap() + for _, fn := range opts { + fn(cfg) + } + if !math.IsNaN(cfg.analyticsRate) { + cfg.spanOpts = append(cfg.spanOpts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) + } + + cfg.spanOpts = append(cfg.spanOpts, tracer.Tag(ext.SpanKind, ext.SpanKindServer)) + cfg.spanOpts = append(cfg.spanOpts, tracer.Tag(ext.Component, componentName)) + return cfg +} + +type Option func(*Config) + +// WithServiceName sets the given service name for the returned router. +func WithServiceName(name string) Option { + return func(cfg *Config) { + cfg.serviceName = name + } +} + +// WithSpanOptions applies the given set of options to the span started by the router. +func WithSpanOptions(opts ...ddtrace.StartSpanOption) Option { + return func(cfg *Config) { + cfg.spanOpts = opts + } +} + +// WithAnalytics enables Trace Analytics for all started spans. +func WithAnalytics(on bool) Option { + return func(cfg *Config) { + if on { + cfg.analyticsRate = 1.0 + } else { + cfg.analyticsRate = math.NaN() + } + } +} + +// WithAnalyticsRate sets the sampling rate for Trace Analytics events +// correlated to started spans. +func WithAnalyticsRate(rate float64) Option { + return func(cfg *Config) { + if rate >= 0.0 && rate <= 1.0 { + cfg.analyticsRate = rate + } else { + cfg.analyticsRate = math.NaN() + } + } +} + +// WithHeaderTags enables the integration to attach HTTP request headers as span tags. +// Warning: +// Using this feature can risk exposing sensitive data such as authorization tokens to Datadog. +// Special headers can not be sub-selected. E.g., an entire Cookie header would be transmitted, without the ability to choose specific Cookies. +func WithHeaderTags(headers []string) Option { + headerTagsMap := normalizer.HeaderTagSlice(headers) + return func(cfg *Config) { + cfg.headerTags = internal.NewLockMap(headerTagsMap) + } +} diff --git a/contrib/julienschmidt/httprouter/internal/tracing/tracing.go b/contrib/julienschmidt/httprouter/internal/tracing/tracing.go index 53d8189639..03faf2179d 100644 --- a/contrib/julienschmidt/httprouter/internal/tracing/tracing.go +++ b/contrib/julienschmidt/httprouter/internal/tracing/tracing.go @@ -1,5 +1,48 @@ package tracing -func BeforeRequest() { +import ( + httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/options" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" + "net/http" + "strings" +) +const componentName = "julienschmidt/httprouter" + +func init() { + telemetry.LoadIntegration(componentName) + tracer.MarkIntegrationImported("github.com/julienschmidt/httprouter") +} + +type Router interface { + Lookup(method string, path string) (any, []Param, bool) +} + +type Param interface { + GetKey() string + GetValue() string +} + +func BeforeHandle[T any, WT Router](cfg *Config, router T, wrapRouter func(T) WT, w http.ResponseWriter, req *http.Request) (http.ResponseWriter, *http.Request, func()) { + wRouter := wrapRouter(router) + // get the resource associated to this request + route := req.URL.Path + _, ps, _ := wRouter.Lookup(req.Method, route) + for _, param := range ps { + route = strings.Replace(route, param.GetValue(), ":"+param.GetKey(), 1) + } + + resource := req.Method + " " + route + spanOpts := options.Copy(cfg.spanOpts...) // spanOpts must be a copy of r.config.spanOpts, locally scoped, to avoid races. + spanOpts = append(spanOpts, httptrace.HeaderTagsFromRequest(req, cfg.headerTags)) + + serveCfg := &httptrace.ServeConfig{ + Service: cfg.serviceName, + Resource: resource, + SpanOpts: spanOpts, + Route: route, + } + return httptrace.BeforeHandle(serveCfg, w, req) } diff --git a/contrib/julienschmidt/httprouter/option.go b/contrib/julienschmidt/httprouter/option.go index e8dbf3720b..295a7705c2 100644 --- a/contrib/julienschmidt/httprouter/option.go +++ b/contrib/julienschmidt/httprouter/option.go @@ -6,13 +6,9 @@ package httprouter import ( - "math" - + "gopkg.in/DataDog/dd-trace-go.v1/contrib/julienschmidt/httprouter/internal/tracing" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/internal" - "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" - "gopkg.in/DataDog/dd-trace-go.v1/internal/namingschema" - "gopkg.in/DataDog/dd-trace-go.v1/internal/normalizer" ) const defaultServiceName = "http.router" @@ -25,62 +21,23 @@ type routerConfig struct { } // RouterOption represents an option that can be passed to New. -type RouterOption func(*routerConfig) - -func defaults(cfg *routerConfig) { - if internal.BoolEnv("DD_TRACE_HTTPROUTER_ANALYTICS_ENABLED", false) { - cfg.analyticsRate = 1.0 - } else { - cfg.analyticsRate = globalconfig.AnalyticsRate() - } - cfg.serviceName = namingschema.ServiceName(defaultServiceName) - cfg.headerTags = globalconfig.HeaderTagMap() -} +type RouterOption = tracing.Option // WithServiceName sets the given service name for the returned router. -func WithServiceName(name string) RouterOption { - return func(cfg *routerConfig) { - cfg.serviceName = name - } -} +var WithServiceName = tracing.WithServiceName // WithSpanOptions applies the given set of options to the span started by the router. -func WithSpanOptions(opts ...ddtrace.StartSpanOption) RouterOption { - return func(cfg *routerConfig) { - cfg.spanOpts = opts - } -} +var WithSpanOptions = tracing.WithSpanOptions // WithAnalytics enables Trace Analytics for all started spans. -func WithAnalytics(on bool) RouterOption { - return func(cfg *routerConfig) { - if on { - cfg.analyticsRate = 1.0 - } else { - cfg.analyticsRate = math.NaN() - } - } -} +var WithAnalytics = tracing.WithAnalytics // WithAnalyticsRate sets the sampling rate for Trace Analytics events // correlated to started spans. -func WithAnalyticsRate(rate float64) RouterOption { - return func(cfg *routerConfig) { - if rate >= 0.0 && rate <= 1.0 { - cfg.analyticsRate = rate - } else { - cfg.analyticsRate = math.NaN() - } - } -} +var WithAnalyticsRate = tracing.WithAnalyticsRate // WithHeaderTags enables the integration to attach HTTP request headers as span tags. // Warning: // Using this feature can risk exposing sensitive data such as authorization tokens to Datadog. // Special headers can not be sub-selected. E.g., an entire Cookie header would be transmitted, without the ability to choose specific Cookies. -func WithHeaderTags(headers []string) RouterOption { - headerTagsMap := normalizer.HeaderTagSlice(headers) - return func(cfg *routerConfig) { - cfg.headerTags = internal.NewLockMap(headerTagsMap) - } -} +var WithHeaderTags = tracing.WithHeaderTags diff --git a/contrib/net/http/trace.go b/contrib/net/http/trace.go index 4aa6a301ec..c955d1ec80 100644 --- a/contrib/net/http/trace.go +++ b/contrib/net/http/trace.go @@ -5,12 +5,10 @@ package http // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http" -//go:generate sh -c "go run make_responsewriter.go | gofmt > trace_gen.go" - import ( "net/http" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http/internal/tracing" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" ) @@ -23,12 +21,12 @@ func init() { } // ServeConfig specifies the tracing configuration when using TraceAndServe. -type ServeConfig = tracing.ServeConfig +type ServeConfig = httptrace.ServeConfig // TraceAndServe serves the handler h using the given ResponseWriter and Request, applying tracing // according to the specified config. func TraceAndServe(h http.Handler, w http.ResponseWriter, r *http.Request, cfg *ServeConfig) { - tw, tr, afterHandle := tracing.BeforeHandle(cfg, w, r) + tw, tr, afterHandle := httptrace.BeforeHandle(cfg, w, r) defer afterHandle() h.ServeHTTP(tw, tr) From 6f6d7d6e54f455e90723227379f248c03941b9c7 Mon Sep 17 00:00:00 2001 From: Rodrigo Arguello Date: Thu, 10 Oct 2024 10:24:39 -0400 Subject: [PATCH 3/5] add appsec --- contrib/internal/httptrace/before_handle.go | 27 +++-- .../julienschmidt/httprouter/example_test.go | 2 - .../julienschmidt/httprouter/httprouter.go | 5 +- .../httprouter/internal/tracing/tracing.go | 9 +- contrib/net/http/trace.go | 5 +- internal/appsec/emitter/httpsec/http.go | 103 ++++++++++-------- 6 files changed, 90 insertions(+), 61 deletions(-) diff --git a/contrib/internal/httptrace/before_handle.go b/contrib/internal/httptrace/before_handle.go index 97cc2dd452..8a512e8420 100644 --- a/contrib/internal/httptrace/before_handle.go +++ b/contrib/internal/httptrace/before_handle.go @@ -1,6 +1,8 @@ package httptrace import ( + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/httpsec" "net/http" "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/options" @@ -30,7 +32,7 @@ type ServeConfig struct { SpanOpts []ddtrace.StartSpanOption } -func BeforeHandle(cfg *ServeConfig, w http.ResponseWriter, r *http.Request) (http.ResponseWriter, *http.Request, func()) { +func BeforeHandle(cfg *ServeConfig, w http.ResponseWriter, r *http.Request) (http.ResponseWriter, *http.Request, func(), bool) { if cfg == nil { cfg = new(ServeConfig) } @@ -46,13 +48,22 @@ func BeforeHandle(cfg *ServeConfig, w http.ResponseWriter, r *http.Request) (htt } span, ctx := StartRequestSpan(r, opts...) rw, ddrw := wrapResponseWriter(w) - afterHandle := func() { + rt := r.WithContext(ctx) + + closeSpan := func() { FinishRequestSpan(span, ddrw.status, cfg.FinishOpts...) } - - // TODO: find a way to run this - //if appsec.Enabled() { - // h = httpsec.WrapHandler(h, span, cfg.RouteParams, nil) - //} - return rw, r.WithContext(ctx), afterHandle + afterHandle := closeSpan + handled := false + if appsec.Enabled() { + secW, secReq, secAfterHandle, secHandled := httpsec.BeforeHandle(rw, rt, span, cfg.RouteParams, nil) + afterHandle = func() { + secAfterHandle() + closeSpan() + } + rw = secW + rt = secReq + handled = secHandled + } + return rw, rt, afterHandle, handled } diff --git a/contrib/julienschmidt/httprouter/example_test.go b/contrib/julienschmidt/httprouter/example_test.go index d821c09665..497ada0da8 100644 --- a/contrib/julienschmidt/httprouter/example_test.go +++ b/contrib/julienschmidt/httprouter/example_test.go @@ -26,8 +26,6 @@ func Hello(w http.ResponseWriter, _ *http.Request, ps httprouter.Params) { } func Example() { - httprouter.New() - router := httptrace.New() router.GET("/", Index) router.GET("/hello/:name", Hello) diff --git a/contrib/julienschmidt/httprouter/httprouter.go b/contrib/julienschmidt/httprouter/httprouter.go index 286c593ebd..cffaa28876 100644 --- a/contrib/julienschmidt/httprouter/httprouter.go +++ b/contrib/julienschmidt/httprouter/httprouter.go @@ -30,8 +30,11 @@ func New(opts ...RouterOption) *Router { // ServeHTTP implements http.Handler. func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { - tw, treq, afterHandle := tracing.BeforeHandle(r.config, r.Router, wrapRouter, w, req) + tw, treq, afterHandle, handled := tracing.BeforeHandle(r.config, r.Router, wrapRouter, w, req) defer afterHandle() + if handled { + return + } r.Router.ServeHTTP(tw, treq) } diff --git a/contrib/julienschmidt/httprouter/internal/tracing/tracing.go b/contrib/julienschmidt/httprouter/internal/tracing/tracing.go index 03faf2179d..8c7c92b4e2 100644 --- a/contrib/julienschmidt/httprouter/internal/tracing/tracing.go +++ b/contrib/julienschmidt/httprouter/internal/tracing/tracing.go @@ -1,12 +1,13 @@ package tracing import ( - httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" + "net/http" + "strings" + + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/options" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" - "net/http" - "strings" ) const componentName = "julienschmidt/httprouter" @@ -25,7 +26,7 @@ type Param interface { GetValue() string } -func BeforeHandle[T any, WT Router](cfg *Config, router T, wrapRouter func(T) WT, w http.ResponseWriter, req *http.Request) (http.ResponseWriter, *http.Request, func()) { +func BeforeHandle[T any, WT Router](cfg *Config, router T, wrapRouter func(T) WT, w http.ResponseWriter, req *http.Request) (http.ResponseWriter, *http.Request, func(), bool) { wRouter := wrapRouter(router) // get the resource associated to this request route := req.URL.Path diff --git a/contrib/net/http/trace.go b/contrib/net/http/trace.go index c955d1ec80..f78aa19a9a 100644 --- a/contrib/net/http/trace.go +++ b/contrib/net/http/trace.go @@ -26,8 +26,11 @@ type ServeConfig = httptrace.ServeConfig // TraceAndServe serves the handler h using the given ResponseWriter and Request, applying tracing // according to the specified config. func TraceAndServe(h http.Handler, w http.ResponseWriter, r *http.Request, cfg *ServeConfig) { - tw, tr, afterHandle := httptrace.BeforeHandle(cfg, w, r) + tw, tr, afterHandle, handled := httptrace.BeforeHandle(cfg, w, r) defer afterHandle() + if handled { + return + } h.ServeHTTP(tw, tr) } diff --git a/internal/appsec/emitter/httpsec/http.go b/internal/appsec/emitter/httpsec/http.go index 8552a71343..7ed885b12d 100644 --- a/internal/appsec/emitter/httpsec/http.go +++ b/internal/appsec/emitter/httpsec/http.go @@ -108,61 +108,74 @@ func makeCookies(parsed []*http.Cookie) map[string][]string { return cookies } -// WrapHandler wraps the given HTTP handler with the abstract HTTP operation defined by HandlerOperationArgs and -// HandlerOperationRes. -// The onBlock params are used to cleanup the context when needed. -// It is a specific patch meant for Gin, for which we must abort the -// context since it uses a queue of handlers and it's the only way to make -// sure other queued handlers don't get executed. -// TODO: this patch must be removed/improved when we rework our actions/operations system -func WrapHandler(handler http.Handler, span ddtrace.Span, pathParams map[string]string, opts *Config) http.Handler { +func BeforeHandle(w http.ResponseWriter, r *http.Request, span ddtrace.Span, pathParams map[string]string, opts *Config) ( + http.ResponseWriter, *http.Request, func(), bool) { if opts == nil { opts = defaultWrapHandlerConfig } else if opts.ResponseHeaderCopier == nil { opts.ResponseHeaderCopier = defaultWrapHandlerConfig.ResponseHeaderCopier } - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - op, blockAtomic, ctx := StartOperation(r.Context(), HandlerOperationArgs{ - Method: r.Method, - RequestURI: r.RequestURI, - Host: r.Host, - RemoteAddr: r.RemoteAddr, - Headers: r.Header, - Cookies: makeCookies(r.Cookies()), - QueryParams: r.URL.Query(), - PathParams: pathParams, - }) - r = r.WithContext(ctx) - - defer func() { - var statusCode int - if res, ok := w.(interface{ Status() int }); ok { - statusCode = res.Status() + op, blockAtomic, ctx := StartOperation(r.Context(), HandlerOperationArgs{ + Method: r.Method, + RequestURI: r.RequestURI, + Host: r.Host, + RemoteAddr: r.RemoteAddr, + Headers: r.Header, + Cookies: makeCookies(r.Cookies()), + QueryParams: r.URL.Query(), + PathParams: pathParams, + }) + tr := r.WithContext(ctx) + + afterHandle := func() { + var statusCode int + if res, ok := w.(interface{ Status() int }); ok { + statusCode = res.Status() + } + op.Finish(HandlerOperationRes{ + Headers: opts.ResponseHeaderCopier(w), + StatusCode: statusCode, + }, span) + + // Execute the onBlock functions to make sure blocking works properly + // in case we are instrumenting the Gin framework + if blockPtr := blockAtomic.Load(); blockPtr != nil { + for _, f := range opts.OnBlock { + f() } - op.Finish(HandlerOperationRes{ - Headers: opts.ResponseHeaderCopier(w), - StatusCode: statusCode, - }, span) - - // Execute the onBlock functions to make sure blocking works properly - // in case we are instrumenting the Gin framework - if blockPtr := blockAtomic.Load(); blockPtr != nil { - for _, f := range opts.OnBlock { - f() - } - - if blockPtr.Handler != nil { - blockPtr.Handler.ServeHTTP(w, r) - } + + if blockPtr.Handler != nil { + blockPtr.Handler.ServeHTTP(w, tr) } - }() + } + } + + handled := false + if blockPtr := blockAtomic.Load(); blockPtr != nil && blockPtr.Handler != nil { + // handler is replaced + blockPtr.Handler.ServeHTTP(w, tr) + blockPtr.Handler = nil + handled = true + } + return w, tr, afterHandle, handled +} - if blockPtr := blockAtomic.Load(); blockPtr != nil && blockPtr.Handler != nil { - handler = blockPtr.Handler - blockPtr.Handler = nil +// WrapHandler wraps the given HTTP handler with the abstract HTTP operation defined by HandlerOperationArgs and +// HandlerOperationRes. +// The onBlock params are used to cleanup the context when needed. +// It is a specific patch meant for Gin, for which we must abort the +// context since it uses a queue of handlers and it's the only way to make +// sure other queued handlers don't get executed. +// TODO: this patch must be removed/improved when we rework our actions/operations system +func WrapHandler(handler http.Handler, span ddtrace.Span, pathParams map[string]string, opts *Config) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tw, tr, afterHandle, handled := BeforeHandle(w, r, span, pathParams, opts) + defer afterHandle() + if handled { + return } - handler.ServeHTTP(w, r) + handler.ServeHTTP(tw, tr) }) } From 55426a1eca27252360c22f9f49f2693c438057d8 Mon Sep 17 00:00:00 2001 From: Rodrigo Arguello Date: Thu, 10 Oct 2024 10:31:06 -0400 Subject: [PATCH 4/5] add copyright header --- contrib/internal/httptrace/before_handle.go | 9 +++++++-- contrib/internal/httptrace/response_writer.go | 7 +++++++ contrib/internal/httptrace/response_writer_test.go | 5 +++++ .../julienschmidt/httprouter/internal/tracing/config.go | 8 +++++++- .../julienschmidt/httprouter/internal/tracing/tracing.go | 5 +++++ 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/contrib/internal/httptrace/before_handle.go b/contrib/internal/httptrace/before_handle.go index 8a512e8420..12f954f32e 100644 --- a/contrib/internal/httptrace/before_handle.go +++ b/contrib/internal/httptrace/before_handle.go @@ -1,14 +1,19 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + package httptrace import ( - "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" - "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/httpsec" "net/http" "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/options" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/httpsec" ) // ServeConfig specifies the tracing configuration when using TraceAndServe. diff --git a/contrib/internal/httptrace/response_writer.go b/contrib/internal/httptrace/response_writer.go index 6d78a7a0d1..2bbc31bad7 100644 --- a/contrib/internal/httptrace/response_writer.go +++ b/contrib/internal/httptrace/response_writer.go @@ -1,5 +1,12 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + package httptrace +//go:generate sh -c "go run make_responsewriter.go | gofmt > trace_gen.go" + import "net/http" // responseWriter is a small wrapper around an http response writer that will diff --git a/contrib/internal/httptrace/response_writer_test.go b/contrib/internal/httptrace/response_writer_test.go index 1bd17205aa..78d5ffc6e2 100644 --- a/contrib/internal/httptrace/response_writer_test.go +++ b/contrib/internal/httptrace/response_writer_test.go @@ -1,3 +1,8 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + package httptrace import ( diff --git a/contrib/julienschmidt/httprouter/internal/tracing/config.go b/contrib/julienschmidt/httprouter/internal/tracing/config.go index b116c26054..533ec2a7cd 100644 --- a/contrib/julienschmidt/httprouter/internal/tracing/config.go +++ b/contrib/julienschmidt/httprouter/internal/tracing/config.go @@ -1,6 +1,13 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + package tracing import ( + "math" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" @@ -8,7 +15,6 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "gopkg.in/DataDog/dd-trace-go.v1/internal/namingschema" "gopkg.in/DataDog/dd-trace-go.v1/internal/normalizer" - "math" ) const defaultServiceName = "http.router" diff --git a/contrib/julienschmidt/httprouter/internal/tracing/tracing.go b/contrib/julienschmidt/httprouter/internal/tracing/tracing.go index 8c7c92b4e2..47c1211c62 100644 --- a/contrib/julienschmidt/httprouter/internal/tracing/tracing.go +++ b/contrib/julienschmidt/httprouter/internal/tracing/tracing.go @@ -1,3 +1,8 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + package tracing import ( From 5a0c9bba7ea7daaf02a0b5ee4e7027d5d4b876f4 Mon Sep 17 00:00:00 2001 From: Rodrigo Arguello Date: Mon, 14 Oct 2024 13:36:31 +0200 Subject: [PATCH 5/5] review comments --- contrib/internal/httptrace/before_handle.go | 4 ++++ .../httprouter/internal/tracing/config.go | 4 ++-- .../httprouter/internal/tracing/tracing.go | 9 ++++++++- internal/appsec/emitter/httpsec/http.go | 13 +++++++++++-- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/contrib/internal/httptrace/before_handle.go b/contrib/internal/httptrace/before_handle.go index 12f954f32e..afd40726ed 100644 --- a/contrib/internal/httptrace/before_handle.go +++ b/contrib/internal/httptrace/before_handle.go @@ -37,6 +37,10 @@ type ServeConfig struct { SpanOpts []ddtrace.StartSpanOption } +// BeforeHandle contains functionality that should be executed before a http.Handler runs. +// It returns the "traced" http.ResponseWriter and http.Request, an additional afterHandle function +// that should be executed after the Handler runs, and a handled bool that instructs if the request has been handled +// or not - in case it was handled, the original handler should not run. func BeforeHandle(cfg *ServeConfig, w http.ResponseWriter, r *http.Request) (http.ResponseWriter, *http.Request, func(), bool) { if cfg == nil { cfg = new(ServeConfig) diff --git a/contrib/julienschmidt/httprouter/internal/tracing/config.go b/contrib/julienschmidt/httprouter/internal/tracing/config.go index 533ec2a7cd..07ee86a765 100644 --- a/contrib/julienschmidt/httprouter/internal/tracing/config.go +++ b/contrib/julienschmidt/httprouter/internal/tracing/config.go @@ -20,10 +20,10 @@ import ( const defaultServiceName = "http.router" type Config struct { - serviceName string + headerTags *internal.LockMap spanOpts []ddtrace.StartSpanOption + serviceName string analyticsRate float64 - headerTags *internal.LockMap } func NewConfig(opts ...Option) *Config { diff --git a/contrib/julienschmidt/httprouter/internal/tracing/tracing.go b/contrib/julienschmidt/httprouter/internal/tracing/tracing.go index 47c1211c62..26d46b1e79 100644 --- a/contrib/julienschmidt/httprouter/internal/tracing/tracing.go +++ b/contrib/julienschmidt/httprouter/internal/tracing/tracing.go @@ -31,7 +31,14 @@ type Param interface { GetValue() string } -func BeforeHandle[T any, WT Router](cfg *Config, router T, wrapRouter func(T) WT, w http.ResponseWriter, req *http.Request) (http.ResponseWriter, *http.Request, func(), bool) { +// BeforeHandle is an adapter of httptrace.BeforeHandle for julienschmidt/httprouter types. +func BeforeHandle[T any, WT Router]( + cfg *Config, + router T, + wrapRouter func(T) WT, + w http.ResponseWriter, + req *http.Request, +) (http.ResponseWriter, *http.Request, func(), bool) { wRouter := wrapRouter(router) // get the resource associated to this request route := req.URL.Path diff --git a/internal/appsec/emitter/httpsec/http.go b/internal/appsec/emitter/httpsec/http.go index 7ed885b12d..9f81cdc20e 100644 --- a/internal/appsec/emitter/httpsec/http.go +++ b/internal/appsec/emitter/httpsec/http.go @@ -108,8 +108,17 @@ func makeCookies(parsed []*http.Cookie) map[string][]string { return cookies } -func BeforeHandle(w http.ResponseWriter, r *http.Request, span ddtrace.Span, pathParams map[string]string, opts *Config) ( - http.ResponseWriter, *http.Request, func(), bool) { +// BeforeHandle contains the appsec functionality that should be executed before a http.Handler runs. +// It returns the modified http.ResponseWriter and http.Request, an additional afterHandle function +// that should be executed after the Handler runs, and a handled bool that instructs if the request has been handled +// or not - in case it was handled, the original handler should not run. +func BeforeHandle( + w http.ResponseWriter, + r *http.Request, + span ddtrace.Span, + pathParams map[string]string, + opts *Config, +) (http.ResponseWriter, *http.Request, func(), bool) { if opts == nil { opts = defaultWrapHandlerConfig } else if opts.ResponseHeaderCopier == nil {