我正在尝试为每条路线的遥测请求添加特定属性。 深入研究之后,我发现可以通过实现ITelemetryInitializer创建自己的自定义TelemetryInitializer。 通过这样做,我设法将全局属性添加到请求中。 但是,我仍然需要在控制器级别添加特定的属性。 你有什么想法我该怎么实现吗?
我试图将TelemetryClient注入控制器,但是如果使用它,属性将在请求之间共享。
这是我尝试登录控制器的方式:
private TelemetryClient telemetryClient;
public ValueController(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
}
[HttpGet]
public async Task<IActionResult> RouteOne([FromQuery(Name = "param1")]string param1, [FromQuery(Name = "param2")]string param2)
{
telemetryClient.Context.GlobalProperties["param1"] = param1;
telemetryClient.Context.GlobalProperties["param2"] = param2;
}
[HttpGet]
public async Task<IActionResult> RouteTwo([FromQuery(Name = "param3")]string param3, [FromQuery(Name = "param4")]string param4)
{
telemetryClient.Context.GlobalProperties["param3"] = param3;
telemetryClient.Context.GlobalProperties["param4"] = param4;
}
这是ITelemetryInitializer的实现:
public class CustomPropertiesTelemetryInitializer : ITelemetryInitializer
{
private readonly IHttpContextAccessor httpContextAccessor;
public CustomPropertiesTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
telemetry.Context.GlobalProperties["RequestId"] = httpContextAccessor.HttpContext.GetProperty("requestId");
telemetry.Context.GlobalProperties["Ip"] = httpContextAccessor.HttpContext?.Connection.RemoteIpAddress.ToString();
telemetry.Context.GlobalProperties["RoutePath"] = httpContextAccessor.HttpContext?.Request.Path;
}
}
答案 0 :(得分:0)
如果您添加的属性始终像“ paramxxx”,那么有一种解决方法(但实际上不是很优雅)。
在控制器构造函数中,检查GlobalProperties是否包含诸如“ paramxxx”之类的键:
public ValueController(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
var props = this.telemetryClient.Context.GlobalProperties;
foreach (var p in props)
{
if (p.Key.Contains("param"))
{
props.Remove(p.Key);
}
}
}
答案 1 :(得分:0)
这里的关键是使用DI框架。您可以使用它将请求范围的数据或服务添加到ITelemetryInitializer
中。
(这些示例基于标准的ASP.Net依赖注入框架。此模式应可与任何DI框架一起使用,但需要稍作调整。)
首先,创建一个类来表示您的请求范围遥测。我使用了一个简单的DTO,但它也可能是知道如何获取/生成数据本身的服务。使用AddScoped
注册它。 “作用域”表示将为每个HTTP请求创建一个新实例,然后在该请求中重新使用该实例。
因为我使用了DTO,所以我不介意使用接口-如果类包含要在单元测试中模拟的任何逻辑,则应该使用接口。
public class RequestScopedTelemetry
{
public string MyCustomProperty { get; set; }
}
services.AddScoped<RequestScopedTelemetry>();
现在,创建ITelemetryInitializer
并将其注册为单例。 App Insights将通过DI框架发现并使用它。
class RequestScopedTelemetryInitializer : ITelemetryInitializer
{
readonly IHttpContextAccessor httpContextAccessor;
public RequestScopedTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
=> this.httpContextAccessor = httpContextAccessor;
public void Initialize(ITelemetry telemetry)
{
// Attempt to resolve the request-scoped telemetry from the DI container
var requestScopedTelemetry = httpContextAccessor
.HttpContext?
.RequestServices?
.GetService<RequestScopedTelemetry>();
// RequestScopedTelemetry is only available within an active request scope
// If no telemetry available, just move along...
if (requestScopedTelemetry == null)
return;
// If telemetry was available, add it to the App Insights telemetry collection
telemetry.Context.GlobalProperties[nameof(RequestScopedTelemetry.MyCustomProperty)]
= requestScopedTelemetry.MyCustomProperty;
}
}
services.AddSingleton<ITelemetryInitializer, RequestScopedTelemetryInitializer>();
最后,在控制器方法中,设置每个请求的值。如果您的遥测类能够本身获取或生成数据,则不需要此部分。
public class ExampleController : ControllerBase
{
readonly RequestScopedTelemetry telemetry;
public ValuesController(RequestScopedTelemetry telemetry)
=> this.telemetry = telemetry;
[HttpGet]
public ActionResult Get()
{
telemetry.MyCustomProperty = "MyCustomValue";
// Do what you want to
return Ok();
}
}
答案 2 :(得分:0)
为了将每个请求的数据添加到遥测中,您需要一种在请求内共享数据的方法。一种可靠的方法是使用HttpContent.Items
property,它基本上是一个字典。
您可以创建一项服务,以将词典包含在HttpContent.Items
内,其中包含您希望在遥测中使用的所有自定义数据(键前缀用于确保我们稍后仅在Initializer中读取想要的内容)
public class LogTelemetryRequest
{
private const string KEY_PREFIX = "CustomTelemetryData_";
private readonly IHttpContextAccessor _httpContextAccessor;
public LogTelemetryRequest(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void AddProperty(string key, string value)
{
_httpContextAccessor.HttpContext.Items[KEY_PREFIX + key] = value;
}
}
将其注册为Startup.cs
中的范围:
services.AddScoped<LogTelemetryRequest>();
在控制器中使用它:
private LogTelemetryRequest logTelemetryRequest;
public ValueController(LogTelemetryRequest logTelemetryRequest)
{
this.logTelemetryRequest = logTelemetryRequest;
}
[HttpGet]
public async Task<IActionResult> RouteOne([FromQuery(Name = "param1")]string param1, [FromQuery(Name = "param2")]string param2)
{
// telemetryClient.Context.GlobalProperties["param1"] = param1;
// telemetryClient.Context.GlobalProperties["param2"] = param2;
logTelemetryRequest.AddProperty("param1", param1);
logTelemetryRequest.AddProperty("param2", param2);
}
然后在初始化程序中读取它:
public class AddCustomTelemetryInitializer : ITelemetryInitializer
{
private const string KEY_PREFIX = "CustomTelemetryData_";
private readonly IHttpContextAccessor _httpContextAccessor;
public AddCustomTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry == null) return;
foreach (var item in _httpContextAccessor.HttpContext.Items)
{
if (item.Key is string key && key.StartsWith(KEY_PREFIX))
requestTelemetry.Properties.Add(key, item.Value.ToString());
}
}
}
理想情况下,LogTelemetryRequest
应该使用接口进行注册,并且键前缀应该是单个共享常量,为简单起见,请不要这样做。