github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/web/interceptor/metrics.go (about) 1 package interceptor 2 3 import ( 4 "context" 5 "strings" 6 7 "connectrpc.com/connect" 8 "github.com/prometheus/client_golang/prometheus" 9 ) 10 11 // RPCMetricsCollectors returns a list of Prometheus metrics collectors for RPC related metrics. 12 func RPCMetricsCollectors() []prometheus.Collector { 13 return []prometheus.Collector{ 14 loginCounter, 15 failedMethodsCounter, 16 accessedMethodsCounter, 17 respondedMethodsCounter, 18 responseTimeGauge, 19 } 20 } 21 22 var ( 23 responseTimeGauge = prometheus.NewGaugeVec(prometheus.GaugeOpts{ 24 Name: "quickfeed_method_response_time", 25 Help: "The response time for method.", 26 }, []string{"method"}) 27 28 accessedMethodsCounter = prometheus.NewCounterVec(prometheus.CounterOpts{ 29 Name: "quickfeed_method_accessed", 30 Help: "Total number of times method accessed", 31 }, []string{"method"}) 32 33 respondedMethodsCounter = prometheus.NewCounterVec(prometheus.CounterOpts{ 34 Name: "quickfeed_method_responded", 35 Help: "Total number of times method responded successfully", 36 }, []string{"method"}) 37 38 failedMethodsCounter = prometheus.NewCounterVec(prometheus.CounterOpts{ 39 Name: "quickfeed_method_failed", 40 Help: "Total number of times method failed with an error", 41 }, []string{"method"}) 42 43 loginCounter = prometheus.NewCounterVec(prometheus.CounterOpts{ 44 Name: "quickfeed_login_attempts", 45 Help: "Total number of login attempts", 46 }, []string{"user"}) 47 ) 48 49 type MetricsInterceptor struct{} 50 51 func NewMetricsInterceptor() *MetricsInterceptor { 52 return &MetricsInterceptor{} 53 } 54 55 func (*MetricsInterceptor) WrapStreamingHandler(next connect.StreamingHandlerFunc) connect.StreamingHandlerFunc { 56 return connect.StreamingHandlerFunc(func(ctx context.Context, conn connect.StreamingHandlerConn) error { 57 procedure := conn.Spec().Procedure 58 methodName := procedure[strings.LastIndex(procedure, "/")+1:] 59 defer metricsTimer(methodName)() 60 accessedMethodsCounter.WithLabelValues(methodName).Inc() 61 err := next(ctx, conn) 62 if err != nil { 63 failedMethodsCounter.WithLabelValues(methodName).Inc() 64 } 65 return err 66 }) 67 } 68 69 func (*MetricsInterceptor) WrapStreamingClient(next connect.StreamingClientFunc) connect.StreamingClientFunc { 70 return connect.StreamingClientFunc(func(ctx context.Context, spec connect.Spec) connect.StreamingClientConn { 71 return next(ctx, spec) 72 }) 73 } 74 75 func (*MetricsInterceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc { 76 return connect.UnaryFunc(func(ctx context.Context, request connect.AnyRequest) (connect.AnyResponse, error) { 77 procedure := request.Spec().Procedure 78 methodName := procedure[strings.LastIndex(procedure, "/")+1:] 79 defer metricsTimer(methodName)() 80 resp, err := next(ctx, request) 81 accessedMethodsCounter.WithLabelValues(methodName).Inc() 82 if resp != nil { 83 respondedMethodsCounter.WithLabelValues(methodName).Inc() 84 } 85 if err != nil { 86 failedMethodsCounter.WithLabelValues(methodName).Inc() 87 if methodName == "GetUser" { 88 // Can't get the user ID from err; so just counting 89 loginCounter.WithLabelValues("").Inc() 90 } 91 } 92 return resp, err 93 }) 94 } 95 96 func metricsTimer(methodName string) func() { 97 responseTimer := prometheus.NewTimer(prometheus.ObserverFunc( 98 responseTimeGauge.WithLabelValues(methodName).Set), 99 ) 100 return func() { responseTimer.ObserveDuration() } 101 }