|
From: Geoghegan, W. A (Willie) <wil...@in...> - 2010-10-01 21:44:21
|
I have seen on the Common.Logging home page that Logging Context support
is planned for the next release. In the meantime, I have implemented a
logging context abstraction that is based, in part, on Castle's logging
context abstraction that they provide for log4net and NLog. At this
point I consider it somewhat experimental, but I think there are some ok
ideas here. I also did not modify any Common.Logging code. In a "real"
implementation, I would expect that context would be available from
LogManager (or maybe from ILog as was done in Castle). I think that
making it available from ILog could be misleading because it could be
read as "the logging context for THIS logger", which is not really true.
Anyway, with my code you can do something like to this to set the
context (LogContext is my static object that provides the entry point
into the logging context abstraction):
LogContext.GlobalProperties["number"] = 1234;
LogContext.ThreadProperties["id"] = System.Threading.Whatever.Id;
using (LogContext.ThreadStack.Push["outer level"])
{
DoSomeStuff();
using (LogContext.ThreadStack.Push["inner level")
{
DoSomeStuffFromInnerLevel();
}
}
Which logging context abstraction is in play is dictated by the current
LogManager.Adapter's implementation (or lack of same) of an interface,
IContextProvider. When one of the context operations (GlobalProperties,
ThreadProperties, ThreadStack) is access on LogContext, code like this
executes:
IContextProperties GlobalProperties
{
get
{
IAdapterContextProvider iacp = LogManager.Adapter as
IContextProvider;
if (iacp == null)
{
return NullContextProvider.GlobalProperties; //Does nothing, but
also does not fail.
}
else
{
return iacp.Context.GlobalProperties; //Global properties relevant
to the currently abstracted logger
}
}
}
To take advantage of LogContext, I have copied the NLog abstraction
provided by Common.Logging and added the IAdapterContextProvider
interface. Now, when I configure my NLog abstraction, LogContext finds
that LogManager.Adapter does support the interface, so the NLog context
values can be set. I have done something similar for log4net.
I also implemented a thread stack (NDC) for
Trace.CorrelationManager.LogicalOperationStack.
I also provided an implementation of GlobalProperties, ThreadProperties,
and ThreadStack (taken largely from NLog). They could be used in a
context where there is not a "real" logging platform behind the
Common.Logging abstraction. Say you have a logger for Silverlight that
logs to the debugger and might log to a WCF LogService. Context can
still be captured. The logging implementation, such as it is, can get
the context information and do something with it. Don't know how useful
it will prove to.
This could easily occur inside of LogManager so a user of Common.Logging
could write something like this:
LogManager.GlobalProperties["name"] = "hello"; //set the "name" global
property on currently abstracted logging platform.
Some notable items:
1. Log4net provides properties and stack info for both "thread"
and "logical thread". From what I can tell, the "thread" values
(formerly MDC and NDC) are way more commonly used then the "logical
thread" values. Castle did not implement an abstraction for the
"logical" flavor.
2. Log4net provides for multiple stacks per thread. Castle exposes
this.
3. NLog does not provide a "logical thread" context, even in NLog
2.0. Castle's implementation exposes "ThreadStacks" (presumably to
support log4net's multiple stacks) but the implementation always hands
back the one NLog stack (NDC/NestedDiagnosticContext) for the thread,
regardless of what stack was requested.
4. Log4net allows objects to be set in the "properties" collections
and strings to be pushed onto the stacks. NLog only allows strings as
"properties" and entries on the stack. Castle exposes a string for both
the "properties" and the stack.
5. Castle based their "properties" implementation on log4net's
"BaseProperties" (maybe not right name), which has only a get/set
property bag interface. As such, they did not implement Clear or
Remove. It seems to make more sense to have Clear and Remove.
If the most common usage of the thread stacks is to have one stack
(NDC), and since NLog has only one stack anyway, I wonder if it really
makes sense to expose the stacks/stack such that you have to write code
like this:
LogContext.ThreadStacks["NDC"].Push("whatever");
Maybe it is better to either only expose one stack or expose a stack AND
a dictionary of stacks. Normally one might just use the single (or
default) stack. If there is a particular application or workflow that
wants to use multiple stacks, they would be there.
LogContext.ThreadStack.Push("whatever"); //pushes onto NDC
LogContext.ThreadStacks["custom stack"].Push("whatever"); //pushes onto
custom stack.
If anyone at Common.Logging is interested in discussing this and whether
or it is relevant or not to Common.Logging's implementation (when?) of
logging contexts, please respond. Also, if anyone at Common.Logging
would like to see what I have done and how it might or might not fit
into Common.Logging, also please respond.
Thanks.
Willie Geoghegan.
|