NLog MappedDiagnosticsLogicalContext не работает в async / await с ConfigureAwait (false)

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

Я не ожидал, что это произойдет и не уверен, как это произошло. Но, просматривая журнал регистрации, я вижу, что такое случалось и раньше.

Я думал, что логический контекст вызова должен был перенести. Это мое использование ConfigureAwait (false), которое причина такого поведения? Это единственное, что приходит мне в голову....

2 ответов


нашли то, что я считаю проблемой. 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}