Repository: open-telemetry/opentelemetry-go-contrib
Pull request #2572 (2022-07-13 08:18:12)

Add gRPC Filter
After the discussion in #2571 and looking up the whole discussion in #512, I changed the direction just to focus on solving the issue #355 and other possible demand to filter the request to trace.

Most of the implementation are from #512. I tried to follow the package structure to match [`otelhttp.Filter`](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/net/http/otelhttp/common.go) but because of the difference between `http.Request` and the objects passed to four types of interceptors, some clumsy structs/functions remain to the surface.

codecov[bot]: # [Codecov](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2572?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) Report
> Merging [#2572](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2572?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) (760439f) into [main](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/commit/efbcbfcb06541ef690398898f5d281068b92581b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) (efbcbfc) will **decrease** coverage by `0.0%`.
> The diff coverage is `71.3%`.

> :exclamation: Current head 760439f differs from pull request most recent head c164bd5. Consider uploading reports for the commit c164bd5 to get more accurate results

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

```diff
@@           Coverage Diff           @@
##            main   #2572     +/-   ##
=======================================
- Coverage   74.3%   74.2%   -0.1%     
=======================================
  Files        144     146      +2     
  Lines       6569    6728    +159     
=======================================
+ Hits        4883    4995    +112     
- Misses      1543    1586     +43     
- Partials     143     147      +4     
```


| [Impacted Files](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2572?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | Coverage Δ | |
|---|---|---|
| [...ation/google.golang.org/grpc/otelgrpc/grpctrace.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2572/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-aW5zdHJ1bWVudGF0aW9uL2dvb2dsZS5nb2xhbmcub3JnL2dycGMvb3RlbGdycGMvZ3JwY3RyYWNlLmdv) | `50.9% <0.0%> (-6.6%)` | :arrow_down: |
| [...ion/google.golang.org/grpc/otelgrpc/interceptor.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2572/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-aW5zdHJ1bWVudGF0aW9uL2dvb2dsZS5nb2xhbmcub3JnL2dycGMvb3RlbGdycGMvaW50ZXJjZXB0b3IuZ28=) | `83.6% <50.0%> (-3.0%)` | :arrow_down: |
| [...google.golang.org/grpc/otelgrpc/filters/filters.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2572/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-aW5zdHJ1bWVudGF0aW9uL2dvb2dsZS5nb2xhbmcub3JnL2dycGMvb3RlbGdycGMvZmlsdGVycy9maWx0ZXJzLmdv) | `73.6% <73.6%> (ø)` | |
| [...olang.org/grpc/otelgrpc/filters/interceptorinfo.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2572/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-aW5zdHJ1bWVudGF0aW9uL2dvb2dsZS5nb2xhbmcub3JnL2dycGMvb3RlbGdycGMvZmlsdGVycy9pbnRlcmNlcHRvcmluZm8uZ28=) | `100.0% <100.0%> (ø)` | |

ymotongpoo: @dashpole thanks for the comment.

@Aneurysm9 does it make sense to you to have everything for filter under `otelgrpc` and `otelgrpc/internal` to eliminate unnecessary public functions and struct? I'm asking you because you submit `otelhttp/filters` and also suggested the common object for filters in #512.
ymotongpoo: @dashpole I moved everything under `otelgrpc` and now unnecessary public members are eliminated. Could you approve this?
ymotongpoo: @dashpole Thank you David for the review and approval!

To maintainers: should I change anything else?
ymotongpoo: @pellared @Aneurysm9 thank you both for the comments and suggestions. I updated the branch and it should look better now.
ymotongpoo: @Aneurysm9 @dashpole I'm trying to move filters to the subsidiary `filters` package and keep `newXXXInterceptorInfo` private in `otelgrpc`, but that requires either of the followings to achieve the restructuring:

1. to expose all fields of `filters.InterceptorInfo` to create the object in `newXXXInterceptorInfo`
2. to prepare a public constructor method to create the `filters.InterceptorInfo` object

Either way make things uglier, and either of them has pros/cons.

* Option 1:
  * Implementation is straight forward, just replacing private field names with public ones
  * It unnecessary exposes fields
* Option 2:
  * We can keep the fields of `filter.InterceptorInfo` private
  * The constructor requires the values of all fields as arguments regardless of actual fields used (eg. for StreamServer, it only uses `ssinfo` and `typ`, so they need to explicitly fill the other arguments with zero values when calling the constructor.) 
  * The original motivation we keep `newXXXInterceptorInfo` functions private is not to expose the unnecessary functions for users, but the intention will be a bit ruined.

All filters and `InterceptorInfo` struct need to be in the same package, i.e. `filters` because it causes cyclic dependency if we keep `InterceptorInfo` in `otelgrpc`, so we need to figure out some ways to construct `filters.InterceptorInfo` objects out of the package.

ymotongpoo: @Aneurysm9 Kindly ping for your comments on the visibility of the fields `IntercepterInfo`, as I can't move forward without your comments. It's been 3 weeks since the beginning of the thread and it would be great if I can make this PR get ready for the merge.
ymotongpoo: @MrAlias thank you for arranging this PR as the milestone. Could you help accelerate the review? I'm ready to change the code but I can't without the review from @Aneurysm9 but I have no idea how I can get help but for waiting.
ymotongpoo: @Aneurysm9 could you reply to this comment so that I can move forward to the acceptable change?
https://github.com/open-telemetry/opentelemetry-go-contrib/pull/2572#issuecomment-1197595873
Aneurysm9: Apologies for the delay, I was on vacation.

> All filters and `InterceptorInfo` struct need to be in the same package, i.e. `filters` because it causes cyclic dependency if we keep `InterceptorInfo` in `otelgrpc`, so we need to figure out some ways to construct `filters.InterceptorInfo` objects out of the package.

Not necessarily.  There could be a third package that contains `InterceptorInfo`.  This would be similar to the `otelhttp` instrumentation that uses the `http.Request` as an argument to its filters.  

It might also be possible to, as @dashpole suggested, use `otelgrpc.Filter` as the type and `filter.*` implement that type.  I think that would then only have `otelgrpc` depending on itself, `filter` depending on `otelgrpc`, and then user code depending on both, as required.
ymotongpoo: @Aneurysm9 I appreciate your comment. I hope you had a great vacation.

> Not necessarily. There could be a third package that contains `InterceptorInfo`. This would be similar to the `otelhttp` instrumentation that uses the `http.Request` as an argument to its filters.

The reason `http.Request` works in `otelhttp` is because it exposes its fields. If it's okay for `IntercepterInfo` to expose its fields as well, I'm +1 to the decision.

Also per declaring `otelgrpc.Filter` type, `filter.*` is referring to `interceptorInfo`'s fields as of current implementation, so from this point of view, `InterceptorInfo`'s fields needs to be public or accessible from public methods.

Given, does it sound good to expose the fields (I think preparing public methods is better)?
ymotongpoo: @Aneurysm9 I moved all filters under `filters` package. Those filters are in `otelgrpc.Filter` type and uses `otelgrpc.InterceptorInfo`. Also, because `InterceptorInfo` got smaller than what it was, I removed `newXXXX` constructors and directly instantiated `InterceptorInfo`, which are enabled by exposing its fields. This expose was required for `fitler_test.go` as well.
ymotongpoo: @Aneurysm9 Kindly ping because it's been a week since your last comment and I updated the code regarding it.
petenorth: Open Telemetry in Go for grpc is unusable without this PR for our organization.
ymotongpoo: @Aneurysm9 @MadVikingGod @MrAlias hi, all maintainers, let me know how I can proceed from here. It's been two weeks since @Aneurysm9's last comment and I'm totally stalling.

For interface simplicity, if we don't think about abstraction for future, we can simply use method name as common input for `otelgrpc.Filter` and eliminate `InterceptorInfo` itself, as commented in https://github.com/open-telemetry/opentelemetry-go-contrib/pull/512#discussion_r602181665.

Of course, if we are to use `context.Context` for more filters, we can add it in the current implementation of `InterceptorInfo`.

```go
type InterceptorInfo struct {
	Ctx              context.Context
	Method           string
	UnaryServerInfo  *grpc.UnaryServerInfo
	StreamServerInfo *grpc.StreamServerInfo
	Typ              InterceptorType
}
```

Another option is just to keep method name and context when we instantiate `InterceptorInfo`, to eliminate `InterceptorType` switch. This enables `InterceptorInfo` to be simpler:

```go
type InterceptorInfo struct {
    Ctx    context.Context
    // Method should be either of the followings:
    // - method passed to unary/stream client interceptor function
    // - info.FullMethodName, where info is Unary/StreamServerInfo passed to server interceptor function
    Method string
}
```
ymotongpoo: @Aneurysm9 Thank you for the approval! Yes, I'm happy to create a follow-up PR for better implementation :)

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4993e94b5..1f1ddbdab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
 
 ### Added
 
+- Add gRPC trace `Filter` to the `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` package to provide the way to filter the traces automatically generated in interceptors. (#2572)
 - The `TextMapPropagator` function to `go.opentelemetry.io/contrib/propagators/autoprop`.
   This function is used to return a composite `TextMapPropagator` from registered names (instead of having to specify with an environment variable). (#2593)
 
diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters.go b/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters.go
new file mode 100644
index 000000000..131d512e6
--- /dev/null
+++ b/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters.go
@@ -0,0 +1,158 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package filters // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters"
+
+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.Typ {
+	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.
+func Any(fs ...otelgrpc.Filter) otelgrpc.Filter {
+	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.
+func All(fs ...otelgrpc.Filter) otelgrpc.Filter {
+	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.
+func None(fs ...otelgrpc.Filter) otelgrpc.Filter {
+	return Not(Any(fs...))
+}
+
+// Not provides a convenience mechanism for inverting a Filter.
+func Not(f otelgrpc.Filter) otelgrpc.Filter {
+	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.
+func MethodName(n string) otelgrpc.Filter {
+	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.
+func MethodPrefix(pre string) otelgrpc.Filter {
+	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.
+func FullMethodName(n string) otelgrpc.Filter {
+	return func(i *otelgrpc.InterceptorInfo) bool {
+		var fm string
+		switch i.Typ {
+		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.
+func ServiceName(s string) otelgrpc.Filter {
+	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.
+func ServicePrefix(pre string) otelgrpc.Filter {
+	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
+// is not health check defined by gRPC Health Checking Protocol.
+// https://github.com/grpc/grpc/blob/master/doc/health-checking.md
+func HealthCheck() otelgrpc.Filter {
+	return ServicePrefix("grpc.health.v1.Health")
+}
diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/grpctrace.go b/instrumentation/google.golang.org/grpc/otelgrpc/grpctrace.go
index 5ad26dadc..a874d39e1 100644
--- a/instrumentation/google.golang.org/grpc/otelgrpc/grpctrace.go
+++ b/instrumentation/google.golang.org/grpc/otelgrpc/grpctrace.go
@@ -33,8 +33,14 @@ 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
+// the request should be traced.
+type Filter func(*InterceptorInfo) bool
+
 // config is a group of options for this instrumentation.
 type config struct {
+	Filter         Filter
 	Propagators    propagation.TextMapPropagator
 	TracerProvider trace.TracerProvider
 }
@@ -78,6 +84,21 @@ func (o tracerProviderOption) apply(c *config) {
 	}
 }
 
+// WithInterceptorFilter returns an Option to use the request filter.
+func WithInterceptorFilter(f Filter) Option {
+	return interceptorFilterOption{f: f}
+}
+
+type interceptorFilterOption struct {
+	f Filter
+}
+
+func (o interceptorFilterOption) apply(c *config) {
+	if o.f != nil {
+		c.Filter = o.f
+	}
+}
+
 // WithTracerProvider returns an Option to use the TracerProvider when
 // creating a Tracer.
 func WithTracerProvider(tp trace.TracerProvider) Option {
diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go
index 207c4d43c..06a969ddd 100644
--- a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go
+++ b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go
@@ -73,10 +73,19 @@ func UnaryClientInterceptor(opts ...Option) grpc.UnaryClientInterceptor {
 		invoker grpc.UnaryInvoker,
 		callOpts ...grpc.CallOption,
 	) error {
+		cfg := newConfig(opts)
+		i := &InterceptorInfo{
+			Method: method,
+			Typ:    UnaryClient,
+		}
+		if cfg.Filter != nil && !cfg.Filter(i) {
+			return invoker(ctx, method, req, reply, cc, callOpts...)
+		}
+
 		requestMetadata, _ := metadata.FromOutgoingContext(ctx)
 		metadataCopy := requestMetadata.Copy()
 
-		tracer := newConfig(opts).TracerProvider.Tracer(
+		tracer := cfg.TracerProvider.Tracer(
 			instrumentationName,
 			trace.WithInstrumentationVersion(SemVersion()),
 		)
@@ -243,10 +252,19 @@ func StreamClientInterceptor(opts ...Option) grpc.StreamClientInterceptor {
 		streamer grpc.Streamer,
 		callOpts ...grpc.CallOption,
 	) (grpc.ClientStream, error) {
+		cfg := newConfig(opts)
+		i := &InterceptorInfo{
+			Method: method,
+			Typ:    StreamClient,
+		}
+		if cfg.Filter != nil && !cfg.Filter(i) {
+			return streamer(ctx, desc, cc, method, callOpts...)
+		}
+
 		requestMetadata, _ := metadata.FromOutgoingContext(ctx)
 		metadataCopy := requestMetadata.Copy()
 
-		tracer := newConfig(opts).TracerProvider.Tracer(
+		tracer := cfg.TracerProvider.Tracer(
 			instrumentationName,
 			trace.WithInstrumentationVersion(SemVersion()),
 		)
@@ -300,13 +318,22 @@ func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor {
 		info *grpc.UnaryServerInfo,
 		handler grpc.UnaryHandler,
 	) (interface{}, error) {
+		cfg := newConfig(opts)
+		i := &InterceptorInfo{
+			UnaryServerInfo: info,
+			Typ:             UnaryServer,
+		}
+		if cfg.Filter != nil && !cfg.Filter(i) {
+			return handler(ctx, req)
+		}
+
 		requestMetadata, _ := metadata.FromIncomingContext(ctx)
 		metadataCopy := requestMetadata.Copy()
 
 		bags, spanCtx := Extract(ctx, &metadataCopy, opts...)
 		ctx = baggage.ContextWithBaggage(ctx, bags)
 
-		tracer := newConfig(opts).TracerProvider.Tracer(
+		tracer := cfg.TracerProvider.Tracer(
 			instrumentationName,
 			trace.WithInstrumentationVersion(SemVersion()),
 		)
@@ -388,6 +415,14 @@ func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor {
 		handler grpc.StreamHandler,
 	) error {
 		ctx := ss.Context()
+		cfg := newConfig(opts)
+		i := &InterceptorInfo{
+			StreamServerInfo: info,
+			Typ:              StreamServer,
+		}
+		if cfg.Filter != nil && !cfg.Filter(i) {
+			return handler(srv, wrapServerStream(ctx, ss))
+		}
 
 		requestMetadata, _ := metadata.FromIncomingContext(ctx)
 		metadataCopy := requestMetadata.Copy()
@@ -395,7 +430,7 @@ func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor {
 		bags, spanCtx := Extract(ctx, &metadataCopy, opts...)
 		ctx = baggage.ContextWithBaggage(ctx, bags)
 
-		tracer := newConfig(opts).TracerProvider.Tracer(
+		tracer := cfg.TracerProvider.Tracer(
 			instrumentationName,
 			trace.WithInstrumentationVersion(SemVersion()),
 		)
diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/interceptorinfo.go b/instrumentation/google.golang.org/grpc/otelgrpc/interceptorinfo.go
new file mode 100644
index 000000000..0d9c4d345
--- /dev/null
+++ b/instrumentation/google.golang.org/grpc/otelgrpc/interceptorinfo.go
@@ -0,0 +1,50 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
+
+import (
+	"google.golang.org/grpc"
+)
+
+// InterceptorType is the flag to define which gRPC interceptor
+// the InterceptorInfo object is.
+type InterceptorType uint8
+
+const (
+	// UndefinedInterceptor is the type for the interceptor information that is not
+	// well initialized or categorized to other types.
+	UndefinedInterceptor InterceptorType = iota
+	// UnaryClient is the type for grpc.UnaryClient interceptor.
+	UnaryClient
+	// StreamClient is the type for grpc.StreamClient interceptor.
+	StreamClient
+	// UnaryServer is the type for grpc.UnaryServer interceptor.
+	UnaryServer
+	// StreamServer is the type for grpc.StreamServer interceptor.
+	StreamServer
+)
+
+// InterceptorInfo is the union of some arguments to four types of
+// gRPC interceptors.
+type InterceptorInfo struct {
+	// Method is method name registered to UnaryClient and StreamClient
+	Method string
+	// UnaryServerInfo is the metadata for UnaryServer
+	UnaryServerInfo *grpc.UnaryServerInfo
+	// StreamServerInfo if the metadata for StreamServer
+	StreamServerInfo *grpc.StreamServerInfo
+	// Typ is the type for interceptor
+	Typ InterceptorType
+}


Test patch:
diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters_test.go b/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters_test.go
new file mode 100644
index 000000000..0d07bf642
--- /dev/null
+++ b/instrumentation/google.golang.org/grpc/otelgrpc/filters/filters_test.go
@@ -0,0 +1,441 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package filters // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters"
+
+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.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 TestMethodName(t *testing.T) {
+	const dummyFullMethodName = "/example.HelloService/Hello"
+	tcs := []testCase{
+		{
+			name: "unary client interceptor",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: otelgrpc.UnaryClient},
+			f:    MethodName("Hello"),
+			want: true,
+		},
+		{
+			name: "stream client interceptor",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: otelgrpc.StreamClient},
+			f:    MethodName("Hello"),
+			want: true,
+		},
+		{
+			name: "unary server interceptor",
+			i:    &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Typ: otelgrpc.UnaryServer},
+			f:    MethodName("Hello"),
+			want: true,
+		},
+		{
+			name: "stream server interceptor",
+			i:    &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Typ: otelgrpc.StreamServer},
+			f:    MethodName("Hello"),
+			want: true,
+		},
+		{
+			name: "unary client interceptor fail",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: 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, Typ: otelgrpc.UnaryClient},
+			f:    MethodPrefix("Foobar"),
+			want: true,
+		},
+		{
+			name: "stream client interceptor",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: otelgrpc.StreamClient},
+			f:    MethodPrefix("Foobar"),
+			want: true,
+		},
+		{
+			name: "unary server interceptor",
+			i:    &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Typ: otelgrpc.UnaryServer},
+			f:    MethodPrefix("Foobar"),
+			want: true,
+		},
+		{
+			name: "stream server interceptor",
+			i:    &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Typ: otelgrpc.StreamServer},
+			f:    MethodPrefix("Foobar"),
+			want: true,
+		},
+		{
+			name: "unary client interceptor fail",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: 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, Typ: otelgrpc.UnaryClient},
+			f:    FullMethodName(dummyFullMethodName),
+			want: true,
+		},
+		{
+			name: "stream client interceptor",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: otelgrpc.StreamClient},
+			f:    FullMethodName(dummyFullMethodName),
+			want: true,
+		},
+		{
+			name: "unary server interceptor",
+			i:    &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Typ: otelgrpc.UnaryServer},
+			f:    FullMethodName(dummyFullMethodName),
+			want: true,
+		},
+		{
+			name: "stream server interceptor",
+			i:    &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Typ: otelgrpc.StreamServer},
+			f:    FullMethodName(dummyFullMethodName),
+			want: true,
+		},
+		{
+			name: "unary client interceptor fail",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: 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, Typ: otelgrpc.UnaryClient},
+			f:    ServiceName("example.HelloService"),
+			want: true,
+		},
+		{
+			name: "stream client interceptor",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: otelgrpc.StreamClient},
+			f:    ServiceName("example.HelloService"),
+			want: true,
+		},
+		{
+			name: "unary server interceptor",
+			i:    &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Typ: otelgrpc.UnaryServer},
+			f:    ServiceName("example.HelloService"),
+			want: true,
+		},
+		{
+			name: "stream server interceptor",
+			i:    &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Typ: otelgrpc.StreamServer},
+			f:    ServiceName("example.HelloService"),
+			want: true,
+		},
+		{
+			name: "unary client interceptor fail",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: 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, Typ: otelgrpc.UnaryClient},
+			f:    ServicePrefix("example"),
+			want: true,
+		},
+		{
+			name: "stream client interceptor",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: otelgrpc.StreamClient},
+			f:    ServicePrefix("example"),
+			want: true,
+		},
+		{
+			name: "unary server interceptor",
+			i:    &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Typ: otelgrpc.UnaryServer},
+			f:    ServicePrefix("example"),
+			want: true,
+		},
+		{
+			name: "stream server interceptor",
+			i:    &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Typ: otelgrpc.StreamServer},
+			f:    ServicePrefix("example"),
+			want: true,
+		},
+		{
+			name: "unary client interceptor fail",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: 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, Typ: otelgrpc.UnaryClient},
+			f:    Any(MethodName("FoobarHello"), MethodPrefix("Foobar")),
+			want: true,
+		},
+		{
+			name: "unary client interceptor false && true",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: otelgrpc.UnaryClient},
+			f:    Any(MethodName("Hello"), MethodPrefix("Foobar")),
+			want: true,
+		},
+		{
+			name: "unary client interceptor false && false",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: 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, Typ: otelgrpc.UnaryClient},
+			f:    All(MethodName("FoobarHello"), MethodPrefix("Foobar")),
+			want: true,
+		},
+		{
+			name: "unary client interceptor true && false",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: otelgrpc.UnaryClient},
+			f:    All(MethodName("FoobarHello"), MethodPrefix("Barfoo")),
+			want: false,
+		},
+		{
+			name: "unary client interceptor false && false",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: 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, Typ: otelgrpc.UnaryClient},
+			f:    None(MethodName("FoobarHello"), MethodPrefix("Foobar")),
+			want: false,
+		},
+		{
+			name: "unary client interceptor true && false",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: otelgrpc.UnaryClient},
+			f:    None(MethodName("FoobarHello"), MethodPrefix("Barfoo")),
+			want: false,
+		},
+		{
+			name: "unary client interceptor false && false",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: 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, Typ: otelgrpc.UnaryClient},
+			f:    Not(MethodName("FoobarHello")),
+			want: false,
+		},
+		{
+			name: "method prefix not",
+			i:    &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Typ: otelgrpc.UnaryServer},
+			f:    Not(MethodPrefix("FoobarHello")),
+			want: false,
+		},
+		{
+			name: "unary client interceptor not all(true && true)",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Typ: 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, Typ: otelgrpc.UnaryClient},
+			f:    HealthCheck(),
+			want: true,
+		},
+		{
+			name: "stream client interceptor healthcheck",
+			i:    &otelgrpc.InterceptorInfo{Method: healthCheck, Typ: otelgrpc.StreamClient},
+			f:    HealthCheck(),
+			want: true,
+		},
+		{
+			name: "unary server interceptor healthcheck",
+			i:    &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(healthCheck), Typ: otelgrpc.UnaryServer},
+			f:    HealthCheck(),
+			want: true,
+		},
+		{
+			name: "stream server interceptor healthcheck",
+			i:    &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(healthCheck), Typ: otelgrpc.StreamServer},
+			f:    HealthCheck(),
+			want: true,
+		},
+		{
+			name: "unary client interceptor",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethod, Typ: otelgrpc.UnaryClient},
+			f:    HealthCheck(),
+			want: false,
+		},
+		{
+			name: "stream client interceptor",
+			i:    &otelgrpc.InterceptorInfo{Method: dummyFullMethod, Typ: otelgrpc.StreamClient},
+			f:    HealthCheck(),
+			want: false,
+		},
+		{
+			name: "unary server interceptor",
+			i:    &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethod), Typ: otelgrpc.UnaryServer},
+			f:    HealthCheck(),
+			want: false,
+		},
+		{
+			name: "stream server interceptor",
+			i:    &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethod), Typ: 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)
+		}
+	}
+}