NLog MappedDiagnosticsLogicalContext не работает в async / await с 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 вызывается другой метод сервера и начинается новая операция. Несколько сообщений журнала записываются с его уникальным идентификатором операции
- эта операция выполняет 2 различных вызова await с ConfigureAwait (false)
- следующая инструкция журнала происходит в потоке 31. С этого момента он регистрирует идентификатор операции, созданный для операции, которая началась в потоке 19!
Я не ожидал, что это произойдет и не уверен, как это произошло. Но, просматривая журнал регистрации, я вижу, что такое случалось и раньше.
Я думал, что логический контекст вызова должен был перенести. Это мое использование ConfigureAwait (false), которое причина такого поведения? Это единственное, что приходит мне в голову....
2 ответов
вы можете обойти это следующим образом:
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}