11
votes

Our ASP.NET MVC application includes some URI path parameters, like:

https://example.com/api/query/14hes1017ceimgS2ESsIec

In Application Insights, this URI above becomes Operation Name

GET /api/query/14hes1017ceimgS2ESsIec

We don't want millions of unique Operations like this; it's just one code method serving them all (see below). We want to roll them up under an Operation Name like

GET /api/query/{path}

Here is the code method - I think App Insights could detect that the URI contains a query parameter... but it doesn't.

    [Route("api/query/{hash}")]
    public HttpResponseMessage Get(string hash)
    {
        ...
4
Sure does @PavelChuchuva :) specifically the HttpContextExtension.GetRequestTelemetry... I have wanted that before...Iain

4 Answers

5
votes

The reason Application Insights does not detect that the suffix of your Operation Name is a parameter is because the SDK does not look at your code, and for all practical purposes that's a valid URI.
Two options to get what you want:

  1. Change your API to pass the parameter in the query string (that is stripped out of the Operation Name)
  2. Implement your own ITelemetryProcessor (detailed explanation can be found here), and remove the suffix hash from the Operation Name yourself
2
votes

I hacked it with this hardcoded OperationNameMunger (using these docs for inspiration).

I wired it into the ApplicationInsights.config, straight after the OperationNameTelemetryInitializer.


using System.Text.RegularExpressions;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.Extensibility;

namespace My.Namespace
{
    public class OperationNameMunger : ITelemetryInitializer
    {
        public void Initialize(ITelemetry telemetry)
        {
            var existingOpName = telemetry.Context?.Operation?.Name;
            if (existingOpName == null)
                return;

            const string matchesInterestingOps = "^([A-Z]+ /api/query/)[^ ]+$";
            var match = Regex.Match(existingOpName, matchesInterestingOps);
            if (match.Success)
            {
                telemetry.Context.Operation.Name = match.Groups[1].Value + "{hash}";
            }
        }
    }
}
1
votes

When I ran into this I felt like it would be more useful to have the actual text of the route as the operation name, rather than try to identify all the different ways an ID could be constructed.

The problem is that route template exists down the tree from HttpRequestMessage, but in a TelemetryInitializer you end up with only access to HttpContext.Current.Request which is an HttpRequest.

They don't make it easy but this code works:


    // This class runs at the start of each request and gets the name of the
    // route template from actionContext.ControllerContext?.RouteData?.Route?.RouteTemplate
    // then stores it in HttpContext.Current.Items
    public class AiRewriteUrlsFilter : System.Web.Http.Filters.ActionFilterAttribute
    {
        internal const string AiTelemetryName = "AiTelemetryName";
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            string method = actionContext.Request?.Method?.Method;
            string routeData = actionContext.ControllerContext?.RouteData?.Route?.RouteTemplate;
            if (!string.IsNullOrEmpty(routeData) && routeData.StartsWith("api/1.0/") && HttpContext.Current != null)
            {
                HttpContext.Current.Items.Add(AiTelemetryName, $"{method} /{routeData}");
            }
            base.OnActionExecuting(actionContext);
        }
    }

    // This class runs when the telemetry is initialized and pulls
    // the value we set in HttpContext.Current.Items and uses it
    // as the new name of the telemetry.
    public class AiRewriteUrlsInitializer : ITelemetryInitializer
    {
        public void Initialize(ITelemetry telemetry)
        {
            if (telemetry is RequestTelemetry rTelemetry && HttpContext.Current != null)
            {
                string telemetryName = HttpContext.Current.Items[AiRewriteUrlsFilter.AiTelemetryName] as string;
                if (!string.IsNullOrEmpty(telemetryName))
                {
                    rTelemetry.Name = telemetryName;
                }
            }
        }
    }