How to handle label names for requests that don't have controller/action values?

1,210 views
Skip to first unread message

OLIVER OLIVE

unread,
Aug 15, 2022, 5:34:16 PM8/15/22
to Prometheus Users
In the default metrics exported with app.usehttpmetrics(), certain metrics, such as http_requests_received_total will have scraped data that only contain ''code' and 'method' labels which I see is defined here in HttpRequestLabelNames.cs. I'm trying to replicate the same thing with custom middleware that records the response body content length. But when requests with bad query params return 404 responses there is no controller, endpoint, or action fields in HttpContext and I can't use my custom counters with those label names specified. How do you deal with dynamic label names without having to create separate metrics counters?

Brian Candler

unread,
Aug 16, 2022, 3:05:52 AM8/16/22
to Prometheus Users
> How do you deal with dynamic label names without having to create separate metrics counters?

You have to have separate counters.

Every combination of (metric name + set of label names/values) creates a distinct timeries, i.e. a different metric, by definition.  Therefore you need different counters for each distinct combination of label values.

Labels are not just somewhere to store a string.  If you put high-cardinality values in a label, i.e. values which change from request to request and are not bounded to a small subset of values, then you will risk a "cardinality explosion" where you have millions of timeseries all with a single value.  Under these conditions, Prometheus will crash and burn.

In your case, if you're using controller name and action name for labels, then I think it should be OK as those are values chosen from a small set defined in code.  For counting requests which don't have these fields, then use a dummy value like "-" for those labels.

OLIVER OLIVE

unread,
Aug 16, 2022, 2:49:36 PM8/16/22
to Prometheus Users
Looks like I answered my question I believe hasn't been authorized yet, or maybe I forgot to click 'post message'. But the solution is to pass the label values as empty strings or with a hyphen placeholder like Brian Candler suggests. Prometheus will pick these up and not show the label name/value pair where an empty string or hyphen is for the value. Passing in null values or creating separate metrics of the same name throw exceptions. 

Here is a snippet of my code for the nullable label names: 

            string[]? labels = new string[] { statusCode, method, actionName ?? "", controllerName ?? "", path ?? "" };

I then pass the labels to a function that increments the counter by the length parameter:

           public void RegisterResponse(double length, params string[] labelValues)
            {
                  _responseCounter.WithLabels(labelValues).Inc( );
                  _responseLength.WithLabels(labelValues).Inc(length);
            }


Brian Candler

unread,
Aug 17, 2022, 3:24:17 AM8/17/22
to Prometheus Users
Glad it's working.

Prometheus treats empty string as label not being present at all, but it's not recommended to have inconsistent sets of labels on your metrics, as it makes them harder to aggregate over.  A concrete value like "-" is better.

Beware including the request path in your metric.  If the actual value supplied by the requestor is used, e.g.

http_requests{code="404",action="-",controller="-",path="/askdjnkj"} 1

then you put yourself at risk of a cardinality explosion. It's a very easy DoS attack vector - and even if unintentional, don't be surprised if people port-scanning your server try all sorts of paths.  Sanitise the path label to permitted/expected values only, or leave it out (since the path has already been mapped to controller and action, right?)

If you do want to log the actual path presented, then really you need to use a logging system like Loki.

OLIVER OLIVE

unread,
Aug 22, 2022, 2:51:07 PM8/22/22
to Prometheus Users
> Beware including the request path in your metric.  If the actual value supplied by the requestor is used, e.g.

Thanks, saved me from a future headache there. My intentions were to copy the labels from the default metrics supplied from app.UseHttpMetrics(). I see you can build a list using ActionDescriptorCollectionProvider, however, my route values get changed at each action method with different appendages which is probably not a good pattern. I don't want to write a complicated method there to account for it. Instead, I'm going to assume it's safe to check the value of the action and controller name since I'm using app.UseRouting() before the middleware in question here gets called. 

        string? controllerName = httpContext.Request.RouteValues["controller"]?.ToString( );
        string? actionName = httpContext.Request.RouteValues["action"]?.ToString( );
        string? endpoint = httpContext.Request.Path.Value; 

        if (controllerName is null || actionName is null)
        {
            endpoint = null;
        }

^I changed 'path' to 'endpoint' to make it consistent with the label name. 
Reply all
Reply to author
Forward
0 new messages