2014-10-02 99 views
7

所以我们有一个日志系统,我们使用Log.Info并写入​​。ThreadLocal并等待

现在我们有多个工作在后台运行,我们希望这些写入他们自己的日志。因此,每个工作人员都捆绑了一切。在执行此任务时记录的所有内容都应该转发给它自己的记录器。

我们想要制作一个方法Log.SetLoggerForCurrentThread,并用ThreadLocal实现它。执行代码会是这个样子:

public class Worker 
{ 
    ILogger _Logger; 

    public void ExecuteTask() 
    { 
     Log.Info("This goes to the 'Global' logger"); 

     using (Log.SetLoggerForCurrentThread(_Logger)) 
     { 
      Log.Info("This goes to local logger"); 
      DoWork(); 
     } 
    } 

    private async void DoWork() 
    { 
     Log.Info("Starting..."); 

     // SomeMethod does some logging, 
     // that also needs to be forwared to the local logger 
     var value = await SomeDeepDomainClass.SomeMethod(); 

     // if we use ThreadLocal, and this thread has been reused, 
     // it could be a completely different logger that is now attached. 
     Log.Info("Ended..."); 
    } 
} 

问题

  • 当我们用等待的线程可以在另一工作人员理论过程中工作,从而混合了当地的伐木工人。
  • 做类似的事情最好的模式是什么?我可以使用哪种存储方式?
  • CultureInfo如何处理?

背景资料

大多数工人将一个蓝色的WorkerRole实例中运行,但现在比他们还触发(一次)从一个控制台应用程序。

+0

'CultureInfo'被设置在每个托管线程的开始 - 这是独立Task'的'。如果你想从'Task'登录,我建议显式使用'Log.SetLoggerForCurrentThread'。它表明你明确地想到了这一点并得到了补偿。 – 2014-10-02 13:40:38

+0

在我开始编写答案之前,您应该知道log4net是免费的,并且已经解决了大部分问题。 – 2014-10-02 13:42:06

+0

Hi @GuillaumeCR,你可能是对的,但将它看作是类似模式的一般编程问题。 – 2014-10-02 13:47:45

回答

6

您可以使用CallContext跨线程传递(可串行化)数据。请参阅本文中的示例:

http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html

对于一些背景信息,请参阅这篇文章:

http://blogs.msdn.com/b/pfxteam/archive/2012/06/15/executioncontext-vs-synchronizationcontext.aspx

+5

您应该发布一个有意义的答案代码示例。不要继续可能死亡的外部链接。 – 2014-10-02 15:32:27

+0

伟大的文章!谢谢! :) – 2014-10-02 18:23:47

6

在我看来,最好的解决方法是通过记录器实例作为论据(或成员变量),或注入它们(例如,使用嵌套的作用域)。

但是,如果要以与await兼容的方式隐式存储和传递日志记录实例,则需要使用逻辑调用上下文。我有一个blog post describing this approach,它指出了这种方法的局限性:

  1. 它只适用于完整的.NET 4.5框架。
  2. 您必须使用“覆盖”语义。这通常意味着只存储不可变的数据。

考虑到这一点,这里的一些代码,应为您的工作需要:

public static class LocalLogger 
{ 
    private static readonly string name = Guid.NewGuid().ToString("N"); 

    // Static Log methods should read this. 
    public static ILogger CurrentLogger 
    { 
    public get 
    { 
     var ret = CallContext.LogicalGetData(name) as ILogger; 
     return ret == null ? Logger.GlobalLogger : ret; 
    } 

    private set 
    { 
     CallContext.LogicalSetData(name, value); 
    } 
    } 

    // Client code uses this. 
    public static IDisposable UseLogger(ILogger logger) 
    { 
    var oldLogger = CurrentLogger; 
    CurrentLogger = logger; 
    if (oldLogger == GlobalLogger) 
     return NoopDisposable.Instance; 
    return new SetWhenDisposed(oldLogger); 
    } 

    private sealed class NoopDisposable : IDisposable 
    { 
    public void Dispose() { } 
    public static readonly Instance = new NoopDisposable(); 
    } 

    private sealed class SetWhenDisposed : IDisposable 
    { 
    private readonly ILogger _oldLogger; 
    private bool _disposed; 

    public SetWhenDisposed(ILogger oldLogger) 
    { 
     _oldLogger = oldLogger; 
    } 

    public void Dispose() 
    { 
     if (_disposed) 
     return; 
     CurrentLogger = _oldLogger; 
     _disposed = true; 
    } 
    } 
}