.Net 6 with NLog not Registering my Custom Render Layout

Viewed 33

In a .Net 6 Web API using NLog, how can I intercept the posted body so I can manipulate it before logging it?

Here's what I've got:

I've got a .Net Framework 4.7.2 Web API with a DelegatingHandler to intercept a request's body. I then go through each key and replace the value if the key is in a list of strings. For example: if the key is Password or Pwd I replace the value with xxx so that I can log the full body without security concerns. it's worked very well. I'm trying to replicate this on a .Net 6 Web API Project.

Here's what I've done:

I've looked through the NLog source code to see how they get the aspnet-request-posted-body. I believe I've got something but I don't see NLog calling/using my RenderLayout.

[LayoutRenderer("aspnet-request-posted-body-2")]
public class TestRender : AspNetLayoutRendererBase {
    protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) {
        var items = HttpContextAccessor.HttpContext?.Items;
        if (items == null || items.Count == 0) {
            return;
        }

        if (items.TryGetValue("aspnet-request-posted-body-2", out var value)) {
            builder.Append(value as string);
        }
    }
}

My Middleware

public class RequestLoggerMiddleware {
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggerMiddleware> _logger;

    public RequestLoggerMiddleware(RequestDelegate next, ILogger<RequestLoggerMiddleware> logger) {
        _next = next;
        _logger = logger;
    }

    public async Task Invoke(HttpContext context) {
        var requestBody = await new HttpContextExtensions().PeekBodyAsync(context);
        var contentType = context.Request.ContentType;

        // Not done yet, but adding this for testing
        switch (contentType) {
            case "application/x-www-form-urlencoded":
                // Clean data
                ...
                // Add to context
                context.Items.Add("aspnet-request-posted-body-2", requestBody);
                break;
            case "application/*+json":
            case "application/json":
            case "text/json":
                // Clean data
                ...
                // Add to context
                context.Items.Add("aspnet-request-posted-body-2", JsonSerializer.Serialize(requestBody));
                break;
            default:
                break;
        }

        _logger
            .LogInformation("TESTING");
        
        // With a breakpoint I checked that `content` does have the item listed above
        await _next.Invoke(context);
    }
}

Here's is my Program.cs file

var logger = LogManager
            .LoadConfiguration(string.Concat(Directory.GetCurrentDirectory(), "/Configs/NLog/nlog.config"))
            .Setup()
            .SetupExtensions(x => {
                x.RegisterLayoutRenderer<TestRender>("aspnet-request-posted-body-2");
            })
            .GetCurrentClassLogger();
logger.Debug("init main");

try {
    var builder = WebApplication.CreateBuilder(args);

    builder.Logging.ClearProviders();
    builder.Host.UseNLog();

    var app = builder.Build();

    // Register NLog's Request Body Layout
    app.UseMiddleware<NLogRequestPostedBodyMiddleware>();
    // Register my custom layout
    app.UseMiddleware<RequestLoggerMiddleware>();

    app.UseHttpsRedirection();

    app.UseAuthorization();

    app.MapControllers();

    app.Run();
} catch (Exception exception) {
    logger.Error(exception, "Stopped program because of exception");
    throw;
} finally {
    LogManager.Shutdown();
}
1 Answers

When you store the request-body in the HttpContext.Items-dictionary, then you don't need to register custom layout. Just use ${aspnet-item:aspnet-request-posted-body-2} in your NLog.config. See also: https://github.com/NLog/NLog/wiki/AspNet-HttpContext-Item-Layout-Renderer

Maybe consider removing NLogRequestPostedBodyMiddleware since you already have your own RequestLoggerMiddleware.

Btw. make sure to hookup SetupExtensions before loading logging-configuration that depends on those extensions. Ex. LogManager.Setup().SetupExtensions(...).LoadConfigurationFromFile(string.Concat(Directory.GetCurrentDirectory(), "/Configs/NLog/nlog.config")

Related