Logging ASP.NET Web API 2 Failed Request Body to Application Insights
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!