I'm trying to work out if it's possible to see the detail of why a 400 error occurred within an application. I think knowing the property that doesn't have a value would allow for better targeting of investigation into the bug. For example, knowing it was the "name" property.
I have app insights configured in Azure. It successfully collects 400 responses made by the web app but shows no detail.
Specifically in this case I am seeing the 400 generated by this code within an ActionFilterAttribute
.
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
The content of the response will contain the JSON serialised ModelState
value.
When this happens App Insights is logging the error saying that the request failed with 400 status code. It shows none of the ModelState
values.
The key piece of information that I actually want to be able to see is NOT the content of the model (That is likely to contain sensitive information) but the collection of the details of the ModelState.Values
collection essentially it equates to the following properties:
ModelState.Values.Keys.Key
ModelState.Values.Keys.Errors()
The invalid properties would have ModelState.Values.Keys.ValidationState
set to false.
So far what I've found seems to suggest that I would approach this by just manually getting a TelemetryClient
and adding another event to App Insights.
Is there a way that I can append this detail to any request that has a 400 exception? Or would I need to add another custom call to the TelemetryClient
and just track it that way?
If the latter can I add it into the ActionFilterAttribute
in some way so it's all handled in there?
Kind of feel like I'm not understanding something simple that will make it all make sense.
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.
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.