Skip to content

Commit

Permalink
make registration of gRPC prom metrics not fail if already registered
Browse files Browse the repository at this point in the history
this allows custom builds of SpiceDB to register their own grpc prom metrics
with custom labels
  • Loading branch information
vroldanbet committed Mar 15, 2024
1 parent a0e18de commit d66d50e
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 12 deletions.
3 changes: 2 additions & 1 deletion pkg/cmd/devtools.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,13 @@ func NewDevtoolsCommand(programName string) *cobra.Command {
}

func runfunc(cmd *cobra.Command, _ []string) error {
grpcUnaryInterceptor, _ := server.GRPCMetrics()
grpcBuilder := grpcServiceBuilder()
grpcServer, err := grpcBuilder.ServerFromFlags(cmd,
grpc.ChainUnaryInterceptor(
grpclog.UnaryServerInterceptor(server.InterceptorLogger(log.Logger)),
otelgrpc.UnaryServerInterceptor(), // nolint: staticcheck
server.GRPCMetricsUnaryInterceptor,
grpcUnaryInterceptor,
))
if err != nil {
log.Ctx(cmd.Context()).Fatal().Err(err).Msg("failed to create gRPC server")
Expand Down
36 changes: 25 additions & 11 deletions pkg/cmd/server/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
"net/http/pprof"
"sync"
"time"

"github.com/fatih/color"
Expand Down Expand Up @@ -164,14 +165,21 @@ type MiddlewareOption struct {
enableResponseLog bool
}

// GRPCMetricsUnaryInterceptor creates the default prometheus metrics interceptor for unary gRPCs
var GRPCMetricsUnaryInterceptor grpc.UnaryServerInterceptor
// gRPCMetricsUnaryInterceptor creates the default prometheus metrics interceptor for unary gRPCs
var gRPCMetricsUnaryInterceptor grpc.UnaryServerInterceptor

// GRPCMetricsStreamingInterceptor creates the default prometheus metrics interceptor for streaming gRPCs
var GRPCMetricsStreamingInterceptor grpc.StreamServerInterceptor
// gRPCMetricsStreamingInterceptor creates the default prometheus metrics interceptor for streaming gRPCs
var gRPCMetricsStreamingInterceptor grpc.StreamServerInterceptor

func init() {
GRPCMetricsUnaryInterceptor, GRPCMetricsStreamingInterceptor = createServerMetrics()
var serverMetricsOnce sync.Once

// GRPCMetrics returns the interceptors used for the default gRPC metrics from grpc-ecosystem/go-grpc-middleware
func GRPCMetrics() (grpc.UnaryServerInterceptor, grpc.StreamServerInterceptor) {
serverMetricsOnce.Do(func() {
gRPCMetricsUnaryInterceptor, gRPCMetricsStreamingInterceptor = createServerMetrics()
})

return gRPCMetricsUnaryInterceptor, gRPCMetricsStreamingInterceptor
}

const healthCheckRoute = "/grpc.health.v1.Health/Check"
Expand All @@ -190,6 +198,7 @@ func doesNotMatchRoute(route string) func(_ context.Context, c interceptors.Call

// DefaultUnaryMiddleware generates the default middleware chain used for the public SpiceDB Unary gRPC methods
func DefaultUnaryMiddleware(opts MiddlewareOption) (*MiddlewareChain[grpc.UnaryServerInterceptor], error) {
grpcMetricsUnaryInterceptor, _ := GRPCMetrics()
chain, err := NewMiddlewareChain([]ReferenceableMiddleware[grpc.UnaryServerInterceptor]{
NewUnaryMiddleware().
WithName(DefaultMiddlewareRequestID).
Expand Down Expand Up @@ -224,7 +233,7 @@ func DefaultUnaryMiddleware(opts MiddlewareOption) (*MiddlewareChain[grpc.UnaryS

NewUnaryMiddleware().
WithName(DefaultMiddlewareGRPCProm).
WithInterceptor(GRPCMetricsUnaryInterceptor).
WithInterceptor(grpcMetricsUnaryInterceptor).
Done(),

NewUnaryMiddleware().
Expand Down Expand Up @@ -267,6 +276,7 @@ func DefaultUnaryMiddleware(opts MiddlewareOption) (*MiddlewareChain[grpc.UnaryS

// DefaultStreamingMiddleware generates the default middleware chain used for the public SpiceDB Streaming gRPC methods
func DefaultStreamingMiddleware(opts MiddlewareOption) (*MiddlewareChain[grpc.StreamServerInterceptor], error) {
_, grpcMetricsStreamingInterceptor := GRPCMetrics()
chain, err := NewMiddlewareChain([]ReferenceableMiddleware[grpc.StreamServerInterceptor]{
NewStreamMiddleware().
WithName(DefaultMiddlewareRequestID).
Expand Down Expand Up @@ -301,7 +311,7 @@ func DefaultStreamingMiddleware(opts MiddlewareOption) (*MiddlewareChain[grpc.St

NewStreamMiddleware().
WithName(DefaultMiddlewareGRPCProm).
WithInterceptor(GRPCMetricsStreamingInterceptor).
WithInterceptor(grpcMetricsStreamingInterceptor).
Done(),

NewStreamMiddleware().
Expand Down Expand Up @@ -357,12 +367,13 @@ func determineEventsToLog(opts MiddlewareOption) grpclog.Option {

// DefaultDispatchMiddleware generates the default middleware chain used for the internal dispatch SpiceDB gRPC API
func DefaultDispatchMiddleware(logger zerolog.Logger, authFunc grpcauth.AuthFunc, ds datastore.Datastore) ([]grpc.UnaryServerInterceptor, []grpc.StreamServerInterceptor) {
grpcMetricsUnaryInterceptor, grpcMetricsStreamingInterceptor := GRPCMetrics()
return []grpc.UnaryServerInterceptor{
requestid.UnaryServerInterceptor(requestid.GenerateIfMissing(true)),
logmw.UnaryServerInterceptor(logmw.ExtractMetadataField("x-request-id", "requestID")),
otelgrpc.UnaryServerInterceptor(), // nolint: staticcheck
grpclog.UnaryServerInterceptor(InterceptorLogger(logger), defaultCodeToLevel, durationFieldOption, traceIDFieldOption),
GRPCMetricsUnaryInterceptor,
grpcMetricsUnaryInterceptor,
grpcauth.UnaryServerInterceptor(authFunc),
datastoremw.UnaryServerInterceptor(ds),
servicespecific.UnaryServerInterceptor,
Expand All @@ -371,7 +382,7 @@ func DefaultDispatchMiddleware(logger zerolog.Logger, authFunc grpcauth.AuthFunc
logmw.StreamServerInterceptor(logmw.ExtractMetadataField("x-request-id", "requestID")),
otelgrpc.StreamServerInterceptor(), // nolint: staticcheck
grpclog.StreamServerInterceptor(InterceptorLogger(logger), defaultCodeToLevel, durationFieldOption, traceIDFieldOption),
GRPCMetricsStreamingInterceptor,
grpcMetricsStreamingInterceptor,
grpcauth.StreamServerInterceptor(authFunc),
datastoremw.StreamServerInterceptor(ds),
servicespecific.StreamServerInterceptor,
Expand Down Expand Up @@ -406,7 +417,10 @@ func createServerMetrics() (grpc.UnaryServerInterceptor, grpc.StreamServerInterc
),
)

prometheus.DefaultRegisterer.MustRegister(srvMetrics)
// deliberately ignore if these metrics were already registered, so that
// custom builds of SpiceDB can register these metrics with custom labels
_ = prometheus.DefaultRegisterer.Register(srvMetrics)

exemplarFromContext := func(ctx context.Context) prometheus.Labels {
if span := trace.SpanContextFromContext(ctx); span.IsSampled() {
return prometheus.Labels{"traceID": span.TraceID().String()}
Expand Down

0 comments on commit d66d50e

Please sign in to comment.