3

I want to include headers in the request events in Application Insights and found the following post that has a solution for application with a HttpContext. I'm working with a Nancy application where the request headers are stored in the NancyContext. Problem is that Nancy doesn't provide a static accessor like HttpContext.Current so I wonder how I can solve it.

I've made two attempts without succeeding. The first was to build an ITelemetryInitializer like described in the link below but then I could not access the NancyContext.

https://blogs.msdn.microsoft.com/stuartleeks/2016/11/03/including-headers-for-request-events-with-application-insights/

My second attempt was to pass the NancyModule to a static function that added the request headers to the ITelemetryContext but then I could not get a hold of the current ITelemetryContext instead.

Has anyone else faced and solved this issue?

OriginalUtter
  • 619
  • 3
  • 15
  • 28

3 Answers3

10

Actually, there is an example for .NET Framework: https://blogs.msdn.microsoft.com/stuartleeks/2016/11/03/including-headers-for-request-events-with-application-insights/

In .NET Core, you just access HttpContext using IHttpContextAccessor instead of HttpContext.Current:

public class TelemetryHeadersInitializer : ITelemetryInitializer
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public List<string> RequestHeaders { get; set; }
    public List<string> ResponseHeaders { get; set; }

    public TelemetryHeadersInitializer(IHttpContextAccessor httpContextAccessor)
    {
        RequestHeaders = new List<string> { "Referer" }; //whatever you need
        ResponseHeaders = new List<string> { ... };
        _httpContextAccessor = httpContextAccessor;
    }

    public void Initialize(ITelemetry telemetry)
    {
        var requestTelemetry = telemetry as RequestTelemetry;
        // Is this a TrackRequest() ?
        if (requestTelemetry == null) return;

        var context = _httpContextAccessor.HttpContext;
        if (context == null) return;

        foreach (var headerName in RequestHeaders)
        {
            var headers = context.Request.Headers[headerName];
            if (headers.Any())
            {
                telemetry.Context.Properties.Add($"Request-{headerName}", string.Join(Environment.NewLine, headers));
            }             
        }
        foreach (var headerName in ResponseHeaders)
        {
            var headers = context.Response.Headers[headerName];
            if (headers.Any())
            {
                telemetry.Context.Properties.Add($"Response-{headerName}", string.Join(Environment.NewLine, headers));
            }
        }
    }
}

//Services.cs:
services.AddSingleton<ITelemetryInitializer, TelemetryHeadersInitializer>();

Also check: https://github.com/Microsoft/ApplicationInsights-aspnetcore/wiki/Custom-Configuration

Liero
  • 25,216
  • 29
  • 151
  • 297
1

Your second approach should work, and you can use an existing extension method to get the current request telemetry, if there is one.

the method is method: HttpContextExtension.GetRequestTelemetry(https://github.com/Microsoft/ApplicationInsights-dotnet-server/blob/2a681f5399a6aaee554fa2d93a1d6447a8402fe1/Src/Web/Web.Shared.Net/HttpContextExtension.cs#L16)

that will give you back the current request telemetry for a given HttpContext, so you should be able to do:

 var requestTelemetry = HttpContext.Current?.GetRequestTelemetry();
 // add whatever you need to the request telemetry?

from inside your Nancy module?

John Gardner
  • 24,225
  • 5
  • 58
  • 76
  • Did not manage to get the RequestTelemetry that way. Maybe because my project is AspNetCore and it uses `Microsoft.AspNetCore.Http.HttpContext` and not `System.Web.HttpContext`? – OriginalUtter Mar 14 '18 at 18:35
  • 1
    the best answer for me asp net (net 452) – zolty13 Jun 27 '19 at 12:19
0

I found a solution, might not be the prettiest but at least it seems to work.

I created a statically accessible AsyncLocal instance to hold the RequestTelemetry object for each thread.

public class RequestVariables
{
    public static AsyncLocal<RequestTelemetry> RequestTelemetry = new AsyncLocal<RequestTelemetry>();
}

I set the value of RequestVariables.RequestTelemetry in an ITelemetryInitializer

public class RequestTelemetryInitializer : ITelemetryInitializer
{
    public void Initialize(ITelemetry telemetry)
    {
        if (telemetry is RequestTelemetry requestTelemetry)
            RequestVariables.RequestTelemetry.Value = requestTelemetry;
    }
}

Then I registered a small middleware in the Startup.Configure(...) method which fetches the RequestTelemetry instance that was set by the ITelemetryInitializer and adds the request headers to it.

app.Use((context, next) =>
{
    var requestTelemetry = RequestVariables.RequestTelemetry.Value;
    if (requestTelemetry?.Context == null) return next();

    foreach (var header in context.Request.Headers)
    {
        if (!requestTelemetry.Context.Properties.ContainsKey(header.Key))
            requestTelemetry.Context.Properties.Add(header.Key, header.Value.First());
    }

    return next();
});
OriginalUtter
  • 619
  • 3
  • 15
  • 28