views:

365

answers:

4

What have others done to get around the fact that the Commons Logging project (for both .NET and Java) do not support Mapped or Nested Diagnostic Contexts as far as I know?

+2  A: 

Exec summary:

We opted to use the implementor logging framework directly (in our case, log4j).

Long answer:

Do you need an abstract logging framework to meet your requirements? They're great for libraries that want to play nice with whatever host environment they end up in, but if you're writing an application, you can just use the implementing logging framework directly in many cases (i.e. there often isn't any reason why the logging implementor should change over the course of the application's life).

We opted to use the implementor logging framework directly (in our case, log4j). Commons-Logging is available on the classpath, but it's only there for the sake of the libraries that depend on it. In our case it was an easy choice, as the company I'm working for has had log4j as a mandatory standard for years and it's unlikely to change, but even if that's less obvious, it boils down to a bit of cost/benefit analysis.

  • what's the benefit NDC and other specialty features give me?
  • what's the chance of having to change logging implementor, and what's the cost if I have to?

and maybe:

  • do I have to support different logging implementors in simultaneous deployments?

In our case, if we ever are required to change logging implementors, we'll have some refactoring to do, but with everything in the org.apache.log4j package, that's neither a difficult nor a very risky refactoring1. The log4e Eclipes plugin will happily convert the actual logging statements automatically; the NDC object could be a problem because not all logging frameworks support such a thing. You could consider hiding the NDC.enter() and NDC.leave() methods inside a utility class under your own control, we didn't bother.


1) famous Last Words

Barend
Thanks for answering. To your point about the relative likelihood of changing logging framework during the lifetime of the application, I've faced a situation where the company I was working for hadn't decided yet which framework to choose but I still need to release the application. I bet on Log4Net but the company ended up choosing something else. Fortunately I used Commons Logging and the migration was trivial.
tgeros
I would argue that in this case, the likelihood of change was much larger and using a meta-logger was the correct approach, as you've found. Gotta love it when a plan comes together.
Barend
+1  A: 

This isn't a direct answer to your question, but do you have an option to use something other than Commons Logging? If you're open to it, SLF4J (Simple Logging Facade - http://www.slf4j.org/) is another log abstraction API that appears to support mapped diagnostic context.

I should mention that I have not personally used it, but have been looking into using for my next major project, since they claim to be so much better than Commons Logging. A lot of newer major open source projects also seem to be using it (Hibernate, Spring Modules, several Apache projects).

Jack Leow
Thanks, I'll take a look.
tgeros
A: 

For the sake of completeness, I ended up writing my own very simple generic interface:

public interface IDiagnosticContextHandler
{
    void Set(string name, string value);
}

then implemented a Log4Net specific version:

public class Log4NetDiagnosticContextHandler : IDiagnosticContextHandler
{
    private readonly Assembly assembly;

    private readonly Type mdcType;

    public Log4NetDiagnosticContextHandler()
    {
        this.assembly = Assembly.Load("log4net");
        this.mdcType = this.assembly.GetType("log4net.MDC", true);
    }

    public void Set(string name, string value)
    {
        this.mdcType.InvokeMember("Set", BindingFlags.InvokeMethod, null, null, new object[] { name, value });
    }
}

I then used an IoC container (Spring.Net) to bring in the correct implementation. If a different logging framework was later required it'd be a simple matter of writing a different implementation of that interface changing the IoC configuration.

tgeros
A: 

This is late compared to the original question and to the most recent (accepted) answer, but I will post it for the benefit of future searchers.

According to the Common.Logging (NET) website, the next version (current version is 2.0) is supposed to have support for this very thing. However, it is not clear when this release is planned. 2.0 was released in April 2009. The website says that the next release is scheduled for "June". No year is mentioned. June 2009 and June 2010 have come and gone.

Having said that, at least implementation of a log4net/NLog "context" abstraction exists in the Castle project's git source code repository. Start here and look at the ExtendedLog4netLogger, GlobalContextProperties, ThreadContextProperties, ThreadContextStack, and ThreadContextStacks to see how Castle exposes NDC and MDC. A similar implementation exists in Castle for NLog.

If Common.Logging for NET were to implement a similar abstraction, you would be able to set context values through the logger instance that you get back from the LogManager like this:

ILog logger = LogManager.GetCurrentClassLogger();
logger.ThreadContextProperties["EventID"] = 123;
logger.GlobalContextPropeties["USER"] = GetUser();

Adding to this, if you had a specific context that you want to set for all log messages (say from your own context that you are passing around in your code), you could write your own log4net and/or NLog abstraction to plug into Common.Logging to populate that information automatically. For example, assume that your application has a "context" that is passed around that helps keep track of information for a given "transaction". Also, assume, for simplicity, that you can access it via a static class like "MyContext".

Your WriteInternal (what you implement when you write a logger abstraction to plug into Common.Logging) might look something like this (this is for NLog. log4net might be slightly different):

protected override void WriteInternal(CommonLoggingLogLevel logLevel, object message, Exception exception)
{
  LogLevelNLog level = GetLevel(logLevel);
  LogEventInfo logEvent = new LogEventInfo(level, _logger.Name, null, "{0}", new object[] { message }, exception);

  //Access these context values for output formatting using the event-context:item token
  logEvent.Context["ActivityID"] = MyContext.ActivityID;
  logEvent.Context["SessionID"] = MyContext.SessionID;
  logEvent.Context["TransactionStartTime"] = MyContext.TransactionStartTime;

  //Note that you can also set NDC and/or MDC here:
  NLog.MDC.Set("SoftwareVersion", MyContext.Version.ToString());
  _logger.Log(declaringType, logEvent);
}

If you (or someone else) is using Common.Logging (NET) needs log4net/NLog context properties exposed now, you could do something similar to what Castle has done to expose them through the logging abstraction interface. If you want to set the context automatically with information that you can get from inside the logging call, you could do something similar to what I propose above.

wageoghe