MattW
MattW

Reputation: 13212

Retrieving HttpContext in a Custom NLog Target

I may me missing something basic here - but is it possible to retrieve the HttpContext.Current in a custom NLog event?

I am trying to give each request a unique Guid so that I can correlate logging messages to a single event (i.e, tie together each log event for a single request). So, I want to store this Guid in HttpContext.Current.Items, then retrieve it in the NLog target and include it in the log message.

Here is my example target where I'd like to access HttpContext.Current:

[Target("AzureTableTarget")]
public class AzureTableTarget : TargetWithLayout
{

    public AzureTableTarget()
    {
        _appSettings = IoCResolver.Get<IAppSettings>();
    }

    protected override void Write(LogEventInfo logEvent)
    {
        var correlationId = HttpContext.Current; //This is always null

        var batchOperation = new TableBatchOperation();
        CxLogEventBuilder.Build(_appSettings, logEvent).ForEach(batchOperation.Insert);
        _loggingTable.ExecuteBatchAsync(batchOperation);
    }
}

Upvotes: 6

Views: 3434

Answers (3)

Rolf Kristensen
Rolf Kristensen

Reputation: 19867

If your custom target should capture one (or more) context-specific values, then I recommend that your target inherits from TargetWithContext (or AsyncTaskTarget).

It gives the ability to setup and capture contextproperty-items. Where the Layout can be assigned to capture context-details. Examples of possible context-details easily available from HttpContext:

https://nlog-project.org/config/?tab=layout-renderers&search=package:nlog.web.aspnetcore

For more details about writing custom-targets:

https://github.com/NLog/NLog/wiki/How-to-write-a-custom-target-for-structured-logging

https://github.com/NLog/NLog/wiki/How-to-write-a-custom-async-target

Btw. there already exists this custom target that nicely inherits from AsyncTaskTarget:

https://www.nuget.org/packages/NLog.Extensions.AzureCosmosTable/

Upvotes: 1

Julian
Julian

Reputation: 36780

Nowadays it's easier to retrieve the HTTP Context in a NLog target (works for ASP.NET and ASP.NET Core)

  1. Install NLog.Web (ASP.NET) or NLog.Web.AspNetCore (ASP.NET Core) package
  2. For ASP.NET core, follow the ASP.NET Core - NLog setup
  3. Inherit from AspNetLayoutRendererBase (namespace NLog.Web.LayoutRenderers)
  4. Get the request by calling var context = HttpContextAccessor.HttpContext;

Example:

[LayoutRenderer("aspnet-sessionid")]
[ThreadSafe]
public class AspNetSessionIdLayoutRenderer : AspNetLayoutRendererBase
{
    protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
    {
        var context = HttpContextAccessor.HttpContext;
        var contextSession = context?.Session();
        if (contextSession == null)
        {
             InternalLogger.Debug("HttpContext Session Lookup returned null");
             return;
        }

        builder.Append(contextSession.SessionID); // ASP.NET Core: contextSession.Id
    }
}

PS: there are currently many predefined renderers for ASP.NET (Core): https://nlog-project.org/config/?tab=layout-renderers&search=aspnet

Upvotes: 1

wageoghe
wageoghe

Reputation: 27618

This article about Working with HttpContext.Current might help. The key, for you, might be that when control passes from one thread to another HttpContext.Current in the new thread can be null.

Here is another question/answer from here on SO that describes HttpContext.Current being null in the context of a web service. The accepted answer suggests turning on ASP.Net compatibility in your web.config file.

I don't know of either of these will help, but they might. I found them by googling for "HttpContext.Current is null", which yielded quite a number of hits. I have done very little ASP.NET development, so I can't really comment on HttpContext.Current from my own personal experience.

Given your use case, I would suggest that you look into System.Diagnostics.CorrelationManager.ActivityId.

One nice feature of ActivityId is that it is "flowed" from parent threads to child threads (including thread pool threads). I think that it works well with Tasks and Parallel operations. Works well meaning that the ActivityId, as set in a parent thread, has the expected value in a child thread.

There is not a LayoutRenderer for ActivityId, but it easy enough to write one. See an example (written against NLog 1.0) here:

Most useful NLog configurations

I'm pretty sure that the "EstimatedBufferSize" stuff is no longer needed, so something like will probably work:

[LayoutRenderer("ActivityId")]
class ActivityIdLayoutRenderer : LayoutRenderer
{
  protected override void Append(StringBuilder builder, LogEventInfo logEvent)
  {
    builder.Append(Trace.CorrelationManager.ActivityId);
  }
}

If you go this route, you might consider adding a Format property to the ActivityIdLayoutRenderer to allow you to specify the guid format. See this answer (from me). It contains a lot of useful information about working with guids.

NewGuid vs System.Guid.NewGuid().ToString("D");

See this source file (in NLog's git repository) for an example of how you can implement and use such a Format property:

https://github.com/NLog/NLog/blob/master/src/NLog/LayoutRenderers/GuidLayoutRenderer.cs

Upvotes: 0

Related Questions