I am using NLog 4.3.5 and .Net framework 4.6.1
When I begin a server side operation I call:
NLog.MappedDiagnosticsLogicalContext.Set("OperationId", Guid.NewGuid());
This gets mapped through and appears in my log files. All is good.... or is it? When reviewing my log files, I noticed that this operation id value doesn't seem to be working as I expected it to.
Example:
In thread 19 an operation begins and sets the context.
It uses .ConfigureAwait(false) on all await calls
It performs a
var tasks = items.Select(item => Task.Run( () => { /* do stuff */}
await Task.WhenAll(tasks).ConfigureAwait(false)
I did not expect this to happen and am unsure of how it happened. But, as I look through my log history, I see that this type of thing has happened before.
I thought the logical call context was supposed to carry over. Is it my use of ConfigureAwait(false) that is causing this behavior? That is the only thing I can think of....
Found what I believe is the problem. https://github.com/NLog/NLog/issues/934
You could work around this as follows:
public static class LogicalThreadContext  
{ 
    private const string KeyPrefix = "NLog.LogicalThreadContext"; 
    private static string GetCallContextKey(string key)
    {
        return string.Format("{0}.{1}", KeyPrefix, key);
    }
    private static string GetCallContextValue(string key)
    {
        return CallContext.LogicalGetData(GetCallContextKey(key)) as string ?? string.Empty;
    }
    private static void SetCallContextValue(string key, string value)
    {
        CallContext.LogicalSetData(GetCallContextKey(key), value);         
    }
    public static string Get(string item)
    {
        return GetCallContextValue(item);
    }
    public static string Get(string item, IFormatProvider formatProvider)
    {
        if ((formatProvider == null) && (LogManager.Configuration != null))
        {
            formatProvider = LogManager.Configuration.DefaultCultureInfo;
        }
        return string.Format(formatProvider, "{0}", GetCallContextValue(item));
    }
    public static void Set(string item, string value)
    {
        SetCallContextValue(item, value);
    }
}
[LayoutRenderer("mdlc2")]
public class LogicalThreadContextLayoutRenderer : LayoutRenderer
{
   [DefaultParameter]
   public bool Name {get;set;}
    protected override void Append(StringBuilder builder, LogEventInfo logEvent)
    {
        builder.Append(LogicalThreadContext.Get(Name, null));
    }
}
//or application_start for ASP.NET 4
static void Main(string[] args) 
{ 
    //layout renderer
    ConfigurationItemFactory.Default.LayoutRenderers
          .RegisterDefinition("mdlc2", typeof(LogicalThreadContextLayoutRenderer ));
}
Usage in the config file:
${mdlc2:OperationId}
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With