Skip to content

Commit

Permalink
feat: add HttpFilterOut sampler to filter traces based on request p…
Browse files Browse the repository at this point in the history
…ath (#454)

Add a new built-in sampler to filter out traces with attribute `http.target` matching a given filter. The filter could be provided either using the `WithPathFilter` method or using the environment variable `OTEL_GO_AUTO_HTTP_INSTRUMENTATION_FILTER_PATH`.

Signed-off-by: thomasgouveia <[email protected]>
  • Loading branch information
thomasgouveia committed Nov 6, 2023
1 parent 68220b0 commit b88895c
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http
- Add the `WithTraceExporter` `InstrumentationOption` to configure the trace `SpanExporter` used by an `Instrumentation`. ([#426](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/426))
- Add HTTP status code attribute to `net/http` server instrumentation. ([#428](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/428))
- The instrumentation scope now includes the version of the auto-instrumentation project. ([#442](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/442))
- Add a new built-in sampler to filter out traces with attribute `http.target` matching a given filter. ([#454](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/468)). The filter could be provided either using the `WithPathFilter` method or using the environment variable `OTEL_GO_AUTO_HTTP_INSTRUMENTATION_FILTER_PATH`.

### Changed

Expand Down
41 changes: 40 additions & 1 deletion instrumentation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ import (
"log"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"

"go.opentelemetry.io/auto/internal/pkg/builtin"

"github.com/go-logr/logr"
"github.com/go-logr/stdr"
"github.com/go-logr/zapr"
Expand Down Expand Up @@ -51,6 +54,9 @@ const (
// envTracesExportersKey is the key for the environment variable value
// containing what OpenTelemetry trace exporter to use.
envTracesExportersKey = "OTEL_TRACES_EXPORTER"
// envGoAutoHttpInstrumentationFilterPath is the key for the environment variable value
// containing a regex allowing to filter out some paths.
envGoAutoHttpInstrumentationFilterPath = "OTEL_GO_AUTO_HTTP_INSTRUMENTATION_FILTER_PATH"
)

// Instrumentation manages and controls all OpenTelemetry Go
Expand Down Expand Up @@ -166,6 +172,7 @@ type instConfig struct {
traceExp trace.SpanExporter
target process.TargetArgs
serviceName string
pathFilter *regexp.Regexp
}

func newInstConfig(ctx context.Context, opts []InstrumentationOption) (instConfig, error) {
Expand Down Expand Up @@ -216,13 +223,20 @@ func (c instConfig) validate() error {

func (c instConfig) tracerProvider() *trace.TracerProvider {
return trace.NewTracerProvider(
trace.WithSampler(trace.AlwaysSample()),
trace.WithSampler(c.sampler()),
trace.WithResource(c.res()),
trace.WithBatcher(c.traceExp),
trace.WithIDGenerator(opentelemetry.NewEBPFSourceIDGenerator()),
)
}

func (c instConfig) sampler() trace.Sampler {
if c.pathFilter != nil {
return builtin.HttpFilterOut(c.pathFilter)
}
return trace.AlwaysSample()
}

func (c instConfig) res() *resource.Resource {
runVer := strings.TrimPrefix(runtime.Version(), "go")
runName := runtime.Compiler
Expand Down Expand Up @@ -382,3 +396,28 @@ func WithTraceExporter(exp trace.SpanExporter) InstrumentationOption {
return c, nil
})
}

// WithPathFilter returns an [InstrumentationOption] that will configure
// an [Instrumentation] to filter out the HTTP request matching the given filter.
//
// If OTEL_GO_AUTO_HTTP_INSTRUMENTATION_FILTER_PATH is defined, it will take precedence over any value passed here.
// the pathFilter parameter value.
func WithPathFilter(pathFilter string) InstrumentationOption {
return fnOpt(func(_ context.Context, c instConfig) (instConfig, error) {
filter := pathFilter

// Check if the OTEL_GO_AUTO_HTTP_INSTRUMENTATION_FILTER_PATH is defined
// and update the value of the filter in that case
if v, ok := lookupEnv(envGoAutoHttpInstrumentationFilterPath); ok && v != "" {
filter = v
}

regex, err := regexp.Compile(filter)
if err != nil {
return c, fmt.Errorf("pathFilter could not be compiled as a regular expression: %w", err)
}

c.pathFilter = regex
return c, nil
})
}
83 changes: 83 additions & 0 deletions internal/pkg/builtin/sampling.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package builtin

import (
"fmt"
"regexp"

"go.opentelemetry.io/otel/attribute"
otelsdk "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)

// httpFilterOutPathSampler is a custom implementation of OpenTelemetry sampler
// used to filter out traces based on their HTTP request path.
type httpFilterOutPathSampler struct {
filter *regexp.Regexp
}

// Ensure that the implementation satisfies the interface.
var _ otelsdk.Sampler = (*httpFilterOutPathSampler)(nil)

// HttpFilterOut returns a Sampler that filter out samples with HTTP path
// matching the given filter. Useful for filter HTTP requests with
// path like /health, /metrics or whatever.
func HttpFilterOut(filter *regexp.Regexp) otelsdk.Sampler {
return &httpFilterOutPathSampler{filter}
}

// ShouldSample implements the interface [otelsdk.Sampler].
func (f httpFilterOutPathSampler) ShouldSample(p otelsdk.SamplingParameters) otelsdk.SamplingResult {
if f.shouldDrop(p) {
return otelsdk.SamplingResult{
Decision: otelsdk.Drop,
Tracestate: trace.SpanContextFromContext(p.ParentContext).TraceState(),
}
}

return otelsdk.SamplingResult{
Decision: otelsdk.RecordAndSample,
Tracestate: trace.SpanContextFromContext(p.ParentContext).TraceState(),
}
}

// Description implements the interface [otelsdk.Sampler].
func (f httpFilterOutPathSampler) Description() string {
return "HttpFilterOut"
}

// shouldDrop determines if the given sample should be dropped or not.
func (f httpFilterOutPathSampler) shouldDrop(p otelsdk.SamplingParameters) bool {
attr, err := getAttribute(p, "http.target")
if err != nil {
// If we have an error here, it is because the attribute is not
// found on the given parameters. We can safely return false.
return false
}
httpPath := attr.Value.AsString()

return f.filter.MatchString(httpPath)
}

// getAttribute returns the attribute at the given key in the given sampling parameters or an error if the key has no value.
func getAttribute(p otelsdk.SamplingParameters, key string) (attribute.KeyValue, error) {
for _, attr := range p.Attributes {
if attr.Key == attribute.Key(key) {
return attr, nil
}
}
return attribute.KeyValue{}, fmt.Errorf("no attribute with key %s", key)
}
62 changes: 62 additions & 0 deletions internal/pkg/builtin/sampling_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package builtin

import (
"regexp"
"testing"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
otelsdk "go.opentelemetry.io/otel/sdk/trace"
)

func TestHttpFilterOut(t *testing.T) {
testCases := []struct {
name string
httpPath string
filter *regexp.Regexp
expectedDecision otelsdk.SamplingDecision
}{
{
"traceNotDropped",
"/api/v1/something",
regexp.MustCompile("/health.+"),
otelsdk.RecordAndSample,
},
{
"traceDropped",
"/health/liveness",
regexp.MustCompile("/health.+"),
otelsdk.Drop,
},
{
"traceNotDroppedIfPatternIsUsedAnywhereInPath",
"/v1/deployments/health",
regexp.MustCompile("/health.+"),
otelsdk.RecordAndSample,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
params := otelsdk.SamplingParameters{
Attributes: []attribute.KeyValue{attribute.String("http.target", tc.httpPath)},
}
sampler := HttpFilterOut(tc.filter)
assert.Equal(t, tc.expectedDecision, sampler.ShouldSample(params).Decision)
})
}
}
2 changes: 1 addition & 1 deletion version.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ package auto

// Version is the current release version of OpenTelemetry Go auto-instrumentation in use.
func Version() string {
return "v0.7.0-alpha"
return "v0.8.0-alpha"
}

0 comments on commit b88895c

Please sign in to comment.