We can face a big problem at Prometheus Counter if we want to add the path for requests. Let imagine the scenario:

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    [HttpGet("Country/{country}/State/{state}/City/{city}")]
    public async Task<IActionResult> Get(string country, string state, string city)
    {
        // Do stuff
        return Ok();
    }
}

When making a request seaching for country Brasil, state Goias and City Goiania we´re going to get the context Request Path Value, and will be /WeatherForecast/Country/brasil/State/goias/City/goiania

Searching for other city /WeatherForecast/Country/brasil/State/goias/City/anapolis

And the counter will count

/WeatherForecast/Country/brasil/State/goias/City/goiania = 1

/WeatherForecast/Country/brasil/State/goias/City/anapolis = 1

And what we want is to count /WeatherForecast/Country/{country}/State/{state}/City/{city} = 2

Unless you want to exactly count the values of route, this will not generate metrics data correctly, we want a perspective counter by method and show the route with placeholders.

In order to solve this we can create an extension method for HttpContext that will extract that information replacing route value with route placeholders.

private static string GetRouteEndpoint(this HttpContext context)
{
// Getting a ObjectPool for StringBuilder, this is used because StringBuilder is a large object, and this benefits the application by reusing the object avoiding creating unecessary object.
    var stringBuilderPool = context.RequestServices.GetRequiredService<ObjectPool<StringBuilder>>();
    var stringBuilder = stringBuilderPool.Get();

// Here we get all the paths and values from request
    var allPaths = context.Request.Path.Value?.Split('/');

    var routeData = context.GetRouteData();

// this is a control list of the paths added to stringBuilder
    var keyAdded = new List<string>();

// This looping is for replace some actual values for some PlaceHolders, this will avoid over counting
    foreach (var path in allPaths)
    {
        if (string.IsNullOrWhiteSpace(path))
            continue;

        stringBuilder.Append('/');

// searching for the KeyPair for the actual path
        var routeDataKeyValue = routeData.Values.FirstOrDefault(x 
                                        => x.Value is not null 
                                        && x.Value.ToString().Equals(path)
                                        && !keyAdded.Contains(path));


        // here we need the value of controller
        if (!string.IsNullOrEmpty(routeDataKeyValue.Key) && routeDataKeyValue.Key.Equals("controller", StringComparison.OrdinalIgnoreCase))
        {
            stringBuilder.Append(routeDataKeyValue.Value);
            continue;
        }

        if (!string.IsNullOrEmpty(routeDataKeyValue.Key))
        {

// we need to check if the value is already added, if exists we need to search for the next one
            if (keyAdded.Contains(routeDataKeyValue.Key))
            {
                routeDataKeyValue = routeData.Values.FirstOrDefault(x 
                    => x.Value is not null 
                    && x.Value.ToString().Equals(path) 
                    && !x.Key.Equals(routeDataKeyValue.Key));
            }

            keyAdded.Add(routeDataKeyValue.Key);
// adding the Key PlaceHolder instead of route value, here were we avoid the over counting
            stringBuilder.Append($"{{{routeDataKeyValue.Key}}}");
            continue;
        }

        // append constant path
        stringBuilder.Append(path);
    }

    var endpoint = stringBuilder.ToString();
    stringBuilderPool.Return(stringBuilder);

    return endpoint;
}

This is the final result.

You can check full code at https://github.com/andrearima/prometheus it´s a simple Api adding the ObjectPool of StringBuilder and Prometheus Metrics with Counter

Leave a comment

I’m André Arima

I am a developer specialized in .NET and C#, with extensive experience in creating robust and scalable applications. I use ASP.NET Core for backend development and modern frameworks for frontend. I have strong skills in software architecture design, performance optimization, and continuous integration practices. I am committed to adopting new technologies and agile methodologies, always aiming to deliver innovative and high-quality solutions.

Let’s connect

Design a site like this with WordPress.com
Get started