Aspnetcore.docs: How to log automatic 400 responses on model validation errors

Created on 25 Apr 2019  ·  11Comments  ·  Source: dotnet/AspNetCore.Docs

“The ApiController attribute makes model validation errors automatically trigger an HTTP 400 response.”
It is not described, does details of 400 errors logged or not?
If not, is it possible to configure error details to be logged?
Or I have to SuppressModelStateInvalidFilter = true; and do logging by myself in the block
if (!ModelState.IsValid)
{
Log (ModelState);
return BadRequest(ModelState);
}


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

P1 Source - Docs.ms

Most helpful comment

There are a few ways to approach this, but I'll list a couple of ideas to get you started:

  1. Perform the logging and return a BadRequestObjectResult:

```c#
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
// Get an instance of ILogger (see below) and log accordingly.

        return new BadRequestObjectResult(context.ModelState);
    };
});
With this option, the response will not be wrapped in `ProblemDetails`, regardless of whether or not that is configured. If you do want a `ProblemDetails` wrapper, you can follow the example in the [docs](https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-2.2#customize-badrequest-response) or...

2. Perform the logging and invoke the existing functionality that generates the response. This is a little more involved as you have to add a [`PostConfigure<>`](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.2#options-post-configuration) step rather than using the `ConfigureApiBehaviorOptions` call to make sure that the built-in setup adds the `ProblemDetails` wrapper first (if it's configured to do so):

```c#
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

services.PostConfigure<ApiBehaviorOptions>(options =>
{
    var builtInFactory = options.InvalidModelStateResponseFactory;

    options.InvalidModelStateResponseFactory = context =>
    {
        // Get an instance of ILogger (see below) and log accordingly.

        return builtInFactory(context);
    };
});

This option sets up a wrapper function that first performs your custom logging and then invokes the existing factory function. This ensures that the default functionality that comes out-of-the-box will work as it did before, but it might be overkill if you do want to take full control of the response or don't need ProblemDetails support.


Getting an instance of ILogger

You can use the context parameter that gets passed in to the InvalidModelStateResponseFactory delegate to get a logger from DI. There are a few options for this:

  1. Resolve ILogger<Startup> from DI and use that:

```c#
var logger = context.HttpContext.RequestServices
.GetRequiredService>();

   This has the downside that the category for the log messages will be the full name of the `Startup` class, but the ActionName is included in the log context so that information will also be logged.

2. Resolve `ILoggerFactory` from DI and create a logger with a custom category:

```c#
var loggerFactory = context.HttpContext.RequestServices
    .GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger("YourCategory");
  1. Resolve ILoggerFactory from DI and create a logger with the full name of the action method as the category:

c# var loggerFactory = context.HttpContext.RequestServices .GetRequiredService<ILoggerFactory>(); var logger = loggerFactory.CreateLogger(context.ActionDescriptor.DisplayName);

All 11 comments

There's a section in the docs that explains how to customise the BadRequest response when using the built-in filter. You can set the InvalidModelStateResponseFactory to a custom function that first performs the logging and then returns an appropriate BadRequestObjectResult.

@Rick-Anderson I can provide an example of what that would look like here if it would be helpful. Otherwise, is this something that could be taken over to Stack Overflow with a link. What's the best approach for handling something like that?

@serpent5 an example in this issue would be great. We can add a link to this issue in the doc and close the issue.

There are a few ways to approach this, but I'll list a couple of ideas to get you started:

  1. Perform the logging and return a BadRequestObjectResult:

```c#
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
// Get an instance of ILogger (see below) and log accordingly.

        return new BadRequestObjectResult(context.ModelState);
    };
});
With this option, the response will not be wrapped in `ProblemDetails`, regardless of whether or not that is configured. If you do want a `ProblemDetails` wrapper, you can follow the example in the [docs](https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-2.2#customize-badrequest-response) or...

2. Perform the logging and invoke the existing functionality that generates the response. This is a little more involved as you have to add a [`PostConfigure<>`](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.2#options-post-configuration) step rather than using the `ConfigureApiBehaviorOptions` call to make sure that the built-in setup adds the `ProblemDetails` wrapper first (if it's configured to do so):

```c#
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

services.PostConfigure<ApiBehaviorOptions>(options =>
{
    var builtInFactory = options.InvalidModelStateResponseFactory;

    options.InvalidModelStateResponseFactory = context =>
    {
        // Get an instance of ILogger (see below) and log accordingly.

        return builtInFactory(context);
    };
});

This option sets up a wrapper function that first performs your custom logging and then invokes the existing factory function. This ensures that the default functionality that comes out-of-the-box will work as it did before, but it might be overkill if you do want to take full control of the response or don't need ProblemDetails support.


Getting an instance of ILogger

You can use the context parameter that gets passed in to the InvalidModelStateResponseFactory delegate to get a logger from DI. There are a few options for this:

  1. Resolve ILogger<Startup> from DI and use that:

```c#
var logger = context.HttpContext.RequestServices
.GetRequiredService>();

   This has the downside that the category for the log messages will be the full name of the `Startup` class, but the ActionName is included in the log context so that information will also be logged.

2. Resolve `ILoggerFactory` from DI and create a logger with a custom category:

```c#
var loggerFactory = context.HttpContext.RequestServices
    .GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger("YourCategory");
  1. Resolve ILoggerFactory from DI and create a logger with the full name of the action method as the category:

c# var loggerFactory = context.HttpContext.RequestServices .GetRequiredService<ILoggerFactory>(); var logger = loggerFactory.CreateLogger(context.ActionDescriptor.DisplayName);

@Rick-Anderson Added link to the doc in #12315 per your plan --

We can add a link to this issue in the doc and close the issue.

There are a few ways to approach this, but I'll list a couple of ideas to get you started:
...

It seems too complex, I just wanna a log..

@RobinHSanner, did you mean “disable the automatic 400 _error handling_”? We want to enable _logging_. And we need to log before return, as I asked at the beginning of the thread

if (!ModelState.IsValid)
{
    LogErrors(ModelState);
    return BadRequest(ModelState);
}

I want to keep "automatic 40x error handling" and log custom log.
For example, if 40x occur, log the user post form params.

Yes, that's what I meant. Thank you for the clarification. Sorry I obviously didn't read carefully enough. I feel pretty stupid for suggesting what you said you were having to do. I obviously have to stop skimming and read more carefully.

I still feel this is complicated to log errors. Probably we need to have something as simple as enabling InvalidModelStateErrors that also logs the details of the request.

var loggerFactory = context.HttpContext.RequestServices
    .GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger(ctx.ActionDescriptor.DisplayName);

@serpent5 Thanks for the exaples. By the way, you meant context.ActionDescriptor.DisplayName right?

@jerrychoubear You're welcome. You're right that it should be context instead of ctx. I've updated the original example. Thanks for letting me know.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

wgutierrezr picture wgutierrezr  ·  3Comments

royshouvik picture royshouvik  ·  3Comments

serpent5 picture serpent5  ·  3Comments

danroth27 picture danroth27  ·  3Comments

Raghumu picture Raghumu  ·  3Comments