NLog MappedDiagnosticsLogicalContext无法在async / await中使用ConfigureAwait(false)

时间:2016-08-19 13:47:48

标签: c# nlog

我正在使用NLog 4.3.5和.Net framework 4.6.1

当我开始服务器端操作时,我打电话:

NLog.MappedDiagnosticsLogicalContext.Set("OperationId", Guid.NewGuid());

这会映射并显示在我的日志文件中。一切都很好......或者是它? 在查看我的日志文件时,我注意到这个操作id值似乎没有像我预期的那样工作。

示例:

  1. 在主题19中,操作开始并设置上下文。

  2. 在所有等待调用中使用.ConfigureAwait(false)

  3. 执行

    var tasks = items.Select(item => Task.Run( () => { /* do stuff */}
    await Task.WhenAll(tasks).ConfigureAwait(false)
    
  4. 用于这些任务的线程之一是线程31(以后记住这一点)
  5. 同时,在线程36中,调用不同的服务器方法并开始新的操作。使用它的唯一操作ID
  6. 编写了几条日志消息
  7. 此操作使用ConfigureAwait(false)
  8. 执行2个不同的等待调用
  9. 下一个日志语句出现在线程31上。从那时起,它记录为在线程19上开始的操作创建的操作ID!
  10. 我没想到会发生这种情况,也不确定它是如何发生的。但是,当我查看我的日志历史时,我发现此类事情之前已经发生过。

    我认为逻辑调用上下文应该继续存在。是否使用了导致此行为的ConfigureAwait(false)?这是我唯一能想到的......

2 个答案:

答案 0 :(得分:2)

找到我认为的问题。 https://github.com/NLog/NLog/issues/934

答案 1 :(得分:1)

您可以按照以下方式解决此问题:

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 ));
}

配置文件中的用法:

${mdlc2:OperationId}