停止在操作名称中包含路径参数的 Application Insights

Stop Application Insights including path parameters in the Operation Name

我们的 ASP.NET MVC 应用程序包含一些 URI 路径参数,例如:

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

在 Application Insights 中,上述 URI 变为 操作名称

GET /api/query/14hes1017ceimgS2ESsIec

我们不想要数百万这样的独特操作;它只是为它们提供服务的一种代码方法(见下文)。我们想将它们汇总到像

这样的操作名称下
GET /api/query/{path}

这是代码方法 - 我认为 App Insights 可以检测到 URI 包含查询参数...但它没有。

    [Route("api/query/{hash}")]
    public HttpResponseMessage Get(string hash)
    {
        ...

Application Insights 没有检测到您的操作名称的后缀是参数的原因是因为 SDK 没有查看您的代码,并且出于所有实际目的,这是一个有效的 URI。
两个选项来获得你想要的:

  1. 更改 API 以在查询字符串中传递参数(从操作名称中删除)
  2. 实现你自己的ITelemetryProcessor(详细解释可以找到here),并自己从操作名称中删除后缀哈希

我用这个硬编码 OperationNameMunger 破解了它(使用 these docs 作为灵感)。

我将它连接到 ApplicationInsights.config,直接在 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}";
            }
        }
    }
}

MS 正在使用 https://github.com/Microsoft/ApplicationInsights-dotnet-server/issues/176

开发此功能

当我 运行 参与其中时,我觉得将路线的实际文本作为操作名称会更有用,而不是尝试识别可以构建 ID 的所有不同方式。

问题是路由模板存在于 HttpRequestMessage 的树下,但在 TelemetryInitializer 中你最终只能访问 HttpContext.Current.Request,这是一个 HttpRequest .

他们并不容易,但这段代码有效:


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

灵感来自@Mike 的

  • 更新 ASP.NET 核心 5/6
  • 如果指定,则使用路由名称。
  • 如果路线数据可用,则使用模板和 API 版本。

遥测名称before/after:
GET /chat/ba1ce6bb-01e8-4633-918b-08d9a363a631/since/2021-11-18T18:51:08
GET /chat/{id}/since/{timestamp}

https://gist.github.com/angularsen/551bcbc5f770d85ff9c4dfbab4465546

解决方案包括:

  • 全局 MVC 操作过滤器,用于根据路由数据计算遥测名称。
  • 用于更新遥测名称的 ITelemetryInitializer。
  • 在 ASP.NET 的启动中配置过滤器和初始化器 class

用于根据 API 操作路线数据计算遥测名称的全局过滤器。

#nullable enable
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Digma.Api.Common.Telemetry
{
    /// <summary>
    /// Action filter to construct a simpler telemetry name from the route name or the route template.
    /// <br/><br/>
    /// Out of the box, Application Insights sometimes uses request telemetry names like "GET /chat/ba1ce6bb-01e8-4633-918b-08d9a363a631/since/2021-11-18T18:51:08".
    /// This makes it hard to see how many requests were for a particular API action.
    /// This is a <a href="https://github.com/microsoft/ApplicationInsights-dotnet/issues/1418">known issue</a>.
    /// <br/><br/>
    /// - If route name is defined, then use that.<br/>
    /// - If route template is defined, then the name is formatted as "{method} /{template} v{version}".
    /// </summary>
    /// <example>
    /// - <b>"MyCustomName"</b> if route name is explicitly defined with <c>[Route("my_path", Name="MyCustomName")]</c><br/>
    /// - <b>"GET /config v2.0"</b> if template is "config" and API version is 2.0.<br/>
    /// - <b>"GET /config"</b> if no API version is defined.
    /// </example>
    /// <remarks>
    /// The value is passed on via <see cref="HttpContext.Items"/> with the key <see cref="SimpleRequestTelemetryNameInitializer.TelemetryNameKey"/>.
    /// </remarks>
    public class SimpleRequestTelemetryNameActionFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var httpContext = context.HttpContext;
            var attributeRouteInfo = context.ActionDescriptor.AttributeRouteInfo;

            if (attributeRouteInfo?.Name is { } name)
            {
                // If route name is defined, it takes precedence.
                httpContext.Items.Add(SimpleRequestTelemetryNameInitializer.TelemetryNameKey, name);
            }
            else if (attributeRouteInfo?.Template is { } template)
            {
                // Otherwise, use the route template if defined.
                string method = httpContext.Request.Method;
                string versionSuffix = GetVersionSuffix(httpContext);

                httpContext.Items.Add(SimpleRequestTelemetryNameInitializer.TelemetryNameKey, $"{method} /{template}{versionSuffix}");
            }

            base.OnActionExecuting(context);
        }

        private static string GetVersionSuffix(HttpContext httpContext)
        {
            try
            {
                var requestedApiVersion = httpContext.GetRequestedApiVersion()?.ToString();

                // Add leading whitespace so we can simply append version string to telemetry name.
                if (!string.IsNullOrWhiteSpace(requestedApiVersion))
                    return $" v{requestedApiVersion}";
            }
            catch (Exception)
            {
                // Some requests lack the IApiVersioningFeature, like requests to get swagger doc
            }

            return string.Empty;
        }
    }
}

更新 RequestTelemetry.

名称的遥测初始化程序
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.AspNetCore.Http;

namespace Digma.Api.Common.Telemetry
{
    /// <summary>
    /// Changes the name of request telemetry to the value assigned by <see cref="SimpleRequestTelemetryNameActionFilter"/>.
    /// </summary>
    /// <remarks>
    /// The value is passed on via <see cref="HttpContext.Items"/> with the key <see cref="TelemetryNameKey"/>.
    /// </remarks>
    public class SimpleRequestTelemetryNameInitializer : ITelemetryInitializer
    {
        internal const string TelemetryNameKey = "SimpleOperationNameInitializer:TelemetryName";
        private readonly IHttpContextAccessor _httpContextAccessor;

        public SimpleRequestTelemetryNameInitializer(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        public void Initialize(ITelemetry telemetry)
        {
            var httpContext = _httpContextAccessor.HttpContext;
            if (telemetry is RequestTelemetry requestTelemetry && httpContext != null)
            {
                if (httpContext.Items.TryGetValue(TelemetryNameKey, out var telemetryNameObj)
                    && telemetryNameObj is string telemetryName
                    && !string.IsNullOrEmpty(telemetryName))
                {
                    requestTelemetry.Name = telemetryName;
                }
            }
        }
    }
}

ASP.NET 启动 class 配置全局过滤器和遥测初始化程序。

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // Register telemetry initializer.
        services.AddApplicationInsightsTelemetry();
        services.AddSingleton<ITelemetryInitializer, SimpleRequestTelemetryNameInitializer>();

        services.AddMvc(opt =>
        {
            // Global MVC filters.
            opt.Filters.Add<SimpleRequestTelemetryNameActionFilter>();
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // ...other configuration
    }
}