NLog MappedDiagnosticsLogicalContext 在 async/await 中无法使用 ConfigureAwait(false)
NLog MappedDiagnosticsLogicalContext not working in async/await with ConfigureAwait(false)
我正在使用 NLog 4.3.5 和 .Net framework 4.6.1
当我开始服务器端操作时,我调用:
NLog.MappedDiagnosticsLogicalContext.Set("OperationId", Guid.NewGuid());
这被映射并出现在我的日志文件中。一切都很好……是吗?
在查看我的日志文件时,我注意到这个操作 ID 值似乎没有像我预期的那样工作。
示例:
在线程 19 中,操作开始并设置上下文。
它在所有等待调用中使用 .ConfigureAwait(false)
它执行一个
var tasks = items.Select(item => Task.Run( () => { /* do stuff */}
await Task.WhenAll(tasks).ConfigureAwait(false)
- 用于这些任务的线程之一是线程 31(稍后记住这一点)
- 同时,在线程 36 中,另一个服务器方法被调用并开始新的操作。几条日志消息是用它唯一的操作 id
写的
- 此操作使用 ConfigureAwait(false) 执行 2 个不同的等待调用
- 下一个日志语句发生在线程 31 上。从那时起,它记录为在线程 19 上开始的操作创建的操作 ID!
我没想到会发生这种情况,也不确定它是如何发生的。但是,当我查看我的日志历史记录时,我发现这种事情以前发生过。
我认为逻辑调用上下文应该继续存在。是我对 ConfigureAwait(false) 的使用导致了这种行为吗?这是我唯一能想到的....
找到了我认为的问题所在。
https://github.com/NLog/NLog/issues/934
您可以按如下方式解决此问题:
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}
我正在使用 NLog 4.3.5 和 .Net framework 4.6.1
当我开始服务器端操作时,我调用:
NLog.MappedDiagnosticsLogicalContext.Set("OperationId", Guid.NewGuid());
这被映射并出现在我的日志文件中。一切都很好……是吗? 在查看我的日志文件时,我注意到这个操作 ID 值似乎没有像我预期的那样工作。
示例:
在线程 19 中,操作开始并设置上下文。
它在所有等待调用中使用 .ConfigureAwait(false)
它执行一个
var tasks = items.Select(item => Task.Run( () => { /* do stuff */} await Task.WhenAll(tasks).ConfigureAwait(false)
- 用于这些任务的线程之一是线程 31(稍后记住这一点)
- 同时,在线程 36 中,另一个服务器方法被调用并开始新的操作。几条日志消息是用它唯一的操作 id 写的
- 此操作使用 ConfigureAwait(false) 执行 2 个不同的等待调用
- 下一个日志语句发生在线程 31 上。从那时起,它记录为在线程 19 上开始的操作创建的操作 ID!
我没想到会发生这种情况,也不确定它是如何发生的。但是,当我查看我的日志历史记录时,我发现这种事情以前发生过。
我认为逻辑调用上下文应该继续存在。是我对 ConfigureAwait(false) 的使用导致了这种行为吗?这是我唯一能想到的....
找到了我认为的问题所在。 https://github.com/NLog/NLog/issues/934
您可以按如下方式解决此问题:
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}