package instrumentation import ( "context" "time" "github.com/99designs/gqlgen/graphql" prometheusclient "github.com/prometheus/client_golang/prometheus" ) const ( existStatusFailure = "failure" exitStatusSuccess = "success" ) var ( requestStartedCounter prometheusclient.Counter requestCompletedCounter prometheusclient.Counter resolverStartedCounter *prometheusclient.CounterVec resolverCompletedCounter *prometheusclient.CounterVec timeToResolveField *prometheusclient.HistogramVec timeToHandleRequest *prometheusclient.HistogramVec ) // Register registers the prometheus metrics. func Register() { requestStartedCounter = prometheusclient.NewCounter( prometheusclient.CounterOpts{ Name: "graphql_request_started_total", Help: "Total number of requests started on the graphql server.", }, ) requestCompletedCounter = prometheusclient.NewCounter( prometheusclient.CounterOpts{ Name: "graphql_request_completed_total", Help: "Total number of requests completed on the graphql server.", }, ) resolverStartedCounter = prometheusclient.NewCounterVec( prometheusclient.CounterOpts{ Name: "graphql_resolver_started_total", Help: "Total number of resolver started on the graphql server.", }, []string{"object", "field"}, ) resolverCompletedCounter = prometheusclient.NewCounterVec( prometheusclient.CounterOpts{ Name: "graphql_resolver_completed_total", Help: "Total number of resolver completed on the graphql server.", }, []string{"object", "field"}, ) timeToResolveField = prometheusclient.NewHistogramVec(prometheusclient.HistogramOpts{ Name: "graphql_resolver_duration_ms", Help: "The time taken to resolve a field by graphql server.", Buckets: prometheusclient.ExponentialBuckets(1, 2, 11), }, []string{"exitStatus"}) timeToHandleRequest = prometheusclient.NewHistogramVec(prometheusclient.HistogramOpts{ Name: "graphql_request_duration_ms", Help: "The time taken to handle a request by graphql server.", Buckets: prometheusclient.ExponentialBuckets(1, 2, 11), }, []string{"exitStatus"}) prometheusclient.MustRegister( requestStartedCounter, requestCompletedCounter, resolverStartedCounter, resolverCompletedCounter, timeToResolveField, timeToHandleRequest, ) } // UnRegister unregisters func UnRegister() { prometheusclient.Unregister(requestStartedCounter) prometheusclient.Unregister(requestCompletedCounter) prometheusclient.Unregister(resolverStartedCounter) prometheusclient.Unregister(resolverCompletedCounter) prometheusclient.Unregister(timeToResolveField) prometheusclient.Unregister(timeToHandleRequest) } // ResolverMiddleware is a resolver middleware that logs metrics for prometheus. func ResolverMiddleware() graphql.FieldMiddleware { return func(ctx context.Context, next graphql.Resolver) (interface{}, error) { rctx := graphql.GetResolverContext(ctx) resolverStartedCounter.WithLabelValues(rctx.Object, rctx.Field.Name).Inc() observerStart := time.Now() res, err := next(ctx) var exitStatus string if err != nil { exitStatus = existStatusFailure } else { exitStatus = exitStatusSuccess } timeToResolveField.With(prometheusclient.Labels{"exitStatus": exitStatus}). Observe(float64(time.Since(observerStart).Nanoseconds() / int64(time.Millisecond))) resolverCompletedCounter.WithLabelValues(rctx.Object, rctx.Field.Name).Inc() return res, err } } // RequestMiddleware is a request middleware that logs metrics for prometheus. func RequestMiddleware() graphql.RequestMiddleware { return func(ctx context.Context, next func(ctx context.Context) []byte) []byte { requestStartedCounter.Inc() observerStart := time.Now() res := next(ctx) rctx := graphql.GetResolverContext(ctx) reqCtx := graphql.GetRequestContext(ctx) errList := reqCtx.GetErrors(rctx) var exitStatus string if len(errList) > 0 { exitStatus = existStatusFailure } else { exitStatus = exitStatusSuccess } timeToHandleRequest.With(prometheusclient.Labels{"exitStatus": exitStatus}). Observe(float64(time.Since(observerStart).Nanoseconds() / int64(time.Millisecond))) requestCompletedCounter.Inc() return res } }