diff --git a/contrib/internal/httptrace/config.go b/contrib/internal/httptrace/config.go index 691a529400..23ee0113d5 100644 --- a/contrib/internal/httptrace/config.go +++ b/contrib/internal/httptrace/config.go @@ -8,6 +8,8 @@ package httptrace import ( "os" "regexp" + "strconv" + "strings" "gopkg.in/DataDog/dd-trace-go.v1/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" @@ -22,6 +24,8 @@ const ( envQueryStringRegexp = "DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP" // envTraceClientIPEnabled is the name of the env var used to specify whether or not to collect client ip in span tags 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" ) // defaultQueryStringRegexp is the regexp used for query string obfuscation if `envQueryStringRegexp` is empty. @@ -31,6 +35,7 @@ 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 } func newConfig() config { @@ -38,6 +43,11 @@ func newConfig() config { queryString: !internal.BoolEnv(envQueryStringDisabled, false), queryStringRegexp: defaultQueryStringRegexp, traceClientIP: internal.BoolEnv(envTraceClientIPEnabled, false), + isStatusError: isServerError, + } + v := os.Getenv(envServerErrorStatuses) + if fn := GetErrorCodesFromInput(v); fn != nil { + c.isStatusError = fn } if s, ok := os.LookupEnv(envQueryStringRegexp); !ok { return c @@ -51,3 +61,62 @@ func newConfig() config { } return c } + +func isServerError(statusCode int) bool { + return statusCode >= 500 && statusCode < 600 +} + +// GetErrorCodesFromInput parses a comma-separated string s to determine which codes are to be considered errors +// Its purpose is to support the DD_TRACE_HTTP_SERVER_ERROR_STATUSES env var +// If error condition cannot be determined from s, `nil` is returned +// e.g, input of "100,200,300-400" returns a function that returns true on 100, 200, and all values between 300-400, inclusive +// any input that cannot be translated to integer values returns nil +func GetErrorCodesFromInput(s string) func(statusCode int) bool { + if s == "" { + return nil + } + var codes []int + var ranges [][]int + vals := strings.Split(s, ",") + for _, val := range vals { + // "-" indicates a range of values + if strings.Contains(val, "-") { + bounds := strings.Split(val, "-") + if len(bounds) != 2 { + log.Debug("Trouble parsing %v due to entry %v, using default error status determination logic", s, val) + return nil + } + before, err := strconv.Atoi(bounds[0]) + if err != nil { + log.Debug("Trouble parsing %v due to entry %v, using default error status determination logic", s, val) + return nil + } + after, err := strconv.Atoi(bounds[1]) + if err != nil { + log.Debug("Trouble parsing %v due to entry %v, using default error status determination logic", s, val) + return nil + } + ranges = append(ranges, []int{before, after}) + } else { + intVal, err := strconv.Atoi(val) + if err != nil { + log.Debug("Trouble parsing %v due to entry %v, using default error status determination logic", s, val) + return nil + } + codes = append(codes, intVal) + } + } + return func(statusCode int) bool { + for _, c := range codes { + if c == statusCode { + return true + } + } + for _, bounds := range ranges { + if statusCode >= bounds[0] && statusCode <= bounds[1] { + return true + } + } + return false + } +} diff --git a/contrib/internal/httptrace/httptrace.go b/contrib/internal/httptrace/httptrace.go index 7865a99182..6fa5a43242 100644 --- a/contrib/internal/httptrace/httptrace.go +++ b/contrib/internal/httptrace/httptrace.go @@ -65,15 +65,21 @@ func StartRequestSpan(r *http.Request, opts ...ddtrace.StartSpanOption) (tracer. // code. Any further span finish option can be added with opts. func FinishRequestSpan(s tracer.Span, status int, opts ...tracer.FinishOption) { var statusStr string + // if status is 0, treat it like 200 unless 0 was called out in DD_TRACE_HTTP_SERVER_ERROR_STATUSES if status == 0 { - statusStr = "200" + if cfg.isStatusError(status) { + statusStr = "0" + s.SetTag(ext.Error, fmt.Errorf("%s: %s", statusStr, http.StatusText(status))) + } else { + statusStr = "200" + } } else { statusStr = strconv.Itoa(status) + if cfg.isStatusError(status) { + s.SetTag(ext.Error, fmt.Errorf("%s: %s", statusStr, http.StatusText(status))) + } } s.SetTag(ext.HTTPCode, statusStr) - if status >= 500 && status < 600 { - s.SetTag(ext.Error, fmt.Errorf("%s: %s", statusStr, http.StatusText(status))) - } s.Finish(opts...) } diff --git a/contrib/internal/httptrace/httptrace_test.go b/contrib/internal/httptrace/httptrace_test.go index c037eebeb7..8733a0a343 100644 --- a/contrib/internal/httptrace/httptrace_test.go +++ b/contrib/internal/httptrace/httptrace_test.go @@ -6,9 +6,11 @@ package httptrace import ( + "fmt" "net/http" "net/http/httptest" "net/url" + "os" "strconv" "testing" @@ -25,6 +27,117 @@ import ( "github.com/stretchr/testify/require" ) +func TestGetErrorCodesFromInput(t *testing.T) { + codesOnly := "400,401,402" + rangesOnly := "400-405,408-410" + mixed := "400,403-405,407-410,412" + invalid1 := "1,100-200-300-" + invalid2 := "abc:@3$5^," + empty := "" + t.Run("codesOnly", func(t *testing.T) { + fn := GetErrorCodesFromInput(codesOnly) + for i := 400; i <= 402; i++ { + assert.True(t, fn(i)) + } + assert.False(t, fn(500)) + assert.False(t, fn(0)) + }) + t.Run("rangesOnly", func(t *testing.T) { + fn := GetErrorCodesFromInput(rangesOnly) + for i := 400; i <= 405; i++ { + assert.True(t, fn(i)) + } + for i := 408; i <= 410; i++ { + assert.True(t, fn(i)) + } + assert.False(t, fn(406)) + assert.False(t, fn(411)) + assert.False(t, fn(500)) + }) + t.Run("mixed", func(t *testing.T) { + fn := GetErrorCodesFromInput(mixed) + assert.True(t, fn(400)) + assert.False(t, fn(401)) + for i := 403; i <= 405; i++ { + assert.True(t, fn(i)) + } + assert.False(t, fn(406)) + for i := 407; i <= 410; i++ { + assert.True(t, fn(i)) + } + assert.False(t, fn(411)) + assert.False(t, fn(500)) + }) + // invalid entries below should result in nils + t.Run("invalid1", func(t *testing.T) { + fn := GetErrorCodesFromInput(invalid1) + assert.Nil(t, fn) + }) + t.Run("invalid2", func(t *testing.T) { + fn := GetErrorCodesFromInput(invalid2) + assert.Nil(t, fn) + }) + t.Run("empty", func(t *testing.T) { + fn := GetErrorCodesFromInput(empty) + assert.Nil(t, fn) + }) +} + +func TestConfiguredErrorStatuses(t *testing.T) { + defer os.Unsetenv("DD_TRACE_HTTP_SERVER_ERROR_STATUSES") + t.Run("configured", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + os.Setenv("DD_TRACE_HTTP_SERVER_ERROR_STATUSES", "199-399,400,501") + + // reset config based on new DD_TRACE_HTTP_SERVER_ERROR_STATUSES value + oldConfig := cfg + defer func() { cfg = oldConfig }() + cfg = newConfig() + + statuses := []int{0, 200, 400, 500} + r := httptest.NewRequest(http.MethodGet, "/test", nil) + for i, status := range statuses { + sp, _ := StartRequestSpan(r) + FinishRequestSpan(sp, status) + spans := mt.FinishedSpans() + require.Len(t, spans, i+1) + + switch status { + case 0: + assert.Equal(t, "200", spans[i].Tag(ext.HTTPCode)) + assert.Nil(t, spans[i].Tag(ext.Error)) + case 200, 400: + assert.Equal(t, strconv.Itoa(status), spans[i].Tag(ext.HTTPCode)) + assert.Equal(t, fmt.Errorf("%s: %s", strconv.Itoa(status), http.StatusText(status)), spans[i].Tag(ext.Error).(error)) + case 500: + assert.Equal(t, strconv.Itoa(status), spans[i].Tag(ext.HTTPCode)) + assert.Nil(t, spans[i].Tag(ext.Error)) + } + } + }) + t.Run("zero", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + os.Setenv("DD_TRACE_HTTP_SERVER_ERROR_STATUSES", "0") + + // reset config based on new DD_TRACE_HTTP_SERVER_ERROR_STATUSES value + oldConfig := cfg + defer func() { cfg = oldConfig }() + cfg = newConfig() + + r := httptest.NewRequest(http.MethodGet, "/test", nil) + sp, _ := StartRequestSpan(r) + FinishRequestSpan(sp, 0) + spans := mt.FinishedSpans() + require.Len(t, spans, 1) + assert.Equal(t, "0", spans[0].Tag(ext.HTTPCode)) + assert.Equal(t, fmt.Errorf("0: %s", http.StatusText(0)), spans[0].Tag(ext.Error).(error)) + }) +} + func TestHeaderTagsFromRequest(t *testing.T) { mt := mocktracer.Start() defer mt.Stop() diff --git a/contrib/net/http/http_test.go b/contrib/net/http/http_test.go index 835557620a..c734bbd297 100644 --- a/contrib/net/http/http_test.go +++ b/contrib/net/http/http_test.go @@ -520,10 +520,14 @@ func handler200(w http.ResponseWriter, _ *http.Request) { w.Write([]byte("OK\n")) } -func handler500(w http.ResponseWriter, _ *http.Request) { +func handler500(w http.ResponseWriter, r *http.Request) { http.Error(w, "500!", http.StatusInternalServerError) } +func handler400(w http.ResponseWriter, r *http.Request) { + http.Error(w, "400!", http.StatusBadRequest) +} + func BenchmarkHttpServeTrace(b *testing.B) { tracer.Start(tracer.WithLogger(log.DiscardLogger{})) defer tracer.Stop() diff --git a/contrib/net/http/option.go b/contrib/net/http/option.go index cb658b4551..22e63ee3b0 100644 --- a/contrib/net/http/option.go +++ b/contrib/net/http/option.go @@ -8,7 +8,9 @@ package http import ( "math" "net/http" + "os" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" "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" @@ -18,7 +20,13 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/normalizer" ) -const defaultServiceName = "http.router" +const ( + defaultServiceName = "http.router" + // envClientQueryStringEnabled is the name of the env var used to specify whether query string collection is enabled for http client spans. + envClientQueryStringEnabled = "DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING" + // envClientErrorStatuses is the name of the env var that specifies error status codes on http client spans + envClientErrorStatuses = "DD_TRACE_HTTP_CLIENT_ERROR_STATUSES" +) type config struct { serviceName string @@ -146,6 +154,8 @@ type roundTripperConfig struct { spanOpts []ddtrace.StartSpanOption propagation bool errCheck func(err error) bool + queryString bool // reports whether the query string is included in the URL tag for http client spans + isStatusError func(statusCode int) bool } func newRoundTripperConfig() *roundTripperConfig { @@ -156,14 +166,22 @@ func newRoundTripperConfig() *roundTripperConfig { defaultSpanNamer := func(_ *http.Request) string { return spanName } - return &roundTripperConfig{ + + c := &roundTripperConfig{ serviceName: namingschema.ServiceNameOverrideV0("", ""), analyticsRate: globalconfig.AnalyticsRate(), resourceNamer: defaultResourceNamer, propagation: true, spanNamer: defaultSpanNamer, ignoreRequest: func(_ *http.Request) bool { return false }, + queryString: internal.BoolEnv(envClientQueryStringEnabled, true), + isStatusError: isClientError, + } + v := os.Getenv(envClientErrorStatuses) + if fn := httptrace.GetErrorCodesFromInput(v); fn != nil { + c.isStatusError = fn } + return c } // A RoundTripperOption represents an option that can be passed to @@ -264,3 +282,7 @@ func RTWithErrorCheck(fn func(err error) bool) RoundTripperOption { cfg.errCheck = fn } } + +func isClientError(statusCode int) bool { + return statusCode >= 400 && statusCode < 500 +} diff --git a/contrib/net/http/roundtripper.go b/contrib/net/http/roundtripper.go index bd71daa801..7e47c530b6 100644 --- a/contrib/net/http/roundtripper.go +++ b/contrib/net/http/roundtripper.go @@ -7,11 +7,13 @@ package http import ( "fmt" - "gopkg.in/DataDog/dd-trace-go.v1/appsec/events" "math" "net/http" "os" "strconv" + "strings" + + "gopkg.in/DataDog/dd-trace-go.v1/appsec/events" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" @@ -38,7 +40,7 @@ func (rt *roundTripper) RoundTrip(req *http.Request) (res *http.Response, err er tracer.SpanType(ext.SpanTypeHTTP), tracer.ResourceName(resourceName), tracer.Tag(ext.HTTPMethod, req.Method), - tracer.Tag(ext.HTTPURL, url.String()), + tracer.Tag(ext.HTTPURL, urlFromRequest(req, rt.cfg.queryString)), tracer.Tag(ext.Component, componentName), tracer.Tag(ext.SpanKind, ext.SpanKindClient), tracer.Tag(ext.NetworkDestinationName, url.Hostname()), @@ -86,7 +88,6 @@ func (rt *roundTripper) RoundTrip(req *http.Request) (res *http.Response, err er } res, err = rt.base.RoundTrip(r2) - if err != nil { span.SetTag("http.errors", err.Error()) if rt.cfg.errCheck == nil || rt.cfg.errCheck(err) { @@ -94,8 +95,7 @@ func (rt *roundTripper) RoundTrip(req *http.Request) (res *http.Response, err er } } else { span.SetTag(ext.HTTPCode, strconv.Itoa(res.StatusCode)) - // treat 5XX as errors - if res.StatusCode/100 == 5 { + if rt.cfg.isStatusError(res.StatusCode) { span.SetTag("http.errors", res.Status) span.SetTag(ext.Error, fmt.Errorf("%d: %s", res.StatusCode, http.StatusText(res.StatusCode))) } @@ -135,3 +135,32 @@ func WrapClient(c *http.Client, opts ...RoundTripperOption) *http.Client { c.Transport = WrapRoundTripper(c.Transport, opts...) return c } + +// urlFromRequest returns the URL from the HTTP request. The URL query string is included in the return object iff queryString is true +// See https://docs.datadoghq.com/tracing/configure_data_security#redacting-the-query-in-the-url for more information. +func urlFromRequest(r *http.Request, queryString bool) string { + // Quoting net/http comments about net.Request.URL on server requests: + // "For most requests, fields other than Path and RawQuery will be + // empty. (See RFC 7230, Section 5.3)" + // This is why we don't rely on url.URL.String(), url.URL.Host, url.URL.Scheme, etc... + var url string + path := r.URL.EscapedPath() + scheme := r.URL.Scheme + if r.TLS != nil { + scheme = "https" + } + if r.Host != "" { + url = strings.Join([]string{scheme, "://", r.Host, path}, "") + } else { + url = path + } + // Collect the query string if we are allowed to report it and obfuscate it if possible/allowed + if queryString && r.URL.RawQuery != "" { + query := r.URL.RawQuery + url = strings.Join([]string{url, query}, "?") + } + if frag := r.URL.EscapedFragment(); frag != "" { + url = strings.Join([]string{url, frag}, "#") + } + return url +} diff --git a/contrib/net/http/roundtripper_test.go b/contrib/net/http/roundtripper_test.go index 3dca33ad4f..4002647fe6 100644 --- a/contrib/net/http/roundtripper_test.go +++ b/contrib/net/http/roundtripper_test.go @@ -11,6 +11,8 @@ import ( "net/http" "net/http/httptest" "net/url" + "os" + "regexp" "strconv" "strings" "testing" @@ -100,58 +102,67 @@ func TestRoundTripper(t *testing.T) { assert.Equal(t, wantPort, s1.Tag(ext.NetworkDestinationPort)) } -func TestRoundTripperServerError(t *testing.T) { - mt := mocktracer.Start() - defer mt.Stop() - - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(r.Header)) - assert.NoError(t, err) - - span := tracer.StartSpan("test", - tracer.ChildOf(spanctx)) - defer span.Finish() - - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Error")) - })) - defer s.Close() - - rt := WrapRoundTripper(http.DefaultTransport, - WithBefore(func(req *http.Request, span ddtrace.Span) { - span.SetTag("CalledBefore", true) - }), - WithAfter(func(res *http.Response, span ddtrace.Span) { - span.SetTag("CalledAfter", true) - })) - +func makeRequests(rt http.RoundTripper, url string, t *testing.T) { client := &http.Client{ Transport: rt, } + resp, err := client.Get(url + "/400") + assert.Nil(t, err) + defer resp.Body.Close() - resp, err := client.Get(s.URL + "/hello/world") + resp, err = client.Get(url + "/500") assert.Nil(t, err) defer resp.Body.Close() - spans := mt.FinishedSpans() - assert.Len(t, spans, 2) - assert.Equal(t, spans[0].TraceID(), spans[1].TraceID()) + resp, err = client.Get(url + "/200") + assert.Nil(t, err) + defer resp.Body.Close() +} - s0 := spans[0] - assert.Equal(t, "test", s0.OperationName()) - assert.Equal(t, "test", s0.Tag(ext.ResourceName)) +func TestRoundTripperErrors(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/200", handler200) + mux.HandleFunc("/400", handler400) + mux.HandleFunc("/500", handler500) + s := httptest.NewServer(mux) + defer s.Close() - s1 := spans[1] - assert.Equal(t, "http.request", s1.OperationName()) - assert.Equal(t, "http.request", s1.Tag(ext.ResourceName)) - assert.Equal(t, "500", s1.Tag(ext.HTTPCode)) - assert.Equal(t, "GET", s1.Tag(ext.HTTPMethod)) - assert.Equal(t, s.URL+"/hello/world", s1.Tag(ext.HTTPURL)) - assert.Equal(t, fmt.Errorf("500: Internal Server Error"), s1.Tag(ext.Error)) - assert.Equal(t, true, s1.Tag("CalledBefore")) - assert.Equal(t, true, s1.Tag("CalledAfter")) - assert.Equal(t, ext.SpanKindClient, s1.Tag(ext.SpanKind)) - assert.Equal(t, "net/http", s1.Tag(ext.Component)) + t.Run("default", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + rt := WrapRoundTripper(http.DefaultTransport) + makeRequests(rt, s.URL, t) + spans := mt.FinishedSpans() + assert.Len(t, spans, 3) + s := spans[0] // 400 is error + assert.Equal(t, "400: Bad Request", s.Tag(ext.Error).(error).Error()) + assert.Equal(t, "400", s.Tag(ext.HTTPCode)) + s = spans[1] // 500 is not error + assert.Empty(t, s.Tag(ext.Error)) + assert.Equal(t, "500", s.Tag(ext.HTTPCode)) + s = spans[2] // 200 is not error + assert.Empty(t, s.Tag(ext.Error)) + assert.Equal(t, "200", s.Tag(ext.HTTPCode)) + }) + t.Run("custom", func(t *testing.T) { + os.Setenv("DD_TRACE_HTTP_CLIENT_ERROR_STATUSES", "500-510") + defer os.Unsetenv("DD_TRACE_HTTP_CLIENT_ERROR_STATUSES") + mt := mocktracer.Start() + defer mt.Stop() + rt := WrapRoundTripper(http.DefaultTransport) + makeRequests(rt, s.URL, t) + spans := mt.FinishedSpans() + assert.Len(t, spans, 3) + s := spans[0] // 400 is not error + assert.Empty(t, s.Tag(ext.Error)) + assert.Equal(t, "400", s.Tag(ext.HTTPCode)) + s = spans[1] // 500 is error + assert.Equal(t, "500: Internal Server Error", s.Tag(ext.Error).(error).Error()) + assert.Equal(t, "500", s.Tag(ext.HTTPCode)) + s = spans[2] // 200 is not error + assert.Empty(t, s.Tag(ext.Error)) + assert.Equal(t, "200", s.Tag(ext.HTTPCode)) + }) } func TestRoundTripperNetworkError(t *testing.T) { @@ -555,6 +566,70 @@ func TestSpanOptions(t *testing.T) { assert.Equal(t, tagValue, spans[0].Tag(tagKey)) } +func TestClientQueryString(t *testing.T) { + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello World")) + })) + defer s.Close() + t.Run("default", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + rt := WrapRoundTripper(http.DefaultTransport) + client := &http.Client{ + Transport: rt, + } + resp, err := client.Get(s.URL + "/hello/world?querystring=xyz") + assert.Nil(t, err) + defer resp.Body.Close() + spans := mt.FinishedSpans() + assert.Len(t, spans, 1) + + assert.Regexp(t, regexp.MustCompile(`^http://.*?/hello/world\?querystring=xyz$`), spans[0].Tag(ext.HTTPURL)) + }) + t.Run("false", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + os.Setenv("DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING", "false") + defer os.Unsetenv("DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING") + + rt := WrapRoundTripper(http.DefaultTransport) + client := &http.Client{ + Transport: rt, + } + resp, err := client.Get(s.URL + "/hello/world?querystring=xyz") + assert.Nil(t, err) + defer resp.Body.Close() + spans := mt.FinishedSpans() + assert.Len(t, spans, 1) + + assert.Regexp(t, regexp.MustCompile(`^http://.*?/hello/world$`), spans[0].Tag(ext.HTTPURL)) + }) + // DD_TRACE_HTTP_URL_QUERY_STRING_DISABLED applies only to server spans, not client + t.Run("Not impacted by DD_TRACE_HTTP_URL_QUERY_STRING_DISABLED", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + os.Setenv("DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING", "true") + os.Setenv("DD_TRACE_HTTP_URL_QUERY_STRING_DISABLED", "true") + defer os.Unsetenv("DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING") + defer os.Unsetenv("DD_TRACE_HTTP_URL_QUERY_STRING_DISABLED") + + rt := WrapRoundTripper(http.DefaultTransport) + client := &http.Client{ + Transport: rt, + } + resp, err := client.Get(s.URL + "/hello/world?querystring=xyz") + assert.Nil(t, err) + defer resp.Body.Close() + spans := mt.FinishedSpans() + assert.Len(t, spans, 1) + + assert.Contains(t, spans[0].Tag(ext.HTTPURL), "/hello/world?querystring=xyz") + }) +} + func TestRoundTripperPropagation(t *testing.T) { mt := mocktracer.Start() defer mt.Stop() diff --git a/ddtrace/tracer/rules_sampler.go b/ddtrace/tracer/rules_sampler.go index 2cd911e3f7..037e393642 100644 --- a/ddtrace/tracer/rules_sampler.go +++ b/ddtrace/tracer/rules_sampler.go @@ -19,6 +19,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" "golang.org/x/time/rate" ) @@ -533,6 +534,7 @@ const defaultRateLimit = 100.0 // The limit is DD_TRACE_RATE_LIMIT if set, `defaultRateLimit` otherwise. func newRateLimiter() *rateLimiter { limit := defaultRateLimit + origin := telemetry.OriginDefault v := os.Getenv("DD_TRACE_RATE_LIMIT") if v != "" { l, err := strconv.ParseFloat(v, 64) @@ -542,9 +544,11 @@ func newRateLimiter() *rateLimiter { log.Warn("DD_TRACE_RATE_LIMIT negative, using default value %f", limit) } else { // override the default limit + origin = telemetry.OriginEnvVar limit = l } } + reportTelemetryOnAppStarted(telemetry.Configuration{Name: "trace_rate_limit", Value: limit, Origin: origin}) return &rateLimiter{ limiter: rate.NewLimiter(rate.Limit(limit), int(math.Ceil(limit))), prevTime: time.Now(), diff --git a/ddtrace/tracer/telemetry.go b/ddtrace/tracer/telemetry.go index 3f9a7d2f33..3fa70b4e92 100644 --- a/ddtrace/tracer/telemetry.go +++ b/ddtrace/tracer/telemetry.go @@ -12,6 +12,12 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" ) +var additionalConfigs []telemetry.Configuration + +func reportTelemetryOnAppStarted(c telemetry.Configuration) { + additionalConfigs = append(additionalConfigs, c) +} + // startTelemetry starts the global instrumentation telemetry client with tracer data // unless instrumentation telemetry is disabled via the DD_INSTRUMENTATION_TELEMETRY_ENABLED // env var. @@ -44,7 +50,8 @@ func startTelemetry(c *config) { {Name: "service", Value: c.serviceName}, {Name: "universal_version", Value: c.universalVersion}, {Name: "env", Value: c.env}, - {Name: "agent_url", Value: c.agentURL.String()}, + {Name: "version", Value: c.version}, + {Name: "trace_agent_url", Value: c.agentURL.String()}, {Name: "agent_hostname", Value: c.hostname}, {Name: "runtime_metrics_enabled", Value: c.runtimeMetrics}, {Name: "dogstatsd_addr", Value: c.dogstatsdAddr}, @@ -102,5 +109,6 @@ func startTelemetry(c *config) { telemetryConfigs = append(telemetryConfigs, telemetry.Configuration{Name: "orchestrion_" + k, Value: v}) } } + telemetryConfigs = append(telemetryConfigs, additionalConfigs...) telemetry.GlobalClient.ProductChange(telemetry.NamespaceTracers, true, telemetryConfigs) } diff --git a/internal/appsec/listener/httpsec/request.go b/internal/appsec/listener/httpsec/request.go index 95607f0ccb..abd3983183 100644 --- a/internal/appsec/listener/httpsec/request.go +++ b/internal/appsec/listener/httpsec/request.go @@ -34,7 +34,7 @@ var ( "x-cluster-client-ip", "fastly-client-ip", "cf-connecting-ip", - "cf-connecting-ip6", + "cf-connecting-ipv6", } // defaultCollectedHeaders is the default list of HTTP headers collected as