diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 118e5f4d2..0b5992107 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -23,6 +23,8 @@ endif::[] https://github.com/elastic/apm-agent-go/compare/v1.10.0...master[View commits] +- Make TRANSACTION_IGNORE_URLS dynamically configurable: {pull}872[#872] + [[release-notes-1.x]] === Go Agent version 1.x diff --git a/config.go b/config.go index 5a99487e1..1881d370c 100644 --- a/config.go +++ b/config.go @@ -18,6 +18,7 @@ package apm // import "go.elastic.co/apm" import ( + "net/url" "os" "path/filepath" "runtime" @@ -53,6 +54,8 @@ const ( envAPIBufferSize = "ELASTIC_APM_API_BUFFER_SIZE" envMetricsBufferSize = "ELASTIC_APM_METRICS_BUFFER_SIZE" envDisableMetrics = "ELASTIC_APM_DISABLE_METRICS" + envIgnoreURLs = "ELASTIC_APM_TRANSACTION_IGNORE_URLS" + deprecatedEnvIgnoreURLs = "ELASTIC_APM_IGNORE_URLS" envGlobalLabels = "ELASTIC_APM_GLOBAL_LABELS" envStackTraceLimit = "ELASTIC_APM_STACK_TRACE_LIMIT" envCentralConfig = "ELASTIC_APM_CENTRAL_CONFIG" @@ -263,6 +266,14 @@ func initialDisabledMetrics() wildcard.Matchers { return configutil.ParseWildcardPatternsEnv(envDisableMetrics, nil) } +func initialIgnoreTransactionURLs() wildcard.Matchers { + matchers := configutil.ParseWildcardPatternsEnv(envIgnoreURLs, nil) + if len(matchers) == 0 { + matchers = configutil.ParseWildcardPatternsEnv(deprecatedEnvIgnoreURLs, nil) + } + return matchers +} + func initialStackTraceLimit() (int, error) { value := os.Getenv(envStackTraceLimit) if value == "" { @@ -348,6 +359,11 @@ func (t *Tracer) updateRemoteConfig(logger WarningLogger, old, attrs map[string] cfg.maxSpans = value }) } + case envIgnoreURLs: + matchers := configutil.ParseWildcardPatterns(v) + updates = append(updates, func(cfg *instrumentationConfig) { + cfg.ignoreTransactionURLs = matchers + }) case envRecording: recording, err := strconv.ParseBool(v) if err != nil { @@ -480,6 +496,11 @@ func (t *Tracer) updateInstrumentationConfig(f func(cfg *instrumentationConfig)) } } +// IgnoredTransactionURL returns whether the given transaction URL should be ignored +func (t *Tracer) IgnoredTransactionURL(url *url.URL) bool { + return t.instrumentationConfig().ignoreTransactionURLs.MatchAny(url.String()) +} + // instrumentationConfig holds current configuration values, as well as information // required to revert from remote to local configuration. type instrumentationConfig struct { @@ -510,4 +531,5 @@ type instrumentationConfigValues struct { stackTraceLimit int propagateLegacyHeader bool sanitizedFieldNames wildcard.Matchers + ignoreTransactionURLs wildcard.Matchers } diff --git a/config_test.go b/config_test.go index 1cf0bd8e5..490a453b1 100644 --- a/config_test.go +++ b/config_test.go @@ -122,6 +122,11 @@ func TestTracerCentralConfigUpdate(t *testing.T) { return len(log) > 0 }) }) + run("transaction_ignore_urls", "*", func(tracer *apmtest.RecordingTracer) bool { + u, err := url.Parse("http://testing.invalid/") + require.NoError(t, err) + return tracer.IgnoredTransactionURL(u) + }) } func testTracerCentralConfigUpdate(t *testing.T, logger apm.Logger, serverResponse string, isRemote func(*apmtest.RecordingTracer) bool) { diff --git a/module/apmecho/middleware.go b/module/apmecho/middleware.go index 5c7c06181..369a17524 100644 --- a/module/apmecho/middleware.go +++ b/module/apmecho/middleware.go @@ -40,12 +40,14 @@ import ( // Use WithTracer to specify an alternative tracer. func Middleware(o ...Option) echo.MiddlewareFunc { opts := options{ - tracer: apm.DefaultTracer, - requestIgnorer: apmhttp.DefaultServerRequestIgnorer(), + tracer: apm.DefaultTracer, } for _, o := range o { o(&opts) } + if opts.requestIgnorer == nil { + opts.requestIgnorer = apmhttp.NewDynamicServerRequestIgnorer(opts.tracer) + } return func(h echo.HandlerFunc) echo.HandlerFunc { m := &middleware{ tracer: opts.tracer, diff --git a/module/apmgin/middleware.go b/module/apmgin/middleware.go index d84d6dfab..c8f36fa84 100644 --- a/module/apmgin/middleware.go +++ b/module/apmgin/middleware.go @@ -45,13 +45,15 @@ func init() { // Use WithTracer to specify an alternative tracer. func Middleware(engine *gin.Engine, o ...Option) gin.HandlerFunc { m := &middleware{ - engine: engine, - tracer: apm.DefaultTracer, - requestIgnorer: apmhttp.DefaultServerRequestIgnorer(), + engine: engine, + tracer: apm.DefaultTracer, } for _, o := range o { o(m) } + if m.requestIgnorer == nil { + m.requestIgnorer = apmhttp.NewDynamicServerRequestIgnorer(m.tracer) + } return m.handle } diff --git a/module/apmgorilla/middleware.go b/module/apmgorilla/middleware.go index 3ed76942a..e75ce9ad1 100644 --- a/module/apmgorilla/middleware.go +++ b/module/apmgorilla/middleware.go @@ -76,12 +76,14 @@ func WrapMethodNotAllowedHandler(h http.Handler, m mux.MiddlewareFunc) http.Hand // Use WithTracer to specify an alternative tracer. func Middleware(o ...Option) mux.MiddlewareFunc { opts := options{ - tracer: apm.DefaultTracer, - requestIgnorer: apmhttp.DefaultServerRequestIgnorer(), + tracer: apm.DefaultTracer, } for _, o := range o { o(&opts) } + if opts.requestIgnorer == nil { + opts.requestIgnorer = apmhttp.NewDynamicServerRequestIgnorer(opts.tracer) + } return func(h http.Handler) http.Handler { return apmhttp.Wrap( h, diff --git a/module/apmhttp/handler.go b/module/apmhttp/handler.go index aa4a484b1..56936db9c 100644 --- a/module/apmhttp/handler.go +++ b/module/apmhttp/handler.go @@ -39,14 +39,16 @@ func Wrap(h http.Handler, o ...ServerOption) http.Handler { panic("h == nil") } handler := &handler{ - handler: h, - tracer: apm.DefaultTracer, - requestName: ServerRequestName, - requestIgnorer: DefaultServerRequestIgnorer(), + handler: h, + tracer: apm.DefaultTracer, + requestName: ServerRequestName, } for _, o := range o { o(handler) } + if handler.requestIgnorer == nil { + handler.requestIgnorer = NewDynamicServerRequestIgnorer(handler.tracer) + } if handler.recovery == nil { handler.recovery = NewTraceRecovery(handler.tracer) } diff --git a/module/apmhttp/ignorer.go b/module/apmhttp/ignorer.go index 28b1145ad..d759c3279 100644 --- a/module/apmhttp/ignorer.go +++ b/module/apmhttp/ignorer.go @@ -22,6 +22,7 @@ import ( "regexp" "sync" + "go.elastic.co/apm" "go.elastic.co/apm/internal/configutil" "go.elastic.co/apm/internal/wildcard" ) @@ -40,6 +41,8 @@ var ( // handlers. If ELASTIC_APM_TRANSACTION_IGNORE_URLS is set, it will be treated as a // comma-separated list of wildcard patterns; requests that match any of the // patterns will be ignored. +// +// DEPRECATED. Use NewDynamicServerRequestIgnorer instead func DefaultServerRequestIgnorer() RequestIgnorerFunc { defaultServerRequestIgnorerOnce.Do(func() { matchers := configutil.ParseWildcardPatternsEnv(envIgnoreURLs, nil) @@ -53,6 +56,14 @@ func DefaultServerRequestIgnorer() RequestIgnorerFunc { return defaultServerRequestIgnorer } +// NewDynamicServerRequestIgnorer returns the RequestIgnorer to use in +// handlers. The list of wildcard patterns comes from central config +func NewDynamicServerRequestIgnorer(t *apm.Tracer) RequestIgnorerFunc { + return func(r *http.Request) bool { + return t.IgnoredTransactionURL(r.URL) + } +} + // NewRegexpRequestIgnorer returns a RequestIgnorerFunc which matches requests' // URLs against re. Note that for server requests, typically only Path and // possibly RawQuery will be set, so the regular expression should take this diff --git a/module/apmhttp/ignorer_test.go b/module/apmhttp/ignorer_test.go index 26973a95d..acee27e3b 100644 --- a/module/apmhttp/ignorer_test.go +++ b/module/apmhttp/ignorer_test.go @@ -31,31 +31,31 @@ import ( "go.elastic.co/apm/module/apmhttp" ) -func TestDefaultServerRequestIgnorer(t *testing.T) { +func TestServerRequestIgnorer(t *testing.T) { r1 := &http.Request{URL: &url.URL{Path: "/foo"}} r2 := &http.Request{URL: &url.URL{Path: "/foo", RawQuery: "bar=baz"}} r3 := &http.Request{URL: &url.URL{Scheme: "http", Host: "testing.invalid", Path: "/foo", RawQuery: "bar=baz"}} - testDefaultServerRequestIgnorer(t, "", r1, false) - testDefaultServerRequestIgnorer(t, "", r2, false) - testDefaultServerRequestIgnorer(t, "", r3, false) - testDefaultServerRequestIgnorer(t, ",", r1, false) // equivalent to empty + testServerRequestIgnorer(t, "", r1, false) + testServerRequestIgnorer(t, "", r2, false) + testServerRequestIgnorer(t, "", r3, false) + testServerRequestIgnorer(t, ",", r1, false) // equivalent to empty - testDefaultServerRequestIgnorer(t, "*/foo*", r1, true) - testDefaultServerRequestIgnorer(t, "*/foo*", r2, true) - testDefaultServerRequestIgnorer(t, "*/foo*", r3, true) - testDefaultServerRequestIgnorer(t, "*/FOO*", r3, true) // case insensitive by default + testServerRequestIgnorer(t, "*/foo*", r1, true) + testServerRequestIgnorer(t, "*/foo*", r2, true) + testServerRequestIgnorer(t, "*/foo*", r3, true) + testServerRequestIgnorer(t, "*/FOO*", r3, true) // case insensitive by default - testDefaultServerRequestIgnorer(t, "*/foo?bar=baz", r1, false) - testDefaultServerRequestIgnorer(t, "*/foo?bar=baz", r2, true) - testDefaultServerRequestIgnorer(t, "*/foo?bar=baz", r3, true) + testServerRequestIgnorer(t, "*/foo?bar=baz", r1, false) + testServerRequestIgnorer(t, "*/foo?bar=baz", r2, true) + testServerRequestIgnorer(t, "*/foo?bar=baz", r3, true) - testDefaultServerRequestIgnorer(t, "http://*", r1, false) - testDefaultServerRequestIgnorer(t, "http://*", r2, false) - testDefaultServerRequestIgnorer(t, "http://*", r3, true) + testServerRequestIgnorer(t, "http://*", r1, false) + testServerRequestIgnorer(t, "http://*", r2, false) + testServerRequestIgnorer(t, "http://*", r3, true) } -func testDefaultServerRequestIgnorer(t *testing.T, ignoreURLs string, r *http.Request, expect bool) { +func testServerRequestIgnorer(t *testing.T, ignoreURLs string, r *http.Request, expect bool) { testName := fmt.Sprintf("%s_%s", ignoreURLs, r.URL.String()) t.Run(testName, func(t *testing.T) { if os.Getenv("_INSIDE_TEST") != "1" { @@ -65,8 +65,14 @@ func testDefaultServerRequestIgnorer(t *testing.T, ignoreURLs string, r *http.Re assert.NoError(t, cmd.Run()) return } - ignorer := apmhttp.DefaultServerRequestIgnorer() - assert.Equal(t, expect, ignorer(r)) + defaultIgnorer := apmhttp.DefaultServerRequestIgnorer() + assert.Equal(t, expect, defaultIgnorer(r)) + + tracer := newTracer() + defer tracer.Close() + + dynamicIgnorer := apmhttp.NewDynamicServerRequestIgnorer(tracer) + assert.Equal(t, expect, dynamicIgnorer(r)) }) } diff --git a/module/apmhttprouter/handler.go b/module/apmhttprouter/handler.go index aa48027bf..feb2e0ffd 100644 --- a/module/apmhttprouter/handler.go +++ b/module/apmhttprouter/handler.go @@ -96,12 +96,14 @@ func wrapHandlerUnknownRoute(h http.Handler, o ...Option) http.Handler { func gatherOptions(o ...Option) options { opts := options{ - tracer: apm.DefaultTracer, - requestIgnorer: apmhttp.DefaultServerRequestIgnorer(), + tracer: apm.DefaultTracer, } for _, o := range o { o(&opts) } + if opts.requestIgnorer == nil { + opts.requestIgnorer = apmhttp.NewDynamicServerRequestIgnorer(opts.tracer) + } if opts.recovery == nil { opts.recovery = apmhttp.NewTraceRecovery(opts.tracer) } diff --git a/module/apmrestful/filter.go b/module/apmrestful/filter.go index fd6b3410d..152bba493 100644 --- a/module/apmrestful/filter.go +++ b/module/apmrestful/filter.go @@ -33,12 +33,14 @@ import ( // Use WithTracer to specify an alternative tracer. func Filter(o ...Option) restful.FilterFunction { opts := options{ - tracer: apm.DefaultTracer, - requestIgnorer: apmhttp.DefaultServerRequestIgnorer(), + tracer: apm.DefaultTracer, } for _, o := range o { o(&opts) } + if opts.requestIgnorer == nil { + opts.requestIgnorer = apmhttp.NewDynamicServerRequestIgnorer(opts.tracer) + } return (&filter{ tracer: opts.tracer, requestIgnorer: opts.requestIgnorer, diff --git a/tracer.go b/tracer.go index 30f33a094..63d3b44c9 100644 --- a/tracer.go +++ b/tracer.go @@ -103,6 +103,7 @@ type TracerOptions struct { sampler Sampler sanitizedFieldNames wildcard.Matchers disabledMetrics wildcard.Matchers + ignoreTransactionURLs wildcard.Matchers captureHeaders bool captureBody CaptureBodyMode spanFramesMinDuration time.Duration @@ -246,6 +247,7 @@ func (opts *TracerOptions) initDefaults(continueOnError bool) error { opts.sampler = sampler opts.sanitizedFieldNames = initialSanitizedFieldNames() opts.disabledMetrics = initialDisabledMetrics() + opts.ignoreTransactionURLs = initialIgnoreTransactionURLs() opts.breakdownMetrics = breakdownMetricsEnabled opts.captureHeaders = captureHeaders opts.captureBody = captureBody @@ -426,6 +428,9 @@ func newTracer(opts TracerOptions) *Tracer { t.setLocalInstrumentationConfig(envSanitizeFieldNames, func(cfg *instrumentationConfigValues) { cfg.sanitizedFieldNames = opts.sanitizedFieldNames }) + t.setLocalInstrumentationConfig(envIgnoreURLs, func(cfg *instrumentationConfigValues) { + cfg.ignoreTransactionURLs = opts.ignoreTransactionURLs + }) if apmlog.DefaultLogger != nil { defaultLogLevel := apmlog.DefaultLogger.Level() t.setLocalInstrumentationConfig(apmlog.EnvLogLevel, func(cfg *instrumentationConfigValues) { @@ -599,6 +604,15 @@ func (t *Tracer) SetSanitizedFieldNames(patterns ...string) error { return nil } +// SetIgnoreTransactionURLs sets the wildcard patterns that will be used to +// ignore transactions with matching URLs. +func (t *Tracer) SetIgnoreTransactionURLs(pattern string) error { + t.setLocalInstrumentationConfig(envIgnoreURLs, func(cfg *instrumentationConfigValues) { + cfg.ignoreTransactionURLs = configutil.ParseWildcardPatterns(pattern) + }) + return nil +} + // RegisterMetricsGatherer registers g for periodic (or forced) metrics // gathering by t. //