ASP.NET Core Application Insights 400 ModelState Details

Solution 1:

For anyone who comes across this question, we ended up creating a result filter in ASP.Net Core that automatically logs the model state as json when a 400 response is generated. It provides us everything we need to help troubleshooting the errors when they occur.

public sealed class Log400DetailsResultFilter : IResultFilter
{
    private readonly ILogger<Log400DetailsResultFilter> _log;

    public Log400DetailsResultFilter(ILogger<Log400DetailsResultFilter> log)
    {
        _log = log;
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        if (context.HttpContext.Response.StatusCode == (int)HttpStatusCode.BadRequest && context.ModelState.ErrorCount > 0)
        {
            var errors = new SerializableError(context.ModelState);
            _log.LogError($"Bad Request Model State Errors: {JsonConvert.SerializeObject(errors)}");
        }
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {

    }
}

Then just register it in startup and it will run for all 400 results.

services.AddMvc(config =>
{
    config.Filters.Add<Log400DetailsResultFilter>();
});

In Application Insights you can then just click the "View All Telemetry" button from the "End-to-end transaction details" blade and it will show you all of the logs for that transaction including the details of the model state.

enter image description here

Edit 16/01/2020 The filter registration code has changed in .NET Core 3.0. This is the new registration code:

services.AddControllers(config =>
{
    config.Filters.Add<ModelValidationResultFilter>();
});

Edit 14/01/2021 You can now achieve something similar with the built in HTTP Logging, see here for full details.

Solution 2:

You can try leveraging Telemetry Initializers in AI SDK. This GutHub repo has an example on how to update properties on Request Telemetry being collected, however, it does not touch on how to specifically work in case of ModelState context properties, but if that context is also available, you should be able to use the properties you're looking for.

It should look along these lines:

public class PropertyTelemetryInitializer : ITelemetryInitializer
{
    IHttpContextAccessor httpContextAccessor;

    public PropertyTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
    }

    public void Initialize(ITelemetry telemetry)
    {
        if (telemetry is RequestTelemetry request)
        {
            request.Context.Properties["tenantName"] = httpContextAccessor.Value.Items["tenantName"].ToString();
        }
    }
}

Then register this in DI:

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITelemetryInitializer, PropertyTelemetryInitializer>();
services.AddApplicationInsightsTelemetry()
...
}