2014-10-02 10 views
7

So haben wir ein Protokollierungssystem, wo wir Log.Info verwenden und es schreibt ILogger.ThreadLocal und erwarten

Jetzt haben wir mehrere Arbeiter im Hintergrund laufen, und wir wollen diese in ihre eigenen Protokolle schreiben. So ist alles pro Worker gebündelt. Alles, was bei der Ausführung dieser Aufgabe protokolliert wird, sollte an die eigene Protokollfunktion weitergeleitet werden.

Wir haben darüber nachgedacht, eine Methode Log.SetLoggerForCurrentThread zu erstellen und sie mit ThreadLocal zu implementieren. Die Ausführung von Code wie folgt aussehen würde:

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

Fragen

  • Wenn wir erwarten verwenden, der Faden könnte theoretisch Prozess Arbeit auf einem anderen Arbeiter, damit die lokalen Logger Vermischung.
  • Was ist das beste Muster, um etwas Ähnliches zu tun? Welche Speichermethode kann ich verwenden?
  • Wie geht das mit CultureInfo?

Hintergrundinformationen

Die meisten dieser Arbeiter innerhalb einer Azure WorkerRole Instanz ausgeführt wird, aber jetzt und als sie eine Konsolenanwendung auch ausgelöst (einmal) aus sind.

+0

'CultureInfo' eingerichtet ist, zu Beginn eines jeden verwalteten Thread - das ist unabhängig von' Task'. Ich würde die explizite Verwendung von 'Log.SetLoggerForCurrentThread' empfehlen, wenn Sie von einer' Task' abmelden wollen. Es zeigt, dass Sie explizit daran gedacht und kompensiert haben. –

+0

Bevor ich anfange, an einer Antwort zu arbeiten, sollten Sie wissen, dass log4net kostenlos ist und die meisten Ihrer Probleme bereits löst. –

+0

Hallo @GuillaumeCR, Sie könnten Recht haben, aber sehen Sie es als allgemeine Programmierfrage für ähnliche Muster. –

Antwort

6

Sie können CallContext verwenden, um (serialisierbare) Daten über Threads zu übergeben. Lesen Sie diesen Artikel für ein Beispiel:

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

Für einige Hintergrundinformationen finden Sie in diesem Artikel:

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

+5

Sie sollten ein aussagekräftiges Codebeispiel Ihrer Antwort posten. Keine Weitergabe auf externen Links, die möglicherweise abstürzen. –

+0

toller Artikel! Vielen Dank! :) –

6

Meiner Meinung nach, ist die beste Lösung, entweder die Logger-Instanzen als Argumente übergeben (oder Member-Variablen), oder injizieren Sie sie (z. B. mit verschachtelten Bereichen).

Wenn Sie jedoch die Protokollierungsinstanz implizit auf eine Weise speichern und übergeben möchten, die mit await kompatibel ist, müssen Sie den logischen Aufrufkontext verwenden. Ich habe einen blog post describing this approach, die die Grenzen dieses Ansatzes betont:

  1. Es nur auf den vollen Rahmen .NET 4.5 arbeitet.
  2. Sie müssen die Semantik "überschreiben" verwenden. Dies bedeutet im Allgemeinen, nur unveränderliche Daten zu speichern.

In diesem Sinne, hier ist einige Code, der für Ihre Bedürfnisse funktionieren sollte:

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