Skip to content

Commit

Permalink
Merge pull request #158 from axw/config-ignore-urls
Browse files Browse the repository at this point in the history
module/apmhttp: support ELASTIC_APM_IGNORE_URLS
  • Loading branch information
axw authored Jul 31, 2018
2 parents d464f3d + a5718c6 commit 2717dc2
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Add Transaction.Context methods for setting user IDs (#144)
- module/apmgocql: new instrumentation module, providing an observer for gocql (#148)
- Add ELASTIC\_APM\_SERVER\_TIMEOUT config (#157)
- Add ELASTIC\_APM\_IGNORE\_URLS config (#158)

## [v0.4.0](https://github.com/elastic/apm-agent-go/releases/tag/v0.4.0)

Expand Down
18 changes: 18 additions & 0 deletions docs/configuration.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,24 @@ The name of the environment this service is deployed in, e.g. "production" or "s
Enable or disable the tracer. If set to false, then the Go agent does not send
any data to the Elastic APM server, and instrumentation overhead is minimized.

[float]
[[config-ignore-urls]]
=== `ELASTIC_APM_IGNORE_URLS`

[options="header"]
|============
| Environment | Default | Example
| `ELASTIC_APM_IGNORE_URLS` | | `/heartbeat\|/_internal/.*`
|============

A regular expression matching the request line of HTTP requests for which
transactions should not be reported.

The pattern specified in `ELASTIC_APM_IGNORE_URLS` is treated
case-insensitively by default. To override this behavior and match case-sensitively,
wrap the value like `(?-i:<value>)`. For a full definition of Go's regular
expression syntax, see https://golang.org/pkg/regexp/syntax/.

[float]
[[config-sanitize-field-names]]
=== `ELASTIC_APM_SANITIZE_FIELD_NAMES`
Expand Down
35 changes: 28 additions & 7 deletions module/apmecho/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,34 @@ import (
// By default, the middleware will use elasticapm.DefaultTracer.
// Use WithTracer to specify an alternative tracer.
func Middleware(o ...Option) echo.MiddlewareFunc {
opts := options{tracer: elasticapm.DefaultTracer}
opts := options{
tracer: elasticapm.DefaultTracer,
requestIgnorer: apmhttp.DefaultServerRequestIgnorer(),
}
for _, o := range o {
o(&opts)
}
return func(h echo.HandlerFunc) echo.HandlerFunc {
m := &middleware{tracer: opts.tracer, handler: h}
m := &middleware{
tracer: opts.tracer,
handler: h,
requestIgnorer: opts.requestIgnorer,
}
return m.handle
}
}

type middleware struct {
handler echo.HandlerFunc
tracer *elasticapm.Tracer
handler echo.HandlerFunc
tracer *elasticapm.Tracer
requestIgnorer apmhttp.RequestIgnorerFunc
}

func (m *middleware) handle(c echo.Context) error {
if !m.tracer.Active() {
req := c.Request()
if !m.tracer.Active() || m.requestIgnorer(req) {
return m.handler(c)
}
req := c.Request()
name := req.Method + " " + c.Path()
tx := m.tracer.StartTransaction(name, "request")
ctx := elasticapm.ContextWithTransaction(req.Context(), tx)
Expand Down Expand Up @@ -84,7 +92,8 @@ func (m *middleware) handle(c echo.Context) error {
}

type options struct {
tracer *elasticapm.Tracer
tracer *elasticapm.Tracer
requestIgnorer apmhttp.RequestIgnorerFunc
}

// Option sets options for tracing.
Expand All @@ -100,3 +109,15 @@ func WithTracer(t *elasticapm.Tracer) Option {
o.tracer = t
}
}

// WithRequestIgnorer returns a Option which sets r as the
// function to use to determine whether or not a request should
// be ignored. If r is nil, all requests will be reported.
func WithRequestIgnorer(r apmhttp.RequestIgnorerFunc) Option {
if r == nil {
r = apmhttp.IgnoreNone
}
return func(o *options) {
o.requestIgnorer = r
}
}
25 changes: 21 additions & 4 deletions module/apmgin/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,21 @@ func init() {
// By default, the middleware will use elasticapm.DefaultTracer.
// Use WithTracer to specify an alternative tracer.
func Middleware(engine *gin.Engine, o ...Option) gin.HandlerFunc {
m := &middleware{engine: engine, tracer: elasticapm.DefaultTracer}
m := &middleware{
engine: engine,
tracer: elasticapm.DefaultTracer,
requestIgnorer: apmhttp.DefaultServerRequestIgnorer(),
}
for _, o := range o {
o(m)
}
return m.handle
}

type middleware struct {
engine *gin.Engine
tracer *elasticapm.Tracer
engine *gin.Engine
tracer *elasticapm.Tracer
requestIgnorer apmhttp.RequestIgnorerFunc

setRouteMapOnce sync.Once
routeMap map[string]map[string]routeInfo
Expand All @@ -47,7 +52,7 @@ type routeInfo struct {
}

func (m *middleware) handle(c *gin.Context) {
if !m.tracer.Active() {
if !m.tracer.Active() || m.requestIgnorer(c.Request) {
c.Next()
return
}
Expand Down Expand Up @@ -129,3 +134,15 @@ func WithTracer(t *elasticapm.Tracer) Option {
m.tracer = t
}
}

// WithRequestIgnorer returns a Option which sets r as the
// function to use to determine whether or not a request should
// be ignored. If r is nil, all requests will be reported.
func WithRequestIgnorer(r apmhttp.RequestIgnorerFunc) Option {
if r == nil {
r = apmhttp.IgnoreNone
}
return func(m *middleware) {
m.requestIgnorer = r
}
}
21 changes: 19 additions & 2 deletions module/apmgorilla/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import (
// By default, the middleware will use elasticapm.DefaultTracer.
// Use WithTracer to specify an alternative tracer.
func Middleware(o ...Option) mux.MiddlewareFunc {
opts := options{tracer: elasticapm.DefaultTracer}
opts := options{
tracer: elasticapm.DefaultTracer,
requestIgnorer: apmhttp.DefaultServerRequestIgnorer(),
}
for _, o := range o {
o(&opts)
}
Expand All @@ -28,6 +31,7 @@ func Middleware(o ...Option) mux.MiddlewareFunc {
h,
apmhttp.WithTracer(opts.tracer),
apmhttp.WithServerRequestName(routeRequestName),
apmhttp.WithServerRequestIgnorer(opts.requestIgnorer),
)
}
}
Expand All @@ -44,7 +48,8 @@ func routeRequestName(req *http.Request) string {
}

type options struct {
tracer *elasticapm.Tracer
tracer *elasticapm.Tracer
requestIgnorer apmhttp.RequestIgnorerFunc
}

// Option sets options for tracing.
Expand All @@ -60,3 +65,15 @@ func WithTracer(t *elasticapm.Tracer) Option {
o.tracer = t
}
}

// WithRequestIgnorer returns a Option which sets r as the
// function to use to determine whether or not a request should
// be ignored. If r is nil, all requests will be reported.
func WithRequestIgnorer(r apmhttp.RequestIgnorerFunc) Option {
if r == nil {
r = apmhttp.IgnoreNone
}
return func(o *options) {
o.requestIgnorer = r
}
}
2 changes: 1 addition & 1 deletion module/apmhttp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func WrapRoundTripper(r http.RoundTripper, o ...ClientOption) http.RoundTripper
rt := &roundTripper{
r: r,
requestName: ClientRequestName,
requestIgnorer: ignoreNone,
requestIgnorer: IgnoreNone,
}
for _, o := range o {
o(rt)
Expand Down
8 changes: 2 additions & 6 deletions module/apmhttp/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func Wrap(h http.Handler, o ...ServerOption) http.Handler {
handler: h,
tracer: elasticapm.DefaultTracer,
requestName: ServerRequestName,
requestIgnorer: ignoreNone,
requestIgnorer: DefaultServerRequestIgnorer(),
}
for _, o := range o {
o(handler)
Expand Down Expand Up @@ -212,10 +212,6 @@ type responseWriterHijackerPusher struct {
http.Pusher
}

func ignoreNone(*http.Request) bool {
return false
}

// ServerOption sets options for tracing server requests.
type ServerOption func(*handler)

Expand Down Expand Up @@ -265,7 +261,7 @@ type RequestIgnorerFunc func(*http.Request) bool
// be ignored. If r is nil, all requests will be reported.
func WithServerRequestIgnorer(r RequestIgnorerFunc) ServerOption {
if r == nil {
r = ignoreNone
r = IgnoreNone
}
return func(h *handler) {
h.requestIgnorer = r
Expand Down
54 changes: 54 additions & 0 deletions module/apmhttp/ignorer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package apmhttp

import (
"fmt"
"net/http"
"os"
"regexp"
"sync"
)

const (
envIgnoreURLs = "ELASTIC_APM_IGNORE_URLS"
)

var (
defaultServerRequestIgnorerOnce sync.Once
defaultServerRequestIgnorer RequestIgnorerFunc = IgnoreNone
)

// DefaultServerRequestIgnorer returns the default RequestIgnorer to use in
// handlers. If ELASTIC_APM_IGNORE_URLS is set to a valid regular expression,
// then it will be used to ignore matching requests; otherwise none are ignored.
func DefaultServerRequestIgnorer() RequestIgnorerFunc {
defaultServerRequestIgnorerOnce.Do(func() {
value := os.Getenv(envIgnoreURLs)
if value == "" {
return
}
re, err := regexp.Compile(fmt.Sprintf("(?i:%s)", value))
if err == nil {
defaultServerRequestIgnorer = NewRegexpRequestIgnorer(re)
}
})
return defaultServerRequestIgnorer
}

// 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
// into account.
func NewRegexpRequestIgnorer(re *regexp.Regexp) RequestIgnorerFunc {
if re == nil {
panic("re == nil")
}
return func(r *http.Request) bool {
fmt.Println(re.String(), r.URL.String(), re.MatchString(r.URL.String()))
return re.MatchString(r.URL.String())
}
}

// IgnoreNone is a RequestIgnorerFunc which ignores no requests.
func IgnoreNone(*http.Request) bool {
return false
}
54 changes: 54 additions & 0 deletions module/apmhttp/ignorer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package apmhttp_test

import (
"fmt"
"net/http"
"net/url"
"os"
"os/exec"
"regexp"
"testing"

"github.com/stretchr/testify/assert"

"github.com/elastic/apm-agent-go/module/apmhttp"
)

func TestDefaultServerRequestIgnorer(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) // invalid regexp matches nothing

testDefaultServerRequestIgnorer(t, "/foo", r1, true)
testDefaultServerRequestIgnorer(t, "/foo", r2, true)
testDefaultServerRequestIgnorer(t, "/foo", r3, true)
testDefaultServerRequestIgnorer(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)

testDefaultServerRequestIgnorer(t, "http://.*", r1, false)
testDefaultServerRequestIgnorer(t, "http://.*", r2, false)
testDefaultServerRequestIgnorer(t, "http://.*", r3, true)
}

func testDefaultServerRequestIgnorer(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" {
cmd := exec.Command(os.Args[0], "-test.run=^"+regexp.QuoteMeta(t.Name())+"$")
cmd.Env = append(os.Environ(), "_INSIDE_TEST=1")
cmd.Env = append(cmd.Env, "ELASTIC_APM_IGNORE_URLS="+ignoreURLs)
assert.NoError(t, cmd.Run())
return
}
ignorer := apmhttp.DefaultServerRequestIgnorer()
assert.Equal(t, expect, ignorer(r))
})
}
22 changes: 18 additions & 4 deletions module/apmhttprouter/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import (
// WithRecovery.
func Wrap(h httprouter.Handle, route string, o ...Option) httprouter.Handle {
opts := options{
tracer: elasticapm.DefaultTracer,
tracer: elasticapm.DefaultTracer,
requestIgnorer: apmhttp.DefaultServerRequestIgnorer(),
}
for _, o := range o {
o(&opts)
Expand All @@ -29,7 +30,7 @@ func Wrap(h httprouter.Handle, route string, o ...Option) httprouter.Handle {
opts.recovery = apmhttp.NewTraceRecovery(opts.tracer)
}
return func(w http.ResponseWriter, req *http.Request, p httprouter.Params) {
if !opts.tracer.Active() {
if !opts.tracer.Active() || opts.requestIgnorer(req) {
h(w, req, p)
return
}
Expand All @@ -54,8 +55,9 @@ func Wrap(h httprouter.Handle, route string, o ...Option) httprouter.Handle {
}

type options struct {
tracer *elasticapm.Tracer
recovery apmhttp.RecoveryFunc
tracer *elasticapm.Tracer
recovery apmhttp.RecoveryFunc
requestIgnorer apmhttp.RequestIgnorerFunc
}

// Option sets options for tracing.
Expand All @@ -82,3 +84,15 @@ func WithRecovery(r apmhttp.RecoveryFunc) Option {
o.recovery = r
}
}

// WithRequestIgnorer returns a Option which sets r as the
// function to use to determine whether or not a request should
// be ignored. If r is nil, all requests will be reported.
func WithRequestIgnorer(r apmhttp.RequestIgnorerFunc) Option {
if r == nil {
r = apmhttp.IgnoreNone
}
return func(o *options) {
o.requestIgnorer = r
}
}

0 comments on commit 2717dc2

Please sign in to comment.