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);
}
}
}
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);
}
}