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  }