Repository: open-telemetry/opentelemetry-go-contrib
Pull request #2965 (2022-11-03 10:08:25)

Add support for filters to the otelgin package
Enhance [otelgin](https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/github.com/gin-gonic/gin/otelgin) to support filtering.

Fixes https://github.com/open-telemetry/opentelemetry-go-contrib/pull/2963.

linux-foundation-easycla[bot]:  <a href="https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/9522512/242202166/2965/#/?version=2"><img src="https://s3.amazonaws.com/cla-project-logo-prod/cla-not-signed.svg" alt="CLA Not Signed" align="left" height="28" width="328"></a><br/><br /><ul><li><a href='https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/9522512/242202166/2965/#/?version=2' target='_blank'>:x:</a> - login: @Orogenesis / name: Maxim . The commit (502cba4fd47d3056be8c218aecc039db5188200e) is not authorized under a signed CLA. <a href='https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/9522512/242202166/2965/#/?version=2' target='_blank'>Please click here to be authorized</a>. For further assistance with EasyCLA, <a href='https://jira.linuxfoundation.org/servicedesk/customer/portal/4' target='_blank'>please submit a support request ticket</a>.</li></ul>
codecov[bot]: # [Codecov](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2965?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) Report
> Merging [#2965](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2965?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) (502cba4) into [main](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/commit/0893cda4564dbc0a9a8fac449304488d45ee0cfe?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) (0893cda) will **increase** coverage by `0.0%`.
> The diff coverage is `100.0%`.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2965/graphs/tree.svg?width=650&height=150&src=pr&token=P6F3W9WA7Q&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry)](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2965?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry)

```diff
@@          Coverage Diff          @@
##            main   #2965   +/-   ##
=====================================
  Coverage   69.4%   69.5%           
=====================================
  Files        146     146           
  Lines       6752    6763   +11     
=====================================
+ Hits        4692    4703   +11     
  Misses      1944    1944           
  Partials     116     116           
```


| [Impacted Files](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2965?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | Coverage Δ | |
|---|---|---|
| [...ation/github.com/gin-gonic/gin/otelgin/gintrace.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2965/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-aW5zdHJ1bWVudGF0aW9uL2dpdGh1Yi5jb20vZ2luLWdvbmljL2dpbi9vdGVsZ2luL2dpbnRyYWNlLmdv) | `82.3% <100.0%> (+1.5%)` | :arrow_up: |
| [...ntation/github.com/gin-gonic/gin/otelgin/option.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2965/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-aW5zdHJ1bWVudGF0aW9uL2dpdGh1Yi5jb20vZ2luLWdvbmljL2dpbi9vdGVsZ2luL29wdGlvbi5nbw==) | `100.0% <100.0%> (ø)` | |

</details>

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 11378f591..4cf4495e0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
 - The `WithLogger` option to `go.opentelemetry.io/contrib/samplers/jaegerremote` to allow users to pass a `logr.Logger` and have operations logged. (#2566)
 - Add the `messaging.url` & `messaging.system` attributes to all appropriate SQS operations in the `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` package. (#2879)
 - Add example use of the metrics signal to `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/example`. (#2610)
+- [otelgin] Add support for filters to the `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` package to provide the way to control which inbound requests are traced. (#2963)
 
 ### Fixed
 
diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go
index dce7e4ae8..1c2ce7853 100644
--- a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go
+++ b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go
@@ -54,6 +54,14 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc {
 		cfg.Propagators = otel.GetTextMapPropagator()
 	}
 	return func(c *gin.Context) {
+		for _, f := range cfg.Filters {
+			if !f(c.Request) {
+				// Serve the request to the next middleware
+				// if a filter rejects the request.
+				c.Next()
+				return
+			}
+		}
 		c.Set(tracerKey, tracer)
 		savedCtx := c.Request.Context()
 		defer func() {
diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/option.go b/instrumentation/github.com/gin-gonic/gin/otelgin/option.go
index 2c3eb7491..d8e1d1d9e 100644
--- a/instrumentation/github.com/gin-gonic/gin/otelgin/option.go
+++ b/instrumentation/github.com/gin-gonic/gin/otelgin/option.go
@@ -17,6 +17,8 @@
 package otelgin // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
 
 import (
+	"net/http"
+
 	"go.opentelemetry.io/otel/propagation"
 	oteltrace "go.opentelemetry.io/otel/trace"
 )
@@ -24,8 +26,13 @@ import (
 type config struct {
 	TracerProvider oteltrace.TracerProvider
 	Propagators    propagation.TextMapPropagator
+	Filters        []Filter
 }
 
+// Filter is a predicate used to determine whether a given http.request should
+// be traced. A Filter must return true if the request should be traced.
+type Filter func(*http.Request) bool
+
 // Option specifies instrumentation configuration options.
 type Option interface {
 	apply(*config)
@@ -57,3 +64,15 @@ func WithTracerProvider(provider oteltrace.TracerProvider) Option {
 		}
 	})
 }
+
+// WithFilter adds a filter to the list of filters used by the handler.
+// If any filter indicates to exclude a request then the request will not be
+// traced. All filters must allow a request to be traced for a Span to be created.
+// If no filters are provided then all requests are traced.
+// Filters will be invoked for each processed request, it is advised to make them
+// simple and fast.
+func WithFilter(f ...Filter) Option {
+	return optionFunc(func(c *config) {
+		c.Filters = append(c.Filters, f...)
+	})
+}


Test patch:
diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/test/gintrace_test.go b/instrumentation/github.com/gin-gonic/gin/otelgin/test/gintrace_test.go
index d67702353..b430c5c8c 100644
--- a/instrumentation/github.com/gin-gonic/gin/otelgin/test/gintrace_test.go
+++ b/instrumentation/github.com/gin-gonic/gin/otelgin/test/gintrace_test.go
@@ -203,3 +203,37 @@ func TestHTML(t *testing.T) {
 	require.NotNil(t, tspan)
 	assert.Contains(t, tspan.Attributes(), attribute.String("go.template", "hello"))
 }
+
+func TestWithFilter(t *testing.T) {
+	t.Run("custom filter filtering route", func(t *testing.T) {
+		sr := tracetest.NewSpanRecorder()
+		otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)))
+
+		router := gin.New()
+		f := func(req *http.Request) bool { return req.URL.Path != "/healthcheck" }
+		router.Use(otelgin.Middleware("foobar", otelgin.WithFilter(f)))
+		router.GET("/healthcheck", func(c *gin.Context) {})
+
+		r := httptest.NewRequest("GET", "/healthcheck", nil)
+		w := httptest.NewRecorder()
+
+		router.ServeHTTP(w, r)
+		assert.Len(t, sr.Ended(), 0)
+	})
+
+	t.Run("custom filter not filtering route", func(t *testing.T) {
+		sr := tracetest.NewSpanRecorder()
+		otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)))
+
+		router := gin.New()
+		f := func(req *http.Request) bool { return req.URL.Path != "/healthcheck" }
+		router.Use(otelgin.Middleware("foobar", otelgin.WithFilter(f)))
+		router.GET("/user/:id", func(c *gin.Context) {})
+
+		r := httptest.NewRequest("GET", "/user/123", nil)
+		w := httptest.NewRecorder()
+
+		router.ServeHTTP(w, r)
+		assert.Len(t, sr.Ended(), 1)
+	})
+}