diff --git a/CHANGELOG.md b/CHANGELOG.md index 54eb7b3c092..e6fb1b6454a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,17 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `prometheus` and `none` are supported values. You can specify multiple producers separated by a comma. - Add `WithFallbackMetricProducer` option that adds a fallback if the `OTEL_METRICS_PRODUCERS` is not set or empty. - The `go.opentelemetry.io/contrib/processors/baggage/baggagetrace` module. This module provides a Baggage Span Processor. (#5404) +- Add gRPC trace `Filter` for stats handler to `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#5196) + +### Changed + +- The gRPC trace `Filter` for interceptor is renamed to `InterceptorFilter`. (#5196) +- The gRPC trace filter functions `Any`, `All`, `None`, `Not`, `MethodName`, `MethodPrefix`, `FullMethodName`, `ServiceName`, `ServicePrefix` and `HealthCheck` for interceptor are moved to `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor`. + With this change, the filters in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` are now working for stats handler. (#5196) + +### Deprecated + +- The `InterceptorFilter` type in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` is deprecated. (#5196) ## [1.26.0/0.51.0/0.20.0/0.6.0/0.1.0] - 2024-04-24 diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/config.go b/instrumentation/google.golang.org/grpc/otelgrpc/config.go index 7a26fb5f6d2..a199b36b4fa 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/config.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/config.go @@ -4,6 +4,8 @@ package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" import ( + "google.golang.org/grpc/stats" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" @@ -20,18 +22,26 @@ const ( GRPCStatusCodeKey = attribute.Key("rpc.grpc.status_code") ) -// Filter is a predicate used to determine whether a given request in -// interceptor info should be traced. A Filter must return true if +// InterceptorFilter is a predicate used to determine whether a given request in +// interceptor info should be instrumented. A InterceptorFilter must return true if // the request should be traced. -type Filter func(*InterceptorInfo) bool +// +// Deprecated: Use stats handlers instead. +type InterceptorFilter func(*InterceptorInfo) bool + +// Filter is a predicate used to determine whether a given request in +// should be instrumented by the attatched RPC tag info. +// A Filter must return true if the request should be instrumented. +type Filter func(*stats.RPCTagInfo) bool // config is a group of options for this instrumentation. type config struct { - Filter Filter - Propagators propagation.TextMapPropagator - TracerProvider trace.TracerProvider - MeterProvider metric.MeterProvider - SpanStartOptions []trace.SpanStartOption + Filter Filter + InterceptorFilter InterceptorFilter + Propagators propagation.TextMapPropagator + TracerProvider trace.TracerProvider + MeterProvider metric.MeterProvider + SpanStartOptions []trace.SpanStartOption ReceivedEvent bool SentEvent bool @@ -152,15 +162,30 @@ func (o tracerProviderOption) apply(c *config) { // WithInterceptorFilter returns an Option to use the request filter. // // Deprecated: Use stats handlers instead. -func WithInterceptorFilter(f Filter) Option { +func WithInterceptorFilter(f InterceptorFilter) Option { return interceptorFilterOption{f: f} } type interceptorFilterOption struct { - f Filter + f InterceptorFilter } func (o interceptorFilterOption) apply(c *config) { + if o.f != nil { + c.InterceptorFilter = o.f + } +} + +// WithFilter returns an Option to use the request filter. +func WithFilter(f Filter) Option { + return filterOption{f: f} +} + +type filterOption struct { + f Filter +} + +func (o filterOption) apply(c *config) { if o.f != nil { c.Filter = o.f } diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters.go b/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters.go index e86c0018292..ee97b4175af 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters.go @@ -1,12 +1,16 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +// Package filters provides a set of filters useful with the +// [otelgrpc.WithFilter] option to control which inbound requests are instrumented. package filters // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters" import ( "path" "strings" + "google.golang.org/grpc/stats" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" ) @@ -19,20 +23,8 @@ type gRPCPath struct { // and returns as gRPCPath object that has divided service and method names // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md // If name is not FullMethod, returned gRPCPath has empty service field. -func splitFullMethod(i *otelgrpc.InterceptorInfo) gRPCPath { - var name string - switch i.Type { - case otelgrpc.UnaryServer: - name = i.UnaryServerInfo.FullMethod - case otelgrpc.StreamServer: - name = i.StreamServerInfo.FullMethod - case otelgrpc.UnaryClient, otelgrpc.StreamClient: - name = i.Method - default: - name = i.Method - } - - s, m := path.Split(name) +func splitFullMethod(i *stats.RPCTagInfo) gRPCPath { + s, m := path.Split(i.FullMethodName) if s != "" { s = path.Clean(s) s = strings.TrimLeft(s, "/") @@ -47,7 +39,7 @@ func splitFullMethod(i *otelgrpc.InterceptorInfo) gRPCPath { // Any takes a list of Filters and returns a Filter that // returns true if any Filter in the list returns true. func Any(fs ...otelgrpc.Filter) otelgrpc.Filter { - return func(i *otelgrpc.InterceptorInfo) bool { + return func(i *stats.RPCTagInfo) bool { for _, f := range fs { if f(i) { return true @@ -60,7 +52,7 @@ func Any(fs ...otelgrpc.Filter) otelgrpc.Filter { // All takes a list of Filters and returns a Filter that // returns true only if all Filters in the list return true. func All(fs ...otelgrpc.Filter) otelgrpc.Filter { - return func(i *otelgrpc.InterceptorInfo) bool { + return func(i *stats.RPCTagInfo) bool { for _, f := range fs { if !f(i) { return false @@ -78,7 +70,7 @@ func None(fs ...otelgrpc.Filter) otelgrpc.Filter { // Not provides a convenience mechanism for inverting a Filter. func Not(f otelgrpc.Filter) otelgrpc.Filter { - return func(i *otelgrpc.InterceptorInfo) bool { + return func(i *stats.RPCTagInfo) bool { return !f(i) } } @@ -86,7 +78,7 @@ func Not(f otelgrpc.Filter) otelgrpc.Filter { // MethodName returns a Filter that returns true if the request's // method name matches the provided string n. func MethodName(n string) otelgrpc.Filter { - return func(i *otelgrpc.InterceptorInfo) bool { + return func(i *stats.RPCTagInfo) bool { p := splitFullMethod(i) return p.method == n } @@ -95,7 +87,7 @@ func MethodName(n string) otelgrpc.Filter { // MethodPrefix returns a Filter that returns true if the request's // method starts with the provided string pre. func MethodPrefix(pre string) otelgrpc.Filter { - return func(i *otelgrpc.InterceptorInfo) bool { + return func(i *stats.RPCTagInfo) bool { p := splitFullMethod(i) return strings.HasPrefix(p.method, pre) } @@ -105,26 +97,15 @@ func MethodPrefix(pre string) otelgrpc.Filter { // full RPC method string, i.e. /package.service/method, starts with // the provided string n. func FullMethodName(n string) otelgrpc.Filter { - return func(i *otelgrpc.InterceptorInfo) bool { - var fm string - switch i.Type { - case otelgrpc.UnaryClient, otelgrpc.StreamClient: - fm = i.Method - case otelgrpc.UnaryServer: - fm = i.UnaryServerInfo.FullMethod - case otelgrpc.StreamServer: - fm = i.StreamServerInfo.FullMethod - default: - fm = i.Method - } - return fm == n + return func(i *stats.RPCTagInfo) bool { + return i.FullMethodName == n } } // ServiceName returns a Filter that returns true if the request's // service name, i.e. package.service, matches s. func ServiceName(s string) otelgrpc.Filter { - return func(i *otelgrpc.InterceptorInfo) bool { + return func(i *stats.RPCTagInfo) bool { p := splitFullMethod(i) return p.service == s } @@ -133,7 +114,7 @@ func ServiceName(s string) otelgrpc.Filter { // ServicePrefix returns a Filter that returns true if the request's // service name, i.e. package.service, starts with the provided string pre. func ServicePrefix(pre string) otelgrpc.Filter { - return func(i *otelgrpc.InterceptorInfo) bool { + return func(i *stats.RPCTagInfo) bool { p := splitFullMethod(i) return strings.HasPrefix(p.service, pre) } diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters_test.go b/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters_test.go index 6ac330a68f0..46260b6585b 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters_test.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters_test.go @@ -6,27 +6,21 @@ package filters // import "go.opentelemetry.io/contrib/instrumentation/google.go import ( "testing" - "google.golang.org/grpc" + "google.golang.org/grpc/stats" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" ) type testCase struct { name string - i *otelgrpc.InterceptorInfo + i *stats.RPCTagInfo f otelgrpc.Filter want bool } -func dummyUnaryServerInfo(n string) *grpc.UnaryServerInfo { - return &grpc.UnaryServerInfo{ - FullMethod: n, - } -} - -func dummyStreamServerInfo(n string) *grpc.StreamServerInfo { - return &grpc.StreamServerInfo{ - FullMethod: n, +func dummyRPCTagInfo(n string) *stats.RPCTagInfo { + return &stats.RPCTagInfo{ + FullMethodName: n, } } @@ -34,32 +28,14 @@ func TestMethodName(t *testing.T) { const dummyFullMethodName = "/example.HelloService/Hello" tcs := []testCase{ { - name: "unary client interceptor", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, - f: MethodName("Hello"), - want: true, - }, - { - name: "stream client interceptor", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.StreamClient}, - f: MethodName("Hello"), - want: true, - }, - { - name: "unary server interceptor", - i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, + name: "true", + i: dummyRPCTagInfo(dummyFullMethodName), f: MethodName("Hello"), want: true, }, { - name: "stream server interceptor", - i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Type: otelgrpc.StreamServer}, - f: MethodName("Hello"), - want: true, - }, - { - name: "unary client interceptor fail", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "false", + i: dummyRPCTagInfo(dummyFullMethodName), f: MethodName("Goodbye"), want: false, }, @@ -77,32 +53,14 @@ func TestMethodPrefix(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { - name: "unary client interceptor", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, - f: MethodPrefix("Foobar"), - want: true, - }, - { - name: "stream client interceptor", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.StreamClient}, - f: MethodPrefix("Foobar"), - want: true, - }, - { - name: "unary server interceptor", - i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, - f: MethodPrefix("Foobar"), - want: true, - }, - { - name: "stream server interceptor", - i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Type: otelgrpc.StreamServer}, + name: "true", + i: dummyRPCTagInfo(dummyFullMethodName), f: MethodPrefix("Foobar"), want: true, }, { - name: "unary client interceptor fail", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "false", + i: dummyRPCTagInfo(dummyFullMethodName), f: MethodPrefix("Barfoo"), want: false, }, @@ -119,32 +77,14 @@ func TestFullMethodName(t *testing.T) { const dummyFullMethodName = "/example.HelloService/Hello" tcs := []testCase{ { - name: "unary client interceptor", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, - f: FullMethodName(dummyFullMethodName), - want: true, - }, - { - name: "stream client interceptor", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.StreamClient}, - f: FullMethodName(dummyFullMethodName), - want: true, - }, - { - name: "unary server interceptor", - i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, - f: FullMethodName(dummyFullMethodName), - want: true, - }, - { - name: "stream server interceptor", - i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Type: otelgrpc.StreamServer}, + name: "true", + i: dummyRPCTagInfo(dummyFullMethodName), f: FullMethodName(dummyFullMethodName), want: true, }, { - name: "unary client interceptor fail", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "false", + i: dummyRPCTagInfo(dummyFullMethodName), f: FullMethodName("/example.HelloService/Goodbye"), want: false, }, @@ -163,32 +103,14 @@ func TestServiceName(t *testing.T) { tcs := []testCase{ { - name: "unary client interceptor", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "true", + i: dummyRPCTagInfo(dummyFullMethodName), f: ServiceName("example.HelloService"), want: true, }, { - name: "stream client interceptor", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.StreamClient}, - f: ServiceName("example.HelloService"), - want: true, - }, - { - name: "unary server interceptor", - i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, - f: ServiceName("example.HelloService"), - want: true, - }, - { - name: "stream server interceptor", - i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Type: otelgrpc.StreamServer}, - f: ServiceName("example.HelloService"), - want: true, - }, - { - name: "unary client interceptor fail", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "false", + i: dummyRPCTagInfo(dummyFullMethodName), f: ServiceName("opentelemetry.HelloService"), want: false, }, @@ -206,32 +128,14 @@ func TestServicePrefix(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { - name: "unary client interceptor", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, - f: ServicePrefix("example"), - want: true, - }, - { - name: "stream client interceptor", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.StreamClient}, + name: "true", + i: dummyRPCTagInfo(dummyFullMethodName), f: ServicePrefix("example"), want: true, }, { - name: "unary server interceptor", - i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, - f: ServicePrefix("example"), - want: true, - }, - { - name: "stream server interceptor", - i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Type: otelgrpc.StreamServer}, - f: ServicePrefix("example"), - want: true, - }, - { - name: "unary client interceptor fail", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "false", + i: dummyRPCTagInfo(dummyFullMethodName), f: ServicePrefix("opentelemetry"), want: false, }, @@ -248,20 +152,20 @@ func TestAny(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { - name: "unary client interceptor true && true", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "true && true", + i: dummyRPCTagInfo(dummyFullMethodName), f: Any(MethodName("FoobarHello"), MethodPrefix("Foobar")), want: true, }, { - name: "unary client interceptor false && true", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "false && true", + i: dummyRPCTagInfo(dummyFullMethodName), f: Any(MethodName("Hello"), MethodPrefix("Foobar")), want: true, }, { - name: "unary client interceptor false && false", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "false && false", + i: dummyRPCTagInfo(dummyFullMethodName), f: Any(MethodName("Goodbye"), MethodPrefix("Barfoo")), want: false, }, @@ -278,20 +182,20 @@ func TestAll(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { - name: "unary client interceptor true && true", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "true && true", + i: dummyRPCTagInfo(dummyFullMethodName), f: All(MethodName("FoobarHello"), MethodPrefix("Foobar")), want: true, }, { - name: "unary client interceptor true && false", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "true && false", + i: dummyRPCTagInfo(dummyFullMethodName), f: All(MethodName("FoobarHello"), MethodPrefix("Barfoo")), want: false, }, { - name: "unary client interceptor false && false", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "false && false", + i: dummyRPCTagInfo(dummyFullMethodName), f: All(MethodName("FoobarGoodbye"), MethodPrefix("Barfoo")), want: false, }, @@ -308,20 +212,20 @@ func TestNone(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { - name: "unary client interceptor true && true", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "true && true", + i: dummyRPCTagInfo(dummyFullMethodName), f: None(MethodName("FoobarHello"), MethodPrefix("Foobar")), want: false, }, { - name: "unary client interceptor true && false", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "true && false", + i: dummyRPCTagInfo(dummyFullMethodName), f: None(MethodName("FoobarHello"), MethodPrefix("Barfoo")), want: false, }, { - name: "unary client interceptor false && false", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "false && false", + i: dummyRPCTagInfo(dummyFullMethodName), f: None(MethodName("FoobarGoodbye"), MethodPrefix("Barfoo")), want: true, }, @@ -339,19 +243,19 @@ func TestNot(t *testing.T) { tcs := []testCase{ { name: "methodname not", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + i: dummyRPCTagInfo(dummyFullMethodName), f: Not(MethodName("FoobarHello")), want: false, }, { name: "method prefix not", - i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, + i: dummyRPCTagInfo(dummyFullMethodName), f: Not(MethodPrefix("FoobarHello")), want: false, }, { - name: "unary client interceptor not all(true && true)", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + name: "not all(true && true)", + i: dummyRPCTagInfo(dummyFullMethodName), f: Not(All(MethodName("FoobarHello"), MethodPrefix("Foobar"))), want: false, }, @@ -372,50 +276,14 @@ func TestHealthCheck(t *testing.T) { ) tcs := []testCase{ { - name: "unary client interceptor healthcheck", - i: &otelgrpc.InterceptorInfo{Method: healthCheck, Type: otelgrpc.UnaryClient}, + name: "true", + i: dummyRPCTagInfo(healthCheck), f: HealthCheck(), want: true, }, { - name: "stream client interceptor healthcheck", - i: &otelgrpc.InterceptorInfo{Method: healthCheck, Type: otelgrpc.StreamClient}, - f: HealthCheck(), - want: true, - }, - { - name: "unary server interceptor healthcheck", - i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(healthCheck), Type: otelgrpc.UnaryServer}, - f: HealthCheck(), - want: true, - }, - { - name: "stream server interceptor healthcheck", - i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(healthCheck), Type: otelgrpc.StreamServer}, - f: HealthCheck(), - want: true, - }, - { - name: "unary client interceptor", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethod, Type: otelgrpc.UnaryClient}, - f: HealthCheck(), - want: false, - }, - { - name: "stream client interceptor", - i: &otelgrpc.InterceptorInfo{Method: dummyFullMethod, Type: otelgrpc.StreamClient}, - f: HealthCheck(), - want: false, - }, - { - name: "unary server interceptor", - i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethod), Type: otelgrpc.UnaryServer}, - f: HealthCheck(), - want: false, - }, - { - name: "stream server interceptor", - i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethod), Type: otelgrpc.StreamServer}, + name: "false", + i: dummyRPCTagInfo(dummyFullMethod), f: HealthCheck(), want: false, }, diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor/filters.go b/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor/filters.go new file mode 100644 index 00000000000..ca9cc9a305d --- /dev/null +++ b/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor/filters.go @@ -0,0 +1,171 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package interceptor provides a set of filters useful with the +// [otelgrpc.WithInterceptorFilter] option to control which inbound requests are instrumented. +// +// Deprecated: Use filters package and [otelgrpc.WithFilter] instead. +package interceptor // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor" + +import ( + "path" + "strings" + + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" +) + +type gRPCPath struct { + service string + method string +} + +// splitFullMethod splits path defined in gRPC protocol +// and returns as gRPCPath object that has divided service and method names +// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md +// If name is not FullMethod, returned gRPCPath has empty service field. +func splitFullMethod(i *otelgrpc.InterceptorInfo) gRPCPath { + var name string + switch i.Type { + case otelgrpc.UnaryServer: + name = i.UnaryServerInfo.FullMethod + case otelgrpc.StreamServer: + name = i.StreamServerInfo.FullMethod + case otelgrpc.UnaryClient, otelgrpc.StreamClient: + name = i.Method + default: + name = i.Method + } + + s, m := path.Split(name) + if s != "" { + s = path.Clean(s) + s = strings.TrimLeft(s, "/") + } + + return gRPCPath{ + service: s, + method: m, + } +} + +// Any takes a list of Filters and returns a Filter that +// returns true if any Filter in the list returns true. +// +// Deprecated: Use stats handler instead. +func Any(fs ...otelgrpc.InterceptorFilter) otelgrpc.InterceptorFilter { + return func(i *otelgrpc.InterceptorInfo) bool { + for _, f := range fs { + if f(i) { + return true + } + } + return false + } +} + +// All takes a list of Filters and returns a Filter that +// returns true only if all Filters in the list return true. +// +// Deprecated: Use stats handler instead. +func All(fs ...otelgrpc.InterceptorFilter) otelgrpc.InterceptorFilter { + return func(i *otelgrpc.InterceptorInfo) bool { + for _, f := range fs { + if !f(i) { + return false + } + } + return true + } +} + +// None takes a list of Filters and returns a Filter that returns +// true only if none of the Filters in the list return true. +// +// Deprecated: Use stats handler instead. +func None(fs ...otelgrpc.InterceptorFilter) otelgrpc.InterceptorFilter { + return Not(Any(fs...)) +} + +// Not provides a convenience mechanism for inverting a Filter. +// +// Deprecated: Use stats handler instead. +func Not(f otelgrpc.InterceptorFilter) otelgrpc.InterceptorFilter { + return func(i *otelgrpc.InterceptorInfo) bool { + return !f(i) + } +} + +// MethodName returns a Filter that returns true if the request's +// method name matches the provided string n. +// +// Deprecated: Use stats handler instead. +func MethodName(n string) otelgrpc.InterceptorFilter { + return func(i *otelgrpc.InterceptorInfo) bool { + p := splitFullMethod(i) + return p.method == n + } +} + +// MethodPrefix returns a Filter that returns true if the request's +// method starts with the provided string pre. +// +// Deprecated: Use stats handler instead. +func MethodPrefix(pre string) otelgrpc.InterceptorFilter { + return func(i *otelgrpc.InterceptorInfo) bool { + p := splitFullMethod(i) + return strings.HasPrefix(p.method, pre) + } +} + +// FullMethodName returns a Filter that returns true if the request's +// full RPC method string, i.e. /package.service/method, starts with +// the provided string n. +// +// Deprecated: Use stats handler instead. +func FullMethodName(n string) otelgrpc.InterceptorFilter { + return func(i *otelgrpc.InterceptorInfo) bool { + var fm string + switch i.Type { + case otelgrpc.UnaryClient, otelgrpc.StreamClient: + fm = i.Method + case otelgrpc.UnaryServer: + fm = i.UnaryServerInfo.FullMethod + case otelgrpc.StreamServer: + fm = i.StreamServerInfo.FullMethod + default: + fm = i.Method + } + return fm == n + } +} + +// ServiceName returns a Filter that returns true if the request's +// service name, i.e. package.service, matches s. +// +// Deprecated: Use stats handler instead. +func ServiceName(s string) otelgrpc.InterceptorFilter { + return func(i *otelgrpc.InterceptorInfo) bool { + p := splitFullMethod(i) + return p.service == s + } +} + +// ServicePrefix returns a Filter that returns true if the request's +// service name, i.e. package.service, starts with the provided string pre. +// +// Deprecated: Use stats handler instead. +func ServicePrefix(pre string) otelgrpc.InterceptorFilter { + return func(i *otelgrpc.InterceptorInfo) bool { + p := splitFullMethod(i) + return strings.HasPrefix(p.service, pre) + } +} + +// HealthCheck returns a Filter that returns true if the request's +// service name is health check defined by gRPC Health Checking Protocol. +// https://github.com/grpc/grpc/blob/master/doc/health-checking.md +// +// Deprecated: Use stats handler instead. +func HealthCheck() otelgrpc.InterceptorFilter { + return ServicePrefix("grpc.health.v1.Health") +} diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor/filters_test.go b/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor/filters_test.go new file mode 100644 index 00000000000..e2816cd7b64 --- /dev/null +++ b/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor/filters_test.go @@ -0,0 +1,431 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package interceptor // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor" + +import ( + "testing" + + "google.golang.org/grpc" + + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" +) + +type testCase struct { + name string + i *otelgrpc.InterceptorInfo + //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. + f otelgrpc.InterceptorFilter + want bool +} + +func dummyUnaryServerInfo(n string) *grpc.UnaryServerInfo { + return &grpc.UnaryServerInfo{ + FullMethod: n, + } +} + +func dummyStreamServerInfo(n string) *grpc.StreamServerInfo { + return &grpc.StreamServerInfo{ + FullMethod: n, + } +} + +func TestMethodName(t *testing.T) { + const dummyFullMethodName = "/example.HelloService/Hello" + tcs := []testCase{ + { + name: "unary client interceptor", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: MethodName("Hello"), + want: true, + }, + { + name: "stream client interceptor", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.StreamClient}, + f: MethodName("Hello"), + want: true, + }, + { + name: "unary server interceptor", + i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, + f: MethodName("Hello"), + want: true, + }, + { + name: "stream server interceptor", + i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Type: otelgrpc.StreamServer}, + f: MethodName("Hello"), + want: true, + }, + { + name: "unary client interceptor fail", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: MethodName("Goodbye"), + want: false, + }, + } + + for _, tc := range tcs { + out := tc.f(tc.i) + if tc.want != out { + t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) + } + } +} + +func TestMethodPrefix(t *testing.T) { + const dummyFullMethodName = "/example.HelloService/FoobarHello" + tcs := []testCase{ + { + name: "unary client interceptor", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: MethodPrefix("Foobar"), + want: true, + }, + { + name: "stream client interceptor", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.StreamClient}, + f: MethodPrefix("Foobar"), + want: true, + }, + { + name: "unary server interceptor", + i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, + f: MethodPrefix("Foobar"), + want: true, + }, + { + name: "stream server interceptor", + i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Type: otelgrpc.StreamServer}, + f: MethodPrefix("Foobar"), + want: true, + }, + { + name: "unary client interceptor fail", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: MethodPrefix("Barfoo"), + want: false, + }, + } + for _, tc := range tcs { + out := tc.f(tc.i) + if tc.want != out { + t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) + } + } +} + +func TestFullMethodName(t *testing.T) { + const dummyFullMethodName = "/example.HelloService/Hello" + tcs := []testCase{ + { + name: "unary client interceptor", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: FullMethodName(dummyFullMethodName), + want: true, + }, + { + name: "stream client interceptor", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.StreamClient}, + f: FullMethodName(dummyFullMethodName), + want: true, + }, + { + name: "unary server interceptor", + i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, + f: FullMethodName(dummyFullMethodName), + want: true, + }, + { + name: "stream server interceptor", + i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Type: otelgrpc.StreamServer}, + f: FullMethodName(dummyFullMethodName), + want: true, + }, + { + name: "unary client interceptor fail", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: FullMethodName("/example.HelloService/Goodbye"), + want: false, + }, + } + + for _, tc := range tcs { + out := tc.f(tc.i) + if tc.want != out { + t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) + } + } +} + +func TestServiceName(t *testing.T) { + const dummyFullMethodName = "/example.HelloService/Hello" + + tcs := []testCase{ + { + name: "unary client interceptor", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: ServiceName("example.HelloService"), + want: true, + }, + { + name: "stream client interceptor", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.StreamClient}, + f: ServiceName("example.HelloService"), + want: true, + }, + { + name: "unary server interceptor", + i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, + f: ServiceName("example.HelloService"), + want: true, + }, + { + name: "stream server interceptor", + i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Type: otelgrpc.StreamServer}, + f: ServiceName("example.HelloService"), + want: true, + }, + { + name: "unary client interceptor fail", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: ServiceName("opentelemetry.HelloService"), + want: false, + }, + } + + for _, tc := range tcs { + out := tc.f(tc.i) + if tc.want != out { + t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) + } + } +} + +func TestServicePrefix(t *testing.T) { + const dummyFullMethodName = "/example.HelloService/FoobarHello" + tcs := []testCase{ + { + name: "unary client interceptor", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: ServicePrefix("example"), + want: true, + }, + { + name: "stream client interceptor", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.StreamClient}, + f: ServicePrefix("example"), + want: true, + }, + { + name: "unary server interceptor", + i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, + f: ServicePrefix("example"), + want: true, + }, + { + name: "stream server interceptor", + i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Type: otelgrpc.StreamServer}, + f: ServicePrefix("example"), + want: true, + }, + { + name: "unary client interceptor fail", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: ServicePrefix("opentelemetry"), + want: false, + }, + } + for _, tc := range tcs { + out := tc.f(tc.i) + if tc.want != out { + t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) + } + } +} + +func TestAny(t *testing.T) { + const dummyFullMethodName = "/example.HelloService/FoobarHello" + tcs := []testCase{ + { + name: "unary client interceptor true && true", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: Any(MethodName("FoobarHello"), MethodPrefix("Foobar")), + want: true, + }, + { + name: "unary client interceptor false && true", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: Any(MethodName("Hello"), MethodPrefix("Foobar")), + want: true, + }, + { + name: "unary client interceptor false && false", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: Any(MethodName("Goodbye"), MethodPrefix("Barfoo")), + want: false, + }, + } + for _, tc := range tcs { + out := tc.f(tc.i) + if tc.want != out { + t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) + } + } +} + +func TestAll(t *testing.T) { + const dummyFullMethodName = "/example.HelloService/FoobarHello" + tcs := []testCase{ + { + name: "unary client interceptor true && true", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: All(MethodName("FoobarHello"), MethodPrefix("Foobar")), + want: true, + }, + { + name: "unary client interceptor true && false", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: All(MethodName("FoobarHello"), MethodPrefix("Barfoo")), + want: false, + }, + { + name: "unary client interceptor false && false", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: All(MethodName("FoobarGoodbye"), MethodPrefix("Barfoo")), + want: false, + }, + } + for _, tc := range tcs { + out := tc.f(tc.i) + if tc.want != out { + t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) + } + } +} + +func TestNone(t *testing.T) { + const dummyFullMethodName = "/example.HelloService/FoobarHello" + tcs := []testCase{ + { + name: "unary client interceptor true && true", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: None(MethodName("FoobarHello"), MethodPrefix("Foobar")), + want: false, + }, + { + name: "unary client interceptor true && false", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: None(MethodName("FoobarHello"), MethodPrefix("Barfoo")), + want: false, + }, + { + name: "unary client interceptor false && false", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: None(MethodName("FoobarGoodbye"), MethodPrefix("Barfoo")), + want: true, + }, + } + for _, tc := range tcs { + out := tc.f(tc.i) + if tc.want != out { + t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) + } + } +} + +func TestNot(t *testing.T) { + const dummyFullMethodName = "/example.HelloService/FoobarHello" + tcs := []testCase{ + { + name: "methodname not", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: Not(MethodName("FoobarHello")), + want: false, + }, + { + name: "method prefix not", + i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, + f: Not(MethodPrefix("FoobarHello")), + want: false, + }, + { + name: "unary client interceptor not all(true && true)", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, + f: Not(All(MethodName("FoobarHello"), MethodPrefix("Foobar"))), + want: false, + }, + } + + for _, tc := range tcs { + out := tc.f(tc.i) + if tc.want != out { + t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) + } + } +} + +func TestHealthCheck(t *testing.T) { + const ( + healthCheck = "/grpc.health.v1.Health/Check" + dummyFullMethod = "/example.HelloService/FoobarHello" + ) + tcs := []testCase{ + { + name: "unary client interceptor healthcheck", + i: &otelgrpc.InterceptorInfo{Method: healthCheck, Type: otelgrpc.UnaryClient}, + f: HealthCheck(), + want: true, + }, + { + name: "stream client interceptor healthcheck", + i: &otelgrpc.InterceptorInfo{Method: healthCheck, Type: otelgrpc.StreamClient}, + f: HealthCheck(), + want: true, + }, + { + name: "unary server interceptor healthcheck", + i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(healthCheck), Type: otelgrpc.UnaryServer}, + f: HealthCheck(), + want: true, + }, + { + name: "stream server interceptor healthcheck", + i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(healthCheck), Type: otelgrpc.StreamServer}, + f: HealthCheck(), + want: true, + }, + { + name: "unary client interceptor", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethod, Type: otelgrpc.UnaryClient}, + f: HealthCheck(), + want: false, + }, + { + name: "stream client interceptor", + i: &otelgrpc.InterceptorInfo{Method: dummyFullMethod, Type: otelgrpc.StreamClient}, + f: HealthCheck(), + want: false, + }, + { + name: "unary server interceptor", + i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethod), Type: otelgrpc.UnaryServer}, + f: HealthCheck(), + want: false, + }, + { + name: "stream server interceptor", + i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethod), Type: otelgrpc.StreamServer}, + f: HealthCheck(), + want: false, + }, + } + + for _, tc := range tcs { + out := tc.f(tc.i) + if tc.want != out { + t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) + } + } +} diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go index b129e377938..7f19058e4c4 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go @@ -70,7 +70,7 @@ func UnaryClientInterceptor(opts ...Option) grpc.UnaryClientInterceptor { Method: method, Type: UnaryClient, } - if cfg.Filter != nil && !cfg.Filter(i) { + if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) { return invoker(ctx, method, req, reply, cc, callOpts...) } @@ -230,7 +230,7 @@ func StreamClientInterceptor(opts ...Option) grpc.StreamClientInterceptor { Method: method, Type: StreamClient, } - if cfg.Filter != nil && !cfg.Filter(i) { + if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) { return streamer(ctx, desc, cc, method, callOpts...) } @@ -285,7 +285,7 @@ func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor { UnaryServerInfo: info, Type: UnaryServer, } - if cfg.Filter != nil && !cfg.Filter(i) { + if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) { return handler(ctx, req) } @@ -411,7 +411,7 @@ func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor { StreamServerInfo: info, Type: StreamServer, } - if cfg.Filter != nil && !cfg.Filter(i) { + if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) { return handler(srv, wrapServerStream(ctx, ss, cfg)) } diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go b/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go index c1c99874696..fad58733fec 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go @@ -27,6 +27,7 @@ type gRPCContext struct { messagesReceived int64 messagesSent int64 metricAttrs []attribute.KeyValue + record bool } type serverHandler struct { @@ -66,6 +67,10 @@ func (h *serverHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont gctx := gRPCContext{ metricAttrs: attrs, + record: true, + } + if h.config.Filter != nil { + gctx.record = h.config.Filter(info) } return context.WithValue(ctx, gRPCContextKey{}, &gctx) } @@ -102,6 +107,10 @@ func (h *clientHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont gctx := gRPCContext{ metricAttrs: attrs, + record: true, + } + if h.config.Filter != nil { + gctx.record = h.config.Filter(info) } return inject(context.WithValue(ctx, gRPCContextKey{}, &gctx), h.config.Propagators) @@ -130,6 +139,9 @@ func (c *config) handleRPC(ctx context.Context, rs stats.RPCStats, isServer bool gctx, _ := ctx.Value(gRPCContextKey{}).(*gRPCContext) if gctx != nil { + if !gctx.record { + return + } metricAttrs = make([]attribute.KeyValue, 0, len(gctx.metricAttrs)+1) metricAttrs = append(metricAttrs, gctx.metricAttrs...) } diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/test/grpc_stats_handler_test.go b/instrumentation/google.golang.org/grpc/otelgrpc/test/grpc_stats_handler_test.go index f34ebdd6e15..72f9de68758 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/test/grpc_stats_handler_test.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/test/grpc_stats_handler_test.go @@ -18,6 +18,7 @@ import ( "google.golang.org/grpc/status" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal/test" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" @@ -33,53 +34,100 @@ import ( ) func TestStatsHandler(t *testing.T) { - clientSR := tracetest.NewSpanRecorder() - clientTP := trace.NewTracerProvider(trace.WithSpanProcessor(clientSR)) - clientMetricReader := metric.NewManualReader() - clientMP := metric.NewMeterProvider(metric.WithReader(clientMetricReader)) - - serverSR := tracetest.NewSpanRecorder() - serverTP := trace.NewTracerProvider(trace.WithSpanProcessor(serverSR)) - serverMetricReader := metric.NewManualReader() - serverMP := metric.NewMeterProvider(metric.WithReader(serverMetricReader)) - - listener, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err, "failed to open port") - client := newGrpcTest(t, listener, - []grpc.DialOption{ - grpc.WithStatsHandler(otelgrpc.NewClientHandler( - otelgrpc.WithTracerProvider(clientTP), - otelgrpc.WithMeterProvider(clientMP), - otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents)), - ), + tests := []struct { + name string + filterSvcName string + expectRecorded bool + }{ + { + name: "Recorded", + filterSvcName: "grpc.testing.TestService", + expectRecorded: true, }, - []grpc.ServerOption{ - grpc.StatsHandler(otelgrpc.NewServerHandler( - otelgrpc.WithTracerProvider(serverTP), - otelgrpc.WithMeterProvider(serverMP), - otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents)), - ), + { + name: "Dropped", + filterSvcName: "grpc.testing.OtherService", + expectRecorded: false, }, - ) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - doCalls(ctx, client) + } - t.Run("ClientSpans", func(t *testing.T) { - checkClientSpans(t, clientSR.Ended(), listener.Addr().String()) - }) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + clientSR := tracetest.NewSpanRecorder() + clientTP := trace.NewTracerProvider(trace.WithSpanProcessor(clientSR)) + clientMetricReader := metric.NewManualReader() + clientMP := metric.NewMeterProvider(metric.WithReader(clientMetricReader)) - t.Run("ClientMetrics", func(t *testing.T) { - checkClientMetrics(t, clientMetricReader) - }) + serverSR := tracetest.NewSpanRecorder() + serverTP := trace.NewTracerProvider(trace.WithSpanProcessor(serverSR)) + serverMetricReader := metric.NewManualReader() + serverMP := metric.NewMeterProvider(metric.WithReader(serverMetricReader)) - t.Run("ServerSpans", func(t *testing.T) { - checkServerSpans(t, serverSR.Ended()) - }) + listener, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err, "failed to open port") + client := newGrpcTest(t, listener, + []grpc.DialOption{ + grpc.WithStatsHandler(otelgrpc.NewClientHandler( + otelgrpc.WithTracerProvider(clientTP), + otelgrpc.WithMeterProvider(clientMP), + otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), + otelgrpc.WithFilter(filters.ServiceName(tt.filterSvcName))), + ), + }, + []grpc.ServerOption{ + grpc.StatsHandler(otelgrpc.NewServerHandler( + otelgrpc.WithTracerProvider(serverTP), + otelgrpc.WithMeterProvider(serverMP), + otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), + otelgrpc.WithFilter(filters.ServiceName(tt.filterSvcName))), + ), + }, + ) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + doCalls(ctx, client) + + if tt.expectRecorded { + t.Run("ClientSpans", func(t *testing.T) { + checkClientSpans(t, clientSR.Ended(), listener.Addr().String()) + }) + + t.Run("ClientMetrics", func(t *testing.T) { + checkClientMetrics(t, clientMetricReader) + }) + + t.Run("ServerSpans", func(t *testing.T) { + checkServerSpans(t, serverSR.Ended()) + }) - t.Run("ServerMetrics", func(t *testing.T) { - checkServerMetrics(t, serverMetricReader) - }) + t.Run("ServerMetrics", func(t *testing.T) { + checkServerMetrics(t, serverMetricReader) + }) + } else { + t.Run("ClientSpans", func(t *testing.T) { + require.Len(t, clientSR.Ended(), 0) + }) + + t.Run("ClientMetrics", func(t *testing.T) { + rm := metricdata.ResourceMetrics{} + err := clientMetricReader.Collect(context.Background(), &rm) + assert.NoError(t, err) + require.Len(t, rm.ScopeMetrics, 0) + }) + + t.Run("ServerSpans", func(t *testing.T) { + require.Len(t, serverSR.Ended(), 0) + }) + + t.Run("ServerMetrics", func(t *testing.T) { + rm := metricdata.ResourceMetrics{} + err := serverMetricReader.Collect(context.Background(), &rm) + assert.NoError(t, err) + require.Len(t, rm.ScopeMetrics, 0) + }) + } + }) + } } func checkClientSpans(t *testing.T, spans []trace.ReadOnlySpan, addr string) {