Approach to bind all NLog logs to the original request in WebAPI?

I am working on creating an API using WebAPI and use NLog to log across the stack. My API solution has two main projects, including:

  • Website level that implements controllers and web files
  • A service level that implements async commands and handlers in CQRS mode.

What I'm trying to achieve is to automatically create a unique identifier that I can attach to the log statements so that any logs written while serving a single request, no matter what layer they come from, can be associated with this original request. I would also like this to work without passing a unique identifier, or with the statements of the journal itself, including it in its calls.

To this end, I began to study the possibility of writing a custom delegation handler to intercept each request (after this post for guidance) and add a unique identifier as a property in NLog. I ended up with the following:

/// <summary>
/// This class is a WebAPI message handler that helps establish the data and operations needed
/// to associate log statements through the entire stack back to the originating request.
/// 
/// Help from here: http://weblogs.asp.net/fredriknormen/log-message-request-and-response-in-asp-net-webapi
/// </summary>
public class InitializeLoggingMessageHandler : DelegatingHandler
{
    private ILogger _logger;

    // The logger is injected with Autofac
    //
    public InitializeLoggingMessageHandler(ILogger logger)
    {
        _logger = logger;
    }

    protected async override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        // Get a unique ID for this request
        //
        var uniqueId = Guid.NewGuid().ToString();

        // Now that we have a unique ID for this request, we add it to NLog MDC as a property
        //  we can use in the log layouts. We do NOT use the global diagnostic context because
        //  WebAPI is multi-threaded, and we want the ID to be scoped to just the thread servicing
        //  this request.
        //
        NLog.MappedDiagnosticsContext.Set("UniqueId", uniqueId);

        // Capture some details about the request for logging
        //
        var requestInfo = string.Format("{0} {1}", request.Method, request.RequestUri);
        var requestMessage = await request.Content.ReadAsByteArrayAsync();

        _logger.Info("Request: {0} - {1}", requestInfo, Encoding.UTF8.GetString(requestMessage));

        var response = await base.SendAsync(request, cancellationToken);

        return response;
    }
}

With this code, I can then use the unique identifier in the log layouts like this:

<target xsi:type="Debugger" name="DebugLogger" 
        layout="${longdate} ${logger} ${mdc:item=UniqueId} ${message}" />

The problem with this approach is that I am using the NLog MappedDiagnosticsContext to try to save a unique identifier as a property that can be used in layouts (so my logging code doesn't have to know). This is a local-local mechanism for storing values, so it is interrupted when you have asynchronous code, because the thread that starts the request may not be the one that does all this.

, , , , , , . GlobalDiagnosticsContext NLog, , WebAPI , .

, , WebAPI, , ?

+3
1

LogicalCallContext. .NET 4.5, async.

. :

.NET Framework , "" . , , AppDomains .

+5

Source: https://habr.com/ru/post/1609372/


All Articles