Logging ASP.NET Web API 2 Failed Request Body to Application Insights

Written by Ken Dale

Debugging web applications can be difficult sometimes. When debugging a failed HTTP GET request in Application Insights you have all of the information. But, when looking at failed HTTP POST/PUT/PATCH requests the request body is an important part in reproducing an issue. And, without the request body, debugging an issue can be difficult.

using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using System.Diagnostics;
using System.IO;
using System.Web;

namespace YourNamespace
{
    // Reference: https://thirum.wordpress.com/2019/08/19/logging-the-http-response-body-in-application-insights/
    //
    // Ideally this is deprecated in the future for functionality baked into Application Insights
    // (although, it doesn't state whether they plan to support ASP.NET full framework with this or not):
    // https://github.com/Microsoft/ApplicationInsights-aspnetcore/issues/686#issuecomment-545531512
    public class FailedRequestBodyTelemetry : ITelemetryInitializer
    {
        private const string RequestBodyProperty = "RequestBody";

        public void Initialize(ITelemetry telemetry)
        {
            var requestTelemetry = telemetry as RequestTelemetry;

            if (requestTelemetry == null || HttpContext.Current == null)
            {
                return;
            }

            var request = HttpContext.Current.Request;
            var response = HttpContext.Current.Response;

            if (request == null || response == null)
            {
                return;
            }

            if (!int.TryParse(requestTelemetry.ResponseCode, out var responseCode))
            {
                return;
            }

            if (responseCode < 400) // non-success status code
            {
                return;
            }

            if (!request.InputStream.CanSeek)
            {
                Trace.WriteLine("Failed request body was not added to the Application Insights telemetry due to non-buffered input stream.");
                return;
            }

            using (var streamReader = new StreamReader(request.InputStream, request.ContentEncoding, true, 1024, true))
            {
                request.InputStream.Position = 0;

                var requestContent = streamReader.ReadToEnd();

                request.InputStream.Position = 0;

                if (string.IsNullOrEmpty(requestContent))
                {
                    return;
                }

                if (requestTelemetry.Properties.ContainsKey(RequestBodyProperty))
                {
                    requestTelemetry.Properties[RequestBodyProperty] = requestContent;
                }
                else
                {
                    requestTelemetry.Properties.Add(RequestBodyProperty, requestContent);
                }
            }
        }
    }
}

You’ll need this too to enable request body rewinding:

using System.Web.Http.WebHost;

namespace YourNamespace
{
    public class InputStreamAlwaysBufferedPolicySelector : WebHostBufferPolicySelector
    {
        public override bool UseBufferedInputStream(object hostContext)
        {
            return true;
        }
    }
}

Now, we can wire these up:

using Microsoft.ApplicationInsights.Extensibility;
using Owin;
using System;
using System.Web.Http;
using System.Web.Http.Hosting;

namespace YourNamespace
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var config = new HttpConfiguration();

            config.Services.Replace(
                typeof(IHostBufferPolicySelector),
                new InputStreamAlwaysBufferedPolicySelector());

            app.UseWebApi(config); 

            TelemetryConfiguration.Active.TelemetryInitializers.Add(new FailedRequestBodyTelemetry());
        }
    }
}

Note

Request bodies may contain sensitive information. Depending on your data it may make sense to log this only in a non-production environment. And, if you need to scrub the data prior to sending it, make sure you do so!

Published November 04, 2019 by

undefined avatar
Ken Dale Github Senior Application Developer (Former)

Suggested Reading