在控制器级别向遥测请求添加自定义属性

时间:2019-02-19 21:08:01

标签: c# azure asp.net-core .net-core azure-application-insights

我正在尝试为每条路线的遥测请求添加特定属性。 深入研究之后,我发现可以通过实现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;           
    }
}

3 个答案:

答案 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应该使用接口进行注册,并且键前缀应该是单个共享常量,为简单起见,请不要这样做。