diff --git a/README.md b/README.md
index e52a240..c89b706 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,7 @@ Interceptor configuration functions \(AddUnaryServerInterceptor, SetFilterFunc,
## Index
+- [Constants](<#constants>)
- [Variables](<#variables>)
- [func AddStreamClientInterceptor\(ctx context.Context, i ...grpc.StreamClientInterceptor\)](<#AddStreamClientInterceptor>)
- [func AddStreamServerInterceptor\(ctx context.Context, i ...grpc.StreamServerInterceptor\)](<#AddStreamServerInterceptor>)
@@ -44,14 +45,24 @@ Interceptor configuration functions \(AddUnaryServerInterceptor, SetFilterFunc,
- [func ResponseTimeLoggingStreamInterceptor\(\) grpc.StreamServerInterceptor](<#ResponseTimeLoggingStreamInterceptor>)
- [func ServerErrorInterceptor\(\) grpc.UnaryServerInterceptor](<#ServerErrorInterceptor>)
- [func ServerErrorStreamInterceptor\(\) grpc.StreamServerInterceptor](<#ServerErrorStreamInterceptor>)
+- [func SetClientMetricsOptions\(opts ...grpcprom.ClientMetricsOption\)](<#SetClientMetricsOptions>)
- [func SetFilterFunc\(ctx context.Context, ff FilterFunc\)](<#SetFilterFunc>)
- [func SetResponseTimeLogLevel\(ctx context.Context, level loggers.Level\)](<#SetResponseTimeLogLevel>)
+- [func SetServerMetricsOptions\(opts ...grpcprom.ServerMetricsOption\)](<#SetServerMetricsOptions>)
- [func TraceIdInterceptor\(\) grpc.UnaryServerInterceptor](<#TraceIdInterceptor>)
- [func UseColdBrewClientInterceptors\(ctx context.Context, flag bool\)](<#UseColdBrewClientInterceptors>)
- [func UseColdBrewServerInterceptors\(ctx context.Context, flag bool\)](<#UseColdBrewServerInterceptors>)
- [type FilterFunc](<#FilterFunc>)
+## Constants
+
+SupportPackageIsVersion1 is a compile\-time assertion constant. Downstream packages \(e.g. core\) reference this constant to enforce version compatibility. When interceptors makes a breaking change, export a new constant and remove this one to force coordinated updates.
+
+```go
+const SupportPackageIsVersion1 = true
+```
+
## Variables
@@ -64,7 +75,7 @@ var (
```
-## func [AddStreamClientInterceptor]()
+## func [AddStreamClientInterceptor]()
```go
func AddStreamClientInterceptor(ctx context.Context, i ...grpc.StreamClientInterceptor)
@@ -73,7 +84,7 @@ func AddStreamClientInterceptor(ctx context.Context, i ...grpc.StreamClientInter
AddStreamClientInterceptor adds a client stream interceptor to default client stream interceptors. Must be called during initialization, before any RPCs are made. Not safe for concurrent use.
-## func [AddStreamServerInterceptor]()
+## func [AddStreamServerInterceptor]()
```go
func AddStreamServerInterceptor(ctx context.Context, i ...grpc.StreamServerInterceptor)
@@ -82,7 +93,7 @@ func AddStreamServerInterceptor(ctx context.Context, i ...grpc.StreamServerInter
AddStreamServerInterceptor adds a server interceptor to default server interceptors. Must be called during initialization, before the server starts. Not safe for concurrent use.
-## func [AddUnaryClientInterceptor]()
+## func [AddUnaryClientInterceptor]()
```go
func AddUnaryClientInterceptor(ctx context.Context, i ...grpc.UnaryClientInterceptor)
@@ -91,7 +102,7 @@ func AddUnaryClientInterceptor(ctx context.Context, i ...grpc.UnaryClientInterce
AddUnaryClientInterceptor adds a client interceptor to default client interceptors. Must be called during initialization, before any RPCs are made. Not safe for concurrent use.
-## func [AddUnaryServerInterceptor]()
+## func [AddUnaryServerInterceptor]()
```go
func AddUnaryServerInterceptor(ctx context.Context, i ...grpc.UnaryServerInterceptor)
@@ -100,7 +111,7 @@ func AddUnaryServerInterceptor(ctx context.Context, i ...grpc.UnaryServerInterce
AddUnaryServerInterceptor adds a server interceptor to default server interceptors. Must be called during initialization, before the server starts. Not safe for concurrent use.
-## func [DebugLoggingInterceptor]()
+## func [DebugLoggingInterceptor]()
```go
func DebugLoggingInterceptor() grpc.UnaryServerInterceptor
@@ -109,7 +120,7 @@ func DebugLoggingInterceptor() grpc.UnaryServerInterceptor
DebugLoggingInterceptor is the interceptor that logs all request/response from a handler
-## func [DefaultClientInterceptor]()
+## func [DefaultClientInterceptor]()
```go
func DefaultClientInterceptor(defaultOpts ...interface{}) grpc.UnaryClientInterceptor
@@ -118,7 +129,7 @@ func DefaultClientInterceptor(defaultOpts ...interface{}) grpc.UnaryClientInterc
DefaultClientInterceptor are the set of default interceptors that should be applied to all client calls
-## func [DefaultClientInterceptors]()
+## func [DefaultClientInterceptors]()
```go
func DefaultClientInterceptors(defaultOpts ...interface{}) []grpc.UnaryClientInterceptor
@@ -127,7 +138,7 @@ func DefaultClientInterceptors(defaultOpts ...interface{}) []grpc.UnaryClientInt
DefaultClientInterceptors are the set of default interceptors that should be applied to all client calls
-## func [DefaultClientStreamInterceptor]()
+## func [DefaultClientStreamInterceptor]()
```go
func DefaultClientStreamInterceptor(defaultOpts ...interface{}) grpc.StreamClientInterceptor
@@ -136,7 +147,7 @@ func DefaultClientStreamInterceptor(defaultOpts ...interface{}) grpc.StreamClien
DefaultClientStreamInterceptor are the set of default interceptors that should be applied to all stream client calls
-## func [DefaultClientStreamInterceptors]()
+## func [DefaultClientStreamInterceptors]()
```go
func DefaultClientStreamInterceptors(defaultOpts ...interface{}) []grpc.StreamClientInterceptor
@@ -145,7 +156,7 @@ func DefaultClientStreamInterceptors(defaultOpts ...interface{}) []grpc.StreamCl
DefaultClientStreamInterceptors are the set of default interceptors that should be applied to all stream client calls
-## func [DefaultInterceptors]()
+## func [DefaultInterceptors]()
```go
func DefaultInterceptors() []grpc.UnaryServerInterceptor
@@ -154,7 +165,7 @@ func DefaultInterceptors() []grpc.UnaryServerInterceptor
DefaultInterceptors are the set of default interceptors that are applied to all coldbrew methods
-## func [DefaultStreamInterceptors]()
+## func [DefaultStreamInterceptors]()
```go
func DefaultStreamInterceptors() []grpc.StreamServerInterceptor
@@ -163,7 +174,7 @@ func DefaultStreamInterceptors() []grpc.StreamServerInterceptor
DefaultStreamInterceptors are the set of default interceptors that should be applied to all coldbrew streams
-## func [DoHTTPtoGRPC]()
+## func [DoHTTPtoGRPC]()
```go
func DoHTTPtoGRPC(ctx context.Context, svr interface{}, handler func(ctx context.Context, req interface{}) (interface{}, error), in interface{}) (interface{}, error)
@@ -172,7 +183,7 @@ func DoHTTPtoGRPC(ctx context.Context, svr interface{}, handler func(ctx context
-## func [FilterMethodsFunc]()
+## func [FilterMethodsFunc]()
```go
func FilterMethodsFunc(ctx context.Context, fullMethodName string) bool
@@ -181,7 +192,7 @@ func FilterMethodsFunc(ctx context.Context, fullMethodName string) bool
FilterMethodsFunc is the default implementation of Filter function
-## func [GRPCClientInterceptor]()
+## func [GRPCClientInterceptor]()
```go
func GRPCClientInterceptor(options ...grpc_opentracing.Option) grpc.UnaryClientInterceptor
@@ -190,7 +201,7 @@ func GRPCClientInterceptor(options ...grpc_opentracing.Option) grpc.UnaryClientI
GRPCClientInterceptor is the interceptor that intercepts all cleint requests and adds tracing info to them
-## func [HystrixClientInterceptor]()
+## func [HystrixClientInterceptor]()
```go
func HystrixClientInterceptor(defaultOpts ...grpc.CallOption) grpc.UnaryClientInterceptor
@@ -203,7 +214,7 @@ Note: This interceptor wraps github.com/afex/hystrix\-go which has been unmainta
The interceptor applies provided default and per\-call client options to configure Hystrix behavior \(for example the command name, disabled flag, excluded errors, and excluded gRPC status codes\). If Hystrix is disabled via options, the RPC is invoked directly. If the underlying RPC returns an error that matches any configured excluded error or whose gRPC status code matches any configured excluded code, Hystrix fallback is skipped and the RPC error is returned. Panics raised during the RPC invocation are captured and reported to the notifier before being converted into an error. If the RPC itself returns an error, that error is returned; otherwise any error produced by Hystrix is returned.
-## func [NRHttpTracer]()
+## func [NRHttpTracer]()
```go
func NRHttpTracer(pattern string, h http.HandlerFunc) (string, http.HandlerFunc)
@@ -212,7 +223,7 @@ func NRHttpTracer(pattern string, h http.HandlerFunc) (string, http.HandlerFunc)
NRHttpTracer adds newrelic tracing to this http function
-## func [NewRelicClientInterceptor]()
+## func [NewRelicClientInterceptor]()
```go
func NewRelicClientInterceptor() grpc.UnaryClientInterceptor
@@ -221,7 +232,7 @@ func NewRelicClientInterceptor() grpc.UnaryClientInterceptor
NewRelicClientInterceptor intercepts all client actions and reports them to newrelic
-## func [NewRelicInterceptor]()
+## func [NewRelicInterceptor]()
```go
func NewRelicInterceptor() grpc.UnaryServerInterceptor
@@ -230,7 +241,7 @@ func NewRelicInterceptor() grpc.UnaryServerInterceptor
NewRelicInterceptor intercepts all server actions and reports them to newrelic
-## func [OptionsInterceptor]()
+## func [OptionsInterceptor]()
```go
func OptionsInterceptor() grpc.UnaryServerInterceptor
@@ -239,7 +250,7 @@ func OptionsInterceptor() grpc.UnaryServerInterceptor
-## func [PanicRecoveryInterceptor]()
+## func [PanicRecoveryInterceptor]()
```go
func PanicRecoveryInterceptor() grpc.UnaryServerInterceptor
@@ -248,7 +259,7 @@ func PanicRecoveryInterceptor() grpc.UnaryServerInterceptor
-## func [ResponseTimeLoggingInterceptor]()
+## func [ResponseTimeLoggingInterceptor]()
```go
func ResponseTimeLoggingInterceptor(ff FilterFunc) grpc.UnaryServerInterceptor
@@ -257,7 +268,7 @@ func ResponseTimeLoggingInterceptor(ff FilterFunc) grpc.UnaryServerInterceptor
ResponseTimeLoggingInterceptor logs response time for each request on server
-## func [ResponseTimeLoggingStreamInterceptor]()
+## func [ResponseTimeLoggingStreamInterceptor]()
```go
func ResponseTimeLoggingStreamInterceptor() grpc.StreamServerInterceptor
@@ -266,7 +277,7 @@ func ResponseTimeLoggingStreamInterceptor() grpc.StreamServerInterceptor
ResponseTimeLoggingStreamInterceptor logs response time for stream RPCs.
-## func [ServerErrorInterceptor]()
+## func [ServerErrorInterceptor]()
```go
func ServerErrorInterceptor() grpc.UnaryServerInterceptor
@@ -275,7 +286,7 @@ func ServerErrorInterceptor() grpc.UnaryServerInterceptor
ServerErrorInterceptor intercepts all server actions and reports them to error notifier
-## func [ServerErrorStreamInterceptor]()
+## func [ServerErrorStreamInterceptor]()
```go
func ServerErrorStreamInterceptor() grpc.StreamServerInterceptor
@@ -283,8 +294,17 @@ func ServerErrorStreamInterceptor() grpc.StreamServerInterceptor
ServerErrorStreamInterceptor intercepts server errors for stream RPCs and reports them to the error notifier.
+
+## func [SetClientMetricsOptions]()
+
+```go
+func SetClientMetricsOptions(opts ...grpcprom.ClientMetricsOption)
+```
+
+SetClientMetricsOptions appends gRPC client metrics options. Must be called during initialization, before the server starts. Not safe for concurrent use.
+
-## func [SetFilterFunc]()
+## func [SetFilterFunc]()
```go
func SetFilterFunc(ctx context.Context, ff FilterFunc)
@@ -293,7 +313,7 @@ func SetFilterFunc(ctx context.Context, ff FilterFunc)
SetFilterFunc sets the default filter function to be used by interceptors. Must be called during initialization, before the server starts. Not safe for concurrent use.
-## func [SetResponseTimeLogLevel]()
+## func [SetResponseTimeLogLevel]()
```go
func SetResponseTimeLogLevel(ctx context.Context, level loggers.Level)
@@ -301,8 +321,17 @@ func SetResponseTimeLogLevel(ctx context.Context, level loggers.Level)
SetResponseTimeLogLevel sets the log level for response time logging. Default is InfoLevel. Must be called during initialization, before the server starts. Not safe for concurrent use.
+
+## func [SetServerMetricsOptions]()
+
+```go
+func SetServerMetricsOptions(opts ...grpcprom.ServerMetricsOption)
+```
+
+SetServerMetricsOptions appends gRPC server metrics options \(histogram, labels, namespace, etc.\). Must be called during initialization, before the server starts. Not safe for concurrent use.
+
-## func [TraceIdInterceptor]()
+## func [TraceIdInterceptor]()
```go
func TraceIdInterceptor() grpc.UnaryServerInterceptor
@@ -311,7 +340,7 @@ func TraceIdInterceptor() grpc.UnaryServerInterceptor
TraceIdInterceptor allows injecting trace id from request objects
-## func [UseColdBrewClientInterceptors]()
+## func [UseColdBrewClientInterceptors]()
```go
func UseColdBrewClientInterceptors(ctx context.Context, flag bool)
@@ -320,7 +349,7 @@ func UseColdBrewClientInterceptors(ctx context.Context, flag bool)
UseColdBrewClientInterceptors allows enabling/disabling coldbrew client interceptors. When set to false, the coldbrew client interceptors will not be used. Must be called during initialization, before any RPCs are made. Not safe for concurrent use.
-## func [UseColdBrewServerInterceptors]()
+## func [UseColdBrewServerInterceptors]()
```go
func UseColdBrewServerInterceptors(ctx context.Context, flag bool)
@@ -329,7 +358,7 @@ func UseColdBrewServerInterceptors(ctx context.Context, flag bool)
UseColdBrewServerInterceptors allows enabling/disabling coldbrew server interceptors. When set to false, the coldbrew server interceptors will not be used. Must be called during initialization, before the server starts. Not safe for concurrent use.
-## type [FilterFunc]()
+## type [FilterFunc]()
If it returns false, the given request will not be traced.
diff --git a/go.mod b/go.mod
index a212259..a25eb36 100644
--- a/go.mod
+++ b/go.mod
@@ -4,15 +4,17 @@ go 1.25.8
require (
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5
- github.com/go-coldbrew/errors v0.2.2
- github.com/go-coldbrew/log v0.2.6
- github.com/go-coldbrew/options v0.2.4
+ github.com/go-coldbrew/errors v0.2.5
+ github.com/go-coldbrew/log v0.2.7
+ github.com/go-coldbrew/options v0.2.6
github.com/go-coldbrew/tracing v0.0.7
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
- github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
+ github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0
+ github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0
github.com/newrelic/go-agent/v3 v3.42.0
github.com/newrelic/go-agent/v3/integrations/nrgrpc v1.4.7
+ github.com/prometheus/client_golang v1.23.2
google.golang.org/grpc v1.79.3
)
@@ -39,6 +41,7 @@ require (
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
+ github.com/airbrake/gobrake/v5 v5.6.2 // indirect
github.com/alecthomas/chroma/v2 v2.23.1 // indirect
github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
github.com/alexkohler/nakedret/v2 v2.0.6 // indirect
@@ -58,9 +61,9 @@ require (
github.com/breml/errchkjson v0.4.1 // indirect
github.com/butuzov/ireturn v0.4.0 // indirect
github.com/butuzov/mirror v1.3.0 // indirect
+ github.com/caio/go-tdigest/v4 v4.0.1 // indirect
github.com/catenacyber/perfsprint v0.10.1 // indirect
github.com/ccojocar/zxcvbn-go v1.0.4 // indirect
- github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charithe/durationcheck v0.0.11 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
@@ -85,7 +88,7 @@ require (
github.com/firefart/nonamedreturns v1.0.6 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
- github.com/getsentry/raven-go v0.2.0 // indirect
+ github.com/getsentry/sentry-go v0.43.0 // indirect
github.com/ghostiam/protogetter v0.3.20 // indirect
github.com/go-critic/go-critic v0.14.3 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
@@ -135,6 +138,7 @@ require (
github.com/jgautheron/goconst v1.8.2 // indirect
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
github.com/jjti/go-spancheck v0.6.5 // indirect
+ github.com/jonboulle/clockwork v0.3.0 // indirect
github.com/julz/importas v0.2.0 // indirect
github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
@@ -174,7 +178,6 @@ require (
github.com/nishanths/exhaustive v0.12.0 // indirect
github.com/nishanths/predeclared v0.2.2 // indirect
github.com/nunnatsa/ginkgolinter v0.23.0 // indirect
- github.com/nxadm/tail v1.4.11 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
@@ -184,7 +187,6 @@ require (
github.com/princjef/gomarkdoc v1.1.0 // indirect
github.com/princjef/mageutil v1.0.0 // indirect
github.com/princjef/termdiff v0.1.0 // indirect
- github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
@@ -196,6 +198,7 @@ require (
github.com/raeperd/recvcheck v0.2.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
+ github.com/rollbar/rollbar-go v1.4.8 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryancurrah/gomodguard v1.4.1 // indirect
github.com/ryanrolds/sqlclosecheck v0.6.0 // indirect
@@ -221,7 +224,6 @@ require (
github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.11.1 // indirect
- github.com/stvp/rollbar v0.5.1 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/tetafro/godot v1.5.4 // indirect
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect
@@ -263,7 +265,6 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect
google.golang.org/protobuf v1.36.11 // indirect
- gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index 90b3b3a..f058f89 100644
--- a/go.sum
+++ b/go.sum
@@ -50,6 +50,8 @@ github.com/adhocore/gronx v1.19.1 h1:S4c3uVp5jPjnk00De0lslyTenGJ4nA3Ydbkj1SbdPVc
github.com/adhocore/gronx v1.19.1/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
+github.com/airbrake/gobrake/v5 v5.6.2 h1:/LLjm0B3Jy3gqg17VpeGEnSTRqiIElGDy3RZy9s7+MU=
+github.com/airbrake/gobrake/v5 v5.6.2/go.mod h1:7lOWiGlpBnOnmWdz7BJzY/shByCkuAIL4LvHbGBKeZs=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
@@ -97,13 +99,13 @@ github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E
github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70=
github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc=
github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI=
+github.com/caio/go-tdigest/v4 v4.0.1 h1:sx4ZxjmIEcLROUPs2j1BGe2WhOtHD6VSe6NNbBdKYh4=
+github.com/caio/go-tdigest/v4 v4.0.1/go.mod h1:Wsa+f0EZnV2gShdj1adgl0tQSoXRxtM0QioTgukFw8U=
github.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ=
github.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc=
github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc=
github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s=
-github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk=
@@ -171,22 +173,24 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
-github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
-github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
+github.com/getsentry/sentry-go v0.43.0 h1:XbXLpFicpo8HmBDaInk7dum18G9KSLcjZiyUKS+hLW4=
+github.com/getsentry/sentry-go v0.43.0/go.mod h1:XDotiNZbgf5U8bPDUAfvcFmOnMQQceESxyKaObSssW0=
github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0=
github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
-github.com/go-coldbrew/errors v0.2.2 h1:yHQ5jNiWRRuP++LJSpJK3PfPQchC9ejPTdVhmMlqvfo=
-github.com/go-coldbrew/errors v0.2.2/go.mod h1:nlRAJImqLRUNrZ3bnFRnlLix6NFGnK5IgWA30LwLdQk=
-github.com/go-coldbrew/log v0.2.6 h1:DZHQlPRRpMM3qUVcLJMUv9Vh3rcl5KlFPuVBFioX4SY=
-github.com/go-coldbrew/log v0.2.6/go.mod h1:EAQFVdPADXsJk8CuFEdJWutMUcJRq9jsDFW2FaqdvhU=
-github.com/go-coldbrew/options v0.2.4 h1:aGcjQWhXjibRRN1XVc3mzz2IAKxNlVC4+xyhrGnSRKg=
-github.com/go-coldbrew/options v0.2.4/go.mod h1:RstwV0WeRJyUN2/P7M0l67LTsLeUfCXkaLU2LrXRx7M=
+github.com/go-coldbrew/errors v0.2.5 h1:FIphU/WNcGy16158gabaY2tP790BSpvc9DyIywmkD1k=
+github.com/go-coldbrew/errors v0.2.5/go.mod h1:zRTdOIR5iUKy42vfB8RVphMtgYaD8EG9egtmicTZT6s=
+github.com/go-coldbrew/log v0.2.7 h1:kRQeTYLPLcjjAWuCcGZpNn8twSY/NbsGpjIKbpXEnRA=
+github.com/go-coldbrew/log v0.2.7/go.mod h1:BB+2VecklLXTaDQdNNe2h3r5rSZL2DGvJBXAgrXq1dk=
+github.com/go-coldbrew/options v0.2.6 h1:Nr93v7PbO+EYLHhzA8biGumaTTSHLHqTYLg70n/foXE=
+github.com/go-coldbrew/options v0.2.6/go.mod h1:Os4pZwIgMHES079iOKXTlzcipWXbxw0OhsAN5D9m2mM=
github.com/go-coldbrew/tracing v0.0.7 h1:I6IG0EqIzP1E1w+YnFHXf3Xebuigy8r1khWWH20YR+g=
github.com/go-coldbrew/tracing v0.0.7/go.mod h1:aAiSSr0x7IcIUXTa7PgXewgav51O3TCuSF+DvaaSKZs=
github.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog=
github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ=
+github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
+github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
@@ -306,8 +310,10 @@ github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+
github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o=
+github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo=
@@ -333,6 +339,8 @@ github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjz
github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=
github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8=
github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU=
+github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg=
+github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ=
@@ -361,6 +369,8 @@ github.com/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98=
github.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs=
github.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w=
github.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4=
github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI=
github.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ=
@@ -375,6 +385,8 @@ github.com/ldez/tagliatelle v0.7.2 h1:KuOlL70/fu9paxuxbeqlicJnCspCRjH0x8FW+NfgYU
github.com/ldez/tagliatelle v0.7.2/go.mod h1:PtGgm163ZplJfZMZ2sf5nhUT170rSuPgBimoyYtdaSI=
github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc=
github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ=
+github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353 h1:X/79QL0b4YJVO5+OsPH9rF2u428CIrGL/jLmPsoOQQ4=
+github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U=
github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY=
github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA=
github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA=
@@ -465,6 +477,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
+github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
+github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -506,6 +520,9 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/rollbar/rollbar-go v1.4.8 h1:SAKy97CHXSFZjxQUxmuBnQmfzCjX54kvQGEQZHEqwuQ=
+github.com/rollbar/rollbar-go v1.4.8/go.mod h1:I/jSI5yHNj7Uy8oxntmCeBSZ1ILvypqRKlFQvZTINgA=
+github.com/rollbar/rollbar-go/errors v1.0.0/go.mod h1:Ie0xEc1Cyj+T4XMO8s0Vf7pMfvSAAy1sb4AYc8aJsao=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g=
@@ -570,8 +587,6 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-github.com/stvp/rollbar v0.5.1 h1:qvyWbd0RNL5V27MBumqCXlcU7ohmHeEtKX+Czc8oeuw=
-github.com/stvp/rollbar v0.5.1/go.mod h1:/fyFC854GgkbHRz/rSsiYc6h84o0G5hxBezoQqRK7Ho=
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA=
@@ -827,8 +842,6 @@ google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhH
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/VividCortex/ewma.v1 v1.1.1/go.mod h1:TekXuFipeiHWiAlO1+wSS23vTcyFau5u3rxXUSXj710=
-gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
-gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/interceptors.go b/interceptors.go
index b95db72..d9b02ac 100644
--- a/interceptors.go
+++ b/interceptors.go
@@ -22,11 +22,10 @@ import (
"github.com/go-coldbrew/log/loggers"
"github.com/go-coldbrew/options"
nrutil "github.com/go-coldbrew/tracing/newrelic"
- grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
- grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
- grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
- grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
+ grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
+ "github.com/prometheus/client_golang/prometheus"
+ grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/retry"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/newrelic/go-agent/v3/integrations/nrgrpc"
newrelic "github.com/newrelic/go-agent/v3/newrelic"
@@ -34,6 +33,15 @@ import (
"google.golang.org/grpc/status"
)
+// SupportPackageIsVersion1 is a compile-time assertion constant.
+// Downstream packages (e.g. core) reference this constant to enforce
+// version compatibility. When interceptors makes a breaking change,
+// export a new constant and remove this one to force coordinated updates.
+const SupportPackageIsVersion1 = true
+
+// Compile-time version compatibility check.
+var _ = errors.SupportPackageIsVersion1
+
var (
//FilterMethods is the list of methods that are filtered by default
FilterMethods = []string{"healthcheck", "readycheck", "serverreflectioninfo"}
@@ -45,6 +53,12 @@ var (
streamClientInterceptors = []grpc.StreamClientInterceptor{}
useCBClientInterceptors = true
responseTimeLogLevel loggers.Level = loggers.InfoLevel
+ srvMetricsOpts []grpcprom.ServerMetricsOption
+ cltMetricsOpts []grpcprom.ClientMetricsOption
+ srvMetricsOnce sync.Once
+ srvMetrics *grpcprom.ServerMetrics
+ cltMetricsOnce sync.Once
+ cltMetrics *grpcprom.ClientMetrics
)
// SetResponseTimeLogLevel sets the log level for response time logging.
@@ -113,6 +127,93 @@ func UseColdBrewClientInterceptors(ctx context.Context, flag bool) {
useCBClientInterceptors = flag
}
+// SetServerMetricsOptions appends gRPC server metrics options (histogram, labels, namespace, etc.).
+// Must be called during initialization, before the server starts. Not safe for concurrent use.
+func SetServerMetricsOptions(opts ...grpcprom.ServerMetricsOption) {
+ srvMetricsOpts = append(srvMetricsOpts, opts...)
+}
+
+// SetClientMetricsOptions appends gRPC client metrics options.
+// Must be called during initialization, before the server starts. Not safe for concurrent use.
+func SetClientMetricsOptions(opts ...grpcprom.ClientMetricsOption) {
+ cltMetricsOpts = append(cltMetricsOpts, opts...)
+}
+
+func registerCollector(c prometheus.Collector) {
+ if err := prometheus.Register(c); err != nil {
+ var are prometheus.AlreadyRegisteredError
+ if stdError.As(err, &are) {
+ prometheus.Unregister(are.ExistingCollector)
+ if err := prometheus.Register(c); err != nil {
+ log.Warn(context.Background(), "msg", "failed to re-register gRPC metrics with Prometheus", "err", err)
+ }
+ return
+ }
+ log.Error(context.Background(), "msg", "gRPC Prometheus metrics registration failed. If you are using github.com/go-coldbrew/core, it may need to be updated to the latest version.", "err", err)
+ }
+}
+
+func getServerMetrics() *grpcprom.ServerMetrics {
+ srvMetricsOnce.Do(func() {
+ srvMetrics = grpcprom.NewServerMetrics(srvMetricsOpts...)
+ registerCollector(srvMetrics)
+ })
+ return srvMetrics
+}
+
+func getClientMetrics() *grpcprom.ClientMetrics {
+ cltMetricsOnce.Do(func() {
+ cltMetrics = grpcprom.NewClientMetrics(cltMetricsOpts...)
+ registerCollector(cltMetrics)
+ })
+ return cltMetrics
+}
+
+// chainUnaryServer chains multiple unary server interceptors into one.
+func chainUnaryServer(interceptors []grpc.UnaryServerInterceptor) grpc.UnaryServerInterceptor {
+ return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
+ chain := handler
+ for i := len(interceptors) - 1; i >= 0; i-- {
+ interceptor := interceptors[i]
+ next := chain
+ chain = func(ctx context.Context, req interface{}) (interface{}, error) {
+ return interceptor(ctx, req, info, next)
+ }
+ }
+ return chain(ctx, req)
+ }
+}
+
+// chainUnaryClient chains multiple unary client interceptors into one.
+func chainUnaryClient(interceptors []grpc.UnaryClientInterceptor) grpc.UnaryClientInterceptor {
+ return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
+ chain := invoker
+ for i := len(interceptors) - 1; i >= 0; i-- {
+ interceptor := interceptors[i]
+ next := chain
+ chain = func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error {
+ return interceptor(ctx, method, req, reply, cc, next, opts...)
+ }
+ }
+ return chain(ctx, method, req, reply, cc, opts...)
+ }
+}
+
+// chainStreamClient chains multiple stream client interceptors into one.
+func chainStreamClient(interceptors []grpc.StreamClientInterceptor) grpc.StreamClientInterceptor {
+ return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
+ chain := streamer
+ for i := len(interceptors) - 1; i >= 0; i-- {
+ interceptor := interceptors[i]
+ next := chain
+ chain = func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
+ return interceptor(ctx, desc, cc, method, next, opts...)
+ }
+ }
+ return chain(ctx, desc, cc, method, opts...)
+ }
+}
+
// DoHTTPtoGRPC allows calling the interceptors when you use the RegisterHandlerServer in grpc-gateway.
// The interceptor chain is cached on first invocation. All interceptor configuration
// (AddUnaryServerInterceptor, SetFilterFunc, etc.) must be finalized before the first call.
@@ -139,7 +240,7 @@ var (
func getHTTPtoGRPCInterceptor() grpc.UnaryServerInterceptor {
httpToGRPCOnce.Do(func() {
- httpToGRPCInterceptor = grpc_middleware.ChainUnaryServer(DefaultInterceptors()...)
+ httpToGRPCInterceptor = chainUnaryServer(DefaultInterceptors())
})
return httpToGRPCInterceptor
}
@@ -167,9 +268,8 @@ func DefaultInterceptors() []grpc.UnaryServerInterceptor {
ints = append(ints,
ResponseTimeLoggingInterceptor(defaultFilterFunc),
TraceIdInterceptor(),
- grpc_ctxtags.UnaryServerInterceptor(),
grpc_opentracing.UnaryServerInterceptor(grpc_opentracing.WithFilterFunc(defaultFilterFunc)),
- grpc_prometheus.UnaryServerInterceptor,
+ getServerMetrics().UnaryServerInterceptor(),
ServerErrorInterceptor(),
NewRelicInterceptor(),
PanicRecoveryInterceptor(),
@@ -203,7 +303,7 @@ func DefaultClientInterceptors(defaultOpts ...interface{}) []grpc.UnaryClientInt
grpc_retry.UnaryClientInterceptor(),
GRPCClientInterceptor(opentracingOpt...),
NewRelicClientInterceptor(),
- grpc_prometheus.UnaryClientInterceptor,
+ getClientMetrics().UnaryClientInterceptor(),
)
}
return ints
@@ -228,7 +328,7 @@ func DefaultClientStreamInterceptors(defaultOpts ...interface{}) []grpc.StreamCl
ints = append(ints,
grpc_opentracing.StreamClientInterceptor(opentracingOpt...),
nrgrpc.StreamClientInterceptor,
- grpc_prometheus.StreamClientInterceptor,
+ getClientMetrics().StreamClientInterceptor(),
)
}
return ints
@@ -243,9 +343,8 @@ func DefaultStreamInterceptors() []grpc.StreamServerInterceptor {
if useCBServerInterceptors {
ints = append(ints,
ResponseTimeLoggingStreamInterceptor(),
- grpc_ctxtags.StreamServerInterceptor(),
grpc_opentracing.StreamServerInterceptor(),
- grpc_prometheus.StreamServerInterceptor,
+ getServerMetrics().StreamServerInterceptor(),
ServerErrorStreamInterceptor(),
)
}
@@ -254,12 +353,12 @@ func DefaultStreamInterceptors() []grpc.StreamServerInterceptor {
// DefaultClientInterceptor are the set of default interceptors that should be applied to all client calls
func DefaultClientInterceptor(defaultOpts ...interface{}) grpc.UnaryClientInterceptor {
- return grpc_middleware.ChainUnaryClient(DefaultClientInterceptors(defaultOpts...)...)
+ return chainUnaryClient(DefaultClientInterceptors(defaultOpts...))
}
// DefaultClientStreamInterceptor are the set of default interceptors that should be applied to all stream client calls
func DefaultClientStreamInterceptor(defaultOpts ...interface{}) grpc.StreamClientInterceptor {
- return grpc_middleware.ChainStreamClient(DefaultClientStreamInterceptors(defaultOpts...)...)
+ return chainStreamClient(DefaultClientStreamInterceptors(defaultOpts...))
}
// DebugLoggingInterceptor is the interceptor that logs all request/response from a handler
@@ -319,12 +418,8 @@ func ServerErrorInterceptor() grpc.UnaryServerInterceptor {
// set trace id if not set
ctx = notifier.SetTraceId(ctx)
- t := grpc_ctxtags.Extract(ctx)
- if t != nil {
- traceID := notifier.GetTraceId(ctx)
- t.Set("trace", traceID)
- ctx = loggers.AddToLogContext(ctx, "trace", traceID)
- }
+ traceID := notifier.GetTraceId(ctx)
+ ctx = loggers.AddToLogContext(ctx, "trace", traceID)
start := time.Now()
resp, err = handler(ctx, req)
if err != nil && defaultFilterFunc(ctx, info.FullMethod) {
@@ -461,12 +556,8 @@ func ServerErrorStreamInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) {
ctx := stream.Context()
ctx = notifier.SetTraceId(ctx)
- t := grpc_ctxtags.Extract(ctx)
- if t != nil {
- traceID := notifier.GetTraceId(ctx)
- t.Set("trace", traceID)
- ctx = loggers.AddToLogContext(ctx, "trace", traceID)
- }
+ traceID := notifier.GetTraceId(ctx)
+ ctx = loggers.AddToLogContext(ctx, "trace", traceID)
start := time.Now()
err = handler(srv, stream)
if err != nil && defaultFilterFunc(ctx, info.FullMethod) {
diff --git a/interceptors_test.go b/interceptors_test.go
index ffc3ffa..b531aa5 100644
--- a/interceptors_test.go
+++ b/interceptors_test.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
+ "sync"
"testing"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
@@ -317,3 +318,198 @@ func TestHystrixClientInterceptor(t *testing.T) {
}
})
}
+
+func TestChainUnaryServer(t *testing.T) {
+ var order []int
+ makeInterceptor := func(id int) grpc.UnaryServerInterceptor {
+ return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
+ order = append(order, id)
+ return handler(ctx, req)
+ }
+ }
+
+ chain := chainUnaryServer([]grpc.UnaryServerInterceptor{
+ makeInterceptor(1),
+ makeInterceptor(2),
+ makeInterceptor(3),
+ })
+
+ info := &grpc.UnaryServerInfo{FullMethod: "/test/Chain"}
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ order = append(order, 0) // handler marker
+ return "ok", nil
+ }
+
+ resp, err := chain(context.Background(), nil, info, handler)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if resp != "ok" {
+ t.Errorf("expected 'ok', got %v", resp)
+ }
+ if len(order) != 4 || order[0] != 1 || order[1] != 2 || order[2] != 3 || order[3] != 0 {
+ t.Errorf("expected execution order [1 2 3 0], got %v", order)
+ }
+}
+
+func TestChainUnaryClient(t *testing.T) {
+ var order []int
+ makeInterceptor := func(id int) grpc.UnaryClientInterceptor {
+ return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
+ order = append(order, id)
+ return invoker(ctx, method, req, reply, cc, opts...)
+ }
+ }
+
+ chain := chainUnaryClient([]grpc.UnaryClientInterceptor{
+ makeInterceptor(1),
+ makeInterceptor(2),
+ makeInterceptor(3),
+ })
+
+ invoker := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error {
+ order = append(order, 0)
+ return nil
+ }
+
+ err := chain(context.Background(), "/test/Chain", nil, nil, nil, invoker)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if len(order) != 4 || order[0] != 1 || order[1] != 2 || order[2] != 3 || order[3] != 0 {
+ t.Errorf("expected execution order [1 2 3 0], got %v", order)
+ }
+}
+
+func TestChainStreamClient(t *testing.T) {
+ var order []int
+ makeInterceptor := func(id int) grpc.StreamClientInterceptor {
+ return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
+ order = append(order, id)
+ return streamer(ctx, desc, cc, method, opts...)
+ }
+ }
+
+ chain := chainStreamClient([]grpc.StreamClientInterceptor{
+ makeInterceptor(1),
+ makeInterceptor(2),
+ makeInterceptor(3),
+ })
+
+ streamer := func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
+ order = append(order, 0)
+ return nil, nil
+ }
+
+ _, err := chain(context.Background(), nil, nil, "/test/Chain", streamer)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if len(order) != 4 || order[0] != 1 || order[1] != 2 || order[2] != 3 || order[3] != 0 {
+ t.Errorf("expected execution order [1 2 3 0], got %v", order)
+ }
+}
+
+// TestChainUnaryServerConcurrent verifies that a single chained interceptor
+// can be invoked concurrently from multiple goroutines without data races
+// and that each goroutine sees the correct execution order and output.
+// Run with -race to detect violations.
+func TestChainUnaryServerConcurrent(t *testing.T) {
+ chain := chainUnaryServer([]grpc.UnaryServerInterceptor{
+ func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
+ return handler(ctx, req.(string)+"-A")
+ },
+ func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
+ return handler(ctx, req.(string)+"-B")
+ },
+ })
+
+ info := &grpc.UnaryServerInfo{FullMethod: "/test/Concurrent"}
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return req.(string) + "-handler", nil
+ }
+
+ var wg sync.WaitGroup
+ for i := 0; i < 100; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ resp, err := chain(context.Background(), "start", info, handler)
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ if resp != "start-A-B-handler" {
+ t.Errorf("expected 'start-A-B-handler', got %v", resp)
+ }
+ }()
+ }
+ wg.Wait()
+}
+
+// TestChainUnaryClientConcurrent verifies that the unary client chain is safe
+// for concurrent use and produces the correct output from each goroutine.
+func TestChainUnaryClientConcurrent(t *testing.T) {
+ chain := chainUnaryClient([]grpc.UnaryClientInterceptor{
+ func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
+ return invoker(ctx, method+"-A", req, reply, cc, opts...)
+ },
+ func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
+ return invoker(ctx, method+"-B", req, reply, cc, opts...)
+ },
+ })
+
+ var wg sync.WaitGroup
+ for i := 0; i < 100; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ var got string
+ invoker := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error {
+ got = method
+ return nil
+ }
+ err := chain(context.Background(), "/svc/Call", nil, nil, nil, invoker)
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ if got != "/svc/Call-A-B" {
+ t.Errorf("expected '/svc/Call-A-B', got %v", got)
+ }
+ }()
+ }
+ wg.Wait()
+}
+
+// TestChainStreamClientConcurrent verifies that the stream client chain is safe
+// for concurrent use and produces the correct output from each goroutine.
+func TestChainStreamClientConcurrent(t *testing.T) {
+ chain := chainStreamClient([]grpc.StreamClientInterceptor{
+ func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
+ return streamer(ctx, desc, cc, method+"-A", opts...)
+ },
+ func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
+ return streamer(ctx, desc, cc, method+"-B", opts...)
+ },
+ })
+
+ var wg sync.WaitGroup
+ for i := 0; i < 100; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ var got string
+ streamer := func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
+ got = method
+ return nil, nil
+ }
+ _, err := chain(context.Background(), nil, nil, "/svc/Stream", streamer)
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ if got != "/svc/Stream-A-B" {
+ t.Errorf("expected '/svc/Stream-A-B', got %v", got)
+ }
+ }()
+ }
+ wg.Wait()
+}