Search code examples
c#asp.netprometheusmiddlewaremetrics

C# prometheus metrics


Title: Collecting Metrics for API Calls in C# with Prometheus

I have a REST API service and I want to collect information about all my API calls. I have tried using a middleware to collect metrics and pass the httpContext.Request.Path as an api_name label. This approach works fine most of the time, but it becomes challenging when the API has arguments in the path, such as api/{version}/{id}/doStuff.In such cases, there would be a large number of labels corresponding to the combination of parameters in the API path.

To address this issue, I thought of using an ActionFilterAttribute called TelemetryAttribute on my endpoint. Here's an example of how it can be implemented:

public class TelemetryAttribute : ActionFilterAttribute
{
    public TelemetryAttribute(string requestId)
    {
        RequestId = requestId;
    }

    public string RequestId { get; set; }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        context.HttpContext.Items["CustomItem"] = RequestId;
        base.OnActionExecuting(context);
    }
}

By using this attribute on my endpoint, I thought i could add the given id of the request to the httpContex. However, ActionFilterAttribute only works once the _next(httpContext) starts execution, as mentioned in the Microsoft documentation.

Prometheus does not allow changing labels after an instance of a metric has been created. So i can not accurately set the labels.

I Could probably use stopwatch and the observe method that Histogram provides. But that is just not what i want.

Here is the middleware code block:

public class ApiCallsMetricMiddleware
{
    private readonly ITelemetryCollector _telemetryCollector;
    private readonly RequestDelegate _next;

    public ApiCallsMetricMiddleware(ITelemetryCollector telemetryCollector, RequestDelegate next)
    {
        _telemetryCollector = telemetryCollector;
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        var apiName = httpContext.Items["RequestId"] as string ?? httpContext.Request.Path;

        using (_telemetryCollector.NewHistogramTimer(MetricsDefinitionDeclarations.ApiCallsHistogram, apiName))
        {
            await _next(httpContext);
        }
    }
}

Solution

  • You have all what you need insdie GetRouteData(), unless I'm missing something:

    public async Task Invoke(HttpContext httpContext)
    {
        var routeData = httpContext.GetRouteData();
        //init apiName out of routeData
        
    
        using (_telemetryCollector.NewHistogramTimer(MetricsDefinitionDeclarations.ApiCallsHistogram, apiName))
        {
            await _next(httpContext);
        }
    }