|
From: Geoghegan, W. A (Willie) <wil...@in...> - 2010-10-25 20:24:01
|
One further thought on Logging Context.
Having thought about this since my last mail, I wonder if an approach
like this would be a good idea. Expose a property on the LogManager
that encapsulates all of the context properties and "stacks":
//
//IContextProperties, IContextStacks, and IContextStack as before.
//
public interface ILoggingContext
{
IContextProperties GlobalProperties { get; }
IContextProperties ThreadProperties { get; }
IContextStacks ThreadStacks { get; }
IContextProperties LogicalThreadProperties { get; }
IContextStacks LogicalThreadStacks { get; }
}
//
//ILoggerFactoryAdapter adds new property...
//
public interface ILoggerFactoryAdapter
{
//Leaving out existing methods/properties
ILoggingContext Context { get; set; }
}
//
//LogManager adds a new static property...
//
public static class LogManager
{
private static ILoggingContext _nullContext = new
NullLoggingContext();
//Leaving out existing methods/properties
//...
ILoggingContext Context
{
return (_adapter.Context == null) ? _nullContext : _adapter.Context;
}
}
Now you can write logging code like this:
LogManager.Context.GlobalProperties["id"] = GetGlobalIdFromSomewhere();
LogManager.Context.ThreadProperties["threadid"] =
GetThreadIdFromThread();
using (LogManager.Context.ThreadStacks["ndc"].Push("operation1"))
{
//All messages logged in this scope will be tagged with "operation1".
}
By adding Context as a property to LogManager, and having it be an
actual object instance, we can write extension methods against it. So,
if someone wanted to they could simplify the interface to the logging
context by making a "default" thread stack, like this:
public static class LoggingContextExtensions
{
//Consider the NDC as the default thread stack ...
public static IContextStack ThreadStack(this ILoggingContext context)
{
return context.ThreadStacks["ndc"];
}
}
Now logging code that uses "stacks" can be simplified like this:
using (LogManager.Context.ThreadStack().Push("operation1"))
{
}
Maybe a better extension name would be DefaultThreadStack or even NDC.
using (LogManager.Context.DefaultThreadStack().Push("operation1"))
{
}
using (LogManager.Context.NDC.Push("operation1"))
{
}
They could also simplify setting of properties (perhaps to encourage or
enforce coding/development/organizational standards) like this:
public static class LoggingContextExtensions
{
public static void SetAppStartTime(this ILoggingContext context,
DateTime start)
{
context.GlobalProperties["appstarttime"] = start;
}
}
LogManager.Context.SetAppStartTime(DateTime.Now);
This does not directly address the fact that log4net has a "logical
thread" context and NLog doesn't. The default implementation for NLog
could be to use "NullContext" objects for the "logical thread" context
properties when NLog is configured (decision of what to expose is
probably actually inside of the NLog adapter). Alternatively, if a
development organization is ambitious and really wants a "logical
context" they could implement one (following the way that log4net has
implemented theirs), expose it via the IContext.Logical* properties, and
access it via a custom NLog LayoutRenderer. All pretty straightforward
implementations.
Another benefit (or would it be "benefit" with air quotes) of exposing
the logging context as a property on LogManager (rather than as discrete
properties as I mentioned in my earlier email) is that someone could
write an extension method on IContext to expose a brand new type of
context. Say that you want to write an IContextProperties
implementation that uses HttpContext.Items. You could expose it
something like this (probably not a good implementation, used for
illustrative purposes only):
public MyHttpContextItemsWrapper : IContextProperties
{
object this [string key]
{
get
{
return HttpContext.Current.Items[key]
}
set
{
HttpContext.Current.Items[key] = value;
}
}
}
public static class LoggingContextExtensions
{
public static IContextProperties HttpContextItems(this ILoggingContext
context)
{
return new MyHttpContextItemsWrapper();
}
}
LogManager. Context.HttpContextItems["my http property"] = "hello";
The value of this completely new context can be output to the logging
stream by (at least in the case of log4net and NLog) by implementing a
custom "output operator" (like a log4net PatternLayoutConverter or a
NLog LayoutRenderer).
Thanks.
Willie Geoghegan.
|