0
votes

I'm trying to get some extra information into Application insights from our .net core MVC application. I've found the following post: Adding custom properties for each request in Application Insights metrics

In the answer they use an custom telemetry initializer and that works if you want some request data or something.

Now we have an set of middleware in our application. They translate some of the headers into readable content.

Of course we could log the headers and search on all different values that those can have. But we would like to have the result from the middleware into the properties of application insights.

Anyone an idee on how to use the some of the outcome of the middleware into properties of the Application Insights request telemetry?

2
Could you clarify what is the outcome of the middleware and where do you store the result of this? Because a can't understand why logging outcome data is a more preferable solution than logging header. Do you store result of middleware work in the HttpContext.Items?svoychik
Yes al the values are stored in the HttpContext.Items. For example, it is an multi tenant application. All tenants have multiple ApiKeys. In the header is the ApiKey but it would be great to have the tenant id that is retrieved in the middleware and stored in an HttpContext Item.C. Molendijk
@svoychik helpt me with the solution. By adding the HttpContextAccessor in the constructor of the custom telemetery initializer you can access the http context and the items of the context. The Initialize function is called multiple times. In the end, the http context has the items. This way you can add those values to the properties of Application Insights.C. Molendijk

2 Answers

5
votes

Got the right idea from @svoychik. The middleware adds the output values to the HttpContext.Items. See the example:

using Microsoft.AspNetCore.Http;
using System.Text;
using System.Threading.Tasks;

namespace Test.API.Middleware
{
    public class ValueMiddleware
    {
        private readonly RequestDelegate next;

        public ApiKeyMiddleware(RequestDelegate next)
        {
            this.next = next;
        }

        public async Task Invoke(HttpContext httpContext)
        {
            if (!context.Items.ContainsKey("ApplicationData"))
            {
                httpContext.Items["ApplicationData"] = "Important Data";
            }
        }
    }
}

Then when you need to get all those items into application insights you can just use the following Initializer:

using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.AspNetCore.Http;

namespace Test.API.TelemetryInitializers : ITelemetryInitializer
{
    public class HttpContextItemsTelemetryInitializer
    {
        private readonly IHttpContextAccessor httpContextAccessor;
        public HttpContextItemsTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
        {
            this.httpContextAccessor = httpContextAccessor;
        }

        public void Initialize(ITelemetry telemetry)
        {
            var context = httpContextAccessor.HttpContext;
            if (context == null)
            {
                return;
            }

            foreach (var item in context.Items)
            {
                var itemKey = item.Key.ToString();

                // Remove some pollution that Microsoft and the systems adds to the HttpContext Items.
                if (itemKey.Contains("Microsoft") || itemKey.Contains("System"))
                {
                    continue;
                }

                if (!telemetry.Context.GlobalProperties.ContainsKey(itemKey))
                {
                    telemetry.Context.GlobalProperties.Add(itemKey, item.Value.ToString());
                }
            }
        }
    }
}

Setup the initializer and the application insights in your Startup.cs as in the following example:

using Test.API.TelemetryInitializers;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;

namespace Test.API
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            services.AddSingleton<ITelemetryInitializer, HttpContextItemsTelemetryInitializer>();
            services.AddApplicationInsightsTelemetry();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMiddleware<ValueMiddleware>();
            app.UseMvc();
        }
    }
}

Then it just add all the values of HttpContext.Items to your application insights.

0
votes

Instead of putting the data within the middleware into HttpContext and running an TelemetryInitializer, you could directly add your desired data into the telemetry object within the middleware:

public class TelemetryMiddleware
{
    private const string _BodyKey = "Body";
    private readonly RequestDelegate _next;

    public TelemetryMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        httpContext.Request.EnableBuffering();

        if (httpContext.Request.Body.CanRead
            && (httpContext.Request.Method == HttpMethods.Put
                || httpContext.Request.Method == HttpMethods.Post
                || httpContext.Request.Method == HttpMethods.Patch))
        {
            // The needed method to access telemetry object within middleware
            var telemetry = httpContext.Features.Get<RequestTelemetry>();

            if (telemetry != null
                && !telemetry.Properties.ContainsKey(_BodyKey))
            {
                var oldPosition = httpContext.Request.Body.Position;
                httpContext.Request.Body.Position = 0;

                using (var reader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, false, 4096, true))
                {
                    var body = await reader.ReadToEndAsync();

                    if (!string.IsNullOrEmpty(body))
                        telemetry.Properties.Add(_BodyKey, body);
                }

                httpContext.Request.Body.Position = oldPosition;
            }
        }

        await _next(httpContext);
    }
}

Building into the pipeline is the same as for every middleware:

app.UseMiddleware<TelemetryMiddleware>();