We have a logging system where we use Log.Info
and it writes to an ILogger
.
Now we have multiple workers running in the background, and we want these to write to their own logs. So everything is bundled per Worker. Everything that is logged when this task is being executed should be forwarded to its own logger.
We were thinking about making a method Log.SetLoggerForCurrentThread, implementing it with ThreadLocal. The executing code would look something like this:
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..." );
}
}
Questions
CultureInfo
handle this?Background information
Most of these Workers will run within an Azure WorkerRole instance, but now and then they are also triggered (once) from a Console application.
This type is thread-safe for all members.
The ThreadLocal class is used to create thread local variables which can only be read and written by the same thread. For example, if two threads are accessing code having reference to same threadLocal variable then each thread will not see any modification to threadLocal variable done by other thread.
Thread-local storage (TLS) is a computer programming method that uses static or global memory local to a thread. All threads of a process share the virtual address space of the process. The local variables of a function are unique to each thread that runs the function.
Represents ambient data that is local to a given asynchronous control flow, such as an asynchronous method.
You can use CallContext to pass (serializable) data across threads. See this article for an example:
https://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html
For some background information, see this article:
https://devblogs.microsoft.com/pfxteam/executioncontext-vs-synchronizationcontext/
In my opinion, the best solution is to either pass the logger instances as arguments (or member variables), or inject them (e.g., using nested scopes).
However, if you want to store and pass the logging instance implicitly in a way that is compatible with await
, then you'll need to use the logical call context. I have a blog post describing this approach, which points out the limitations of this approach:
With this in mind, here's some code that should work for your needs:
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;
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With