停止在操作名称中包含路径参数的 Application Insights
Stop Application Insights including path parameters in the Operation Name
我们的 ASP.NET MVC 应用程序包含一些 URI 路径参数,例如:
在 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。
两个选项来获得你想要的:
- 更改 API 以在查询字符串中传递参数(从操作名称中删除)
- 实现你自己的
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
}
}
我们的 ASP.NET MVC 应用程序包含一些 URI 路径参数,例如:
在 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。
两个选项来获得你想要的:
- 更改 API 以在查询字符串中传递参数(从操作名称中删除)
- 实现你自己的
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
}
}