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

Add ignore_urls to central config #872

Merged
merged 9 commits into from Jan 27, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.asciidoc
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions config.go
Expand Up @@ -18,6 +18,7 @@
package apm // import "go.elastic.co/apm"

import (
"net/url"
"os"
"path/filepath"
"runtime"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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 == "" {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -510,4 +531,5 @@ type instrumentationConfigValues struct {
stackTraceLimit int
propagateLegacyHeader bool
sanitizedFieldNames wildcard.Matchers
ignoreTransactionURLs wildcard.Matchers
}
5 changes: 5 additions & 0 deletions config_test.go
Expand Up @@ -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) {
Expand Down
6 changes: 4 additions & 2 deletions module/apmecho/middleware.go
Expand Up @@ -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,
Expand Down
8 changes: 5 additions & 3 deletions module/apmgin/middleware.go
Expand Up @@ -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
}

Expand Down
6 changes: 4 additions & 2 deletions module/apmgorilla/middleware.go
Expand Up @@ -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,
Expand Down
10 changes: 6 additions & 4 deletions module/apmhttp/handler.go
Expand Up @@ -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)
}
Expand Down
11 changes: 11 additions & 0 deletions module/apmhttp/ignorer.go
Expand Up @@ -22,6 +22,7 @@ import (
"regexp"
"sync"

"go.elastic.co/apm"
"go.elastic.co/apm/internal/configutil"
"go.elastic.co/apm/internal/wildcard"
)
Expand All @@ -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)
Expand All @@ -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
Expand Down
54 changes: 36 additions & 18 deletions module/apmhttp/ignorer_test.go
Expand Up @@ -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" {
Expand All @@ -65,11 +65,29 @@ 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))
})
}

func TestDynamicRequestIgnorer(t *testing.T) {
r := &http.Request{URL: &url.URL{Path: "/foo"}}
tracer := newTracer()
defer tracer.Close()

dynamicIgnorer := apmhttp.NewDynamicServerRequestIgnorer(tracer)
assert.Equal(t, false, dynamicIgnorer(r))

tracer.SetIgnoreTransactionURLs("/fo*")
assert.Equal(t, true, dynamicIgnorer(r))
}
jalvz marked this conversation as resolved.
Show resolved Hide resolved

func TestFallbackDeprecatedRequestIgnorer(t *testing.T) {
if os.Getenv("_INSIDE_TEST") != "1" {
cmd := exec.Command(os.Args[0], "-test.run=^"+regexp.QuoteMeta(t.Name())+"$")
Expand Down
6 changes: 4 additions & 2 deletions module/apmhttprouter/handler.go
Expand Up @@ -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)
}
Expand Down
6 changes: 4 additions & 2 deletions module/apmrestful/filter.go
Expand Up @@ -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,
Expand Down
14 changes: 14 additions & 0 deletions tracer.go
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
//
Expand Down