diff --git a/CHANGELOG.md b/CHANGELOG.md index 78f99a77d5b..fc685ac9845 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - The `WithSchemaURL` option function in `go.opentelemetry.io/contrib/bridges/otelslog`. This option function is used as a replacement of `WithInstrumentationScope` to specify the semantic convention schema URL for the logged records. (#5588) - Add support for Cloud Run jobs in `go.opentelemetry.io/contrib/detectors/gcp`. (#5559) +- Add `WithGinFilter` Filter parameter in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` to allow filtering with the `gin.Context`. (#5743) ### Changed diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go index e40775d7c3e..7dd6b3347f0 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go @@ -52,6 +52,14 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc { return } } + for _, f := range cfg.GinFilters { + if !f(c) { + // 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 113576ca00a..a27b9bfa1d5 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/option.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/option.go @@ -7,7 +7,7 @@ package otelgin // import "go.opentelemetry.io/contrib/instrumentation/github.co import ( "net/http" - + "github.com/gin-gonic/gin" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" ) @@ -16,6 +16,7 @@ type config struct { TracerProvider oteltrace.TracerProvider Propagators propagation.TextMapPropagator Filters []Filter + GinFilters []GinFilter SpanNameFormatter SpanNameFormatter } @@ -23,6 +24,10 @@ type config struct { // be traced. A Filter must return true if the request should be traced. type Filter func(*http.Request) bool +// Adding new Filter parameter (*gin.Context) +//gin.Context has FullPath() method, which returns a matched route full path. +type GinFilter func(*gin.Context) bool + // SpanNameFormatter is used to set span name by http.request. type SpanNameFormatter func(r *http.Request) string @@ -70,6 +75,13 @@ func WithFilter(f ...Filter) Option { }) } +// WithGinFilter adds a filter to the list of filters used by the handler. +func WithGinFilter(f ...GinFilter) Option { + return optionFunc(func(c *config) { + c.GinFilters = append(c.GinFilters, f...) + }) +} + // WithSpanNameFormatter takes a function that will be called on every // request and the returned string will become the Span Name. func WithSpanNameFormatter(f func(r *http.Request) string) Option { 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 628955c4586..04a3d4bc9bb 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 @@ -287,3 +287,38 @@ func TestWithFilter(t *testing.T) { assert.Len(t, sr.Ended(), 1) }) } + +func TestWithGinFilter(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(c *gin.Context) bool { return c.Request.URL.Path != "/healthcheck" } + router.Use(otelgin.Middleware("foobar", otelgin.WithGinFilter(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(c *gin.Context) bool { return c.Request.URL.Path != "/user/:id" } + router.Use(otelgin.Middleware("foobar", otelgin.WithGinFilter(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) + }) +} +