From 3203812df55ccceb4eff9e5984d325c111df42cc Mon Sep 17 00:00:00 2001 From: ymtdzzz Date: Sun, 3 Mar 2024 15:12:03 +0900 Subject: [PATCH] otelgrpc: Add filter for stats handler --- .../google.golang.org/grpc/otelgrpc/config.go | 41 +- .../grpc/otelgrpc/filters/filters.go | 47 +- .../grpc/otelgrpc/filters/filters_test.go | 234 +++------- .../otelgrpc/filters/interceptor/filters.go | 167 +++++++ .../filters/interceptor/filters_test.go | 430 ++++++++++++++++++ .../grpc/otelgrpc/interceptor.go | 8 +- .../grpc/otelgrpc/stats_handler.go | 12 + 7 files changed, 708 insertions(+), 231 deletions(-) create mode 100644 instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor/filters.go create mode 100644 instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor/filters_test.go diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/config.go b/instrumentation/google.golang.org/grpc/otelgrpc/config.go index 7a26fb5f6d2..80643ecb79b 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,24 @@ const ( GRPCStatusCodeKey = attribute.Key("rpc.grpc.status_code") ) +// InterceptorFilter is a predicate used to determine whether a given request in +// interceptor info should be traced. A InterceptorFilter must return true if +// the request should be traced. +type InterceptorFilter func(*InterceptorInfo) bool + // Filter is a predicate used to determine whether a given request in // interceptor info should be traced. A Filter must return true if // the request should be traced. -type Filter func(*InterceptorInfo) bool +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 @@ -150,17 +158,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..26d703fbbe5 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters.go @@ -7,6 +7,8 @@ import ( "path" "strings" + "google.golang.org/grpc/stats" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" ) @@ -19,20 +21,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 +37,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 +50,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 +68,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 +76,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 +85,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 +95,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 +112,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..9dee9149dfb --- /dev/null +++ b/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor/filters.go @@ -0,0 +1,167 @@ +// 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 ( + "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..23fb9db493a --- /dev/null +++ b/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor/filters_test.go @@ -0,0 +1,430 @@ +// 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 + 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 a4fa5357a56..8ce2eabbff7 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..69e846e9f5e 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) @@ -132,6 +141,9 @@ func (c *config) handleRPC(ctx context.Context, rs stats.RPCStats, isServer bool if gctx != nil { metricAttrs = make([]attribute.KeyValue, 0, len(gctx.metricAttrs)+1) metricAttrs = append(metricAttrs, gctx.metricAttrs...) + if !gctx.record { + return + } } switch rs := rs.(type) {