2

I am using Log4Net as a service which is injected into other services using StructureMap.

How do I ensure the log file includes the calling service class context (class name and/or thread) which is making the log4net calls?

Surely the calling class or thread will always be the logging service which doesn't help me understand where the logging calls are really coming from.

EDIT:

Register code:

  ObjectFactory.Initialize(x =>
    {           
        x.For<ILog>().AlwaysUnique().Use(s => s.ParentType == null ? 
            LogManager.GetLogger(s.BuildStack.Current.ConcreteType) : 
            LogManager.GetLogger(s.ParentType));

    });

Service layer:

public class LoggerService : ILoggerService
    {
        private readonly ILog log;

        public LoggerService(ILog logger)
        {            
            log = logger;
            log.Info("Logger started {0}".With(logger.Logger.Name));
        }       

        public void Info(string message)
        {
            log.Info(message);
        }
}

In the logging, I am still always getting the LoggerService as the context so I'll never see what actually called the logger. It doesn't seem to be working correctly. I feel like I'm missing something here...

Edit 2: I've added a pastie link for a console app here:

http://pastie.org/1897389

I would expect the parent class to be logged but it isn't working at the simplest of levels.

jaffa
  • 26,770
  • 50
  • 178
  • 289
  • It seems you've abstracted the logger to a level above what I normally would do but try this for the ObjectFactory.Initiatlize instead: replace [LogManager.GetLogger(s.ParentType)] with [LogManager.GetLogger(s.ParentType.UnderlyingSystemType.Name))] – Bryan Bailliache May 13 '11 at 20:28
  • @Bryan - I've tried using s.root.ConcreteType.Name instead and set the app.config to use %logger to output the name of the logger. It isn't giving me the SubActionService logger name though (see my pastie link below). UnderlyingSystemType is always LoggerService which doesn't help. – jaffa May 16 '11 at 08:31
  • Try using a break-point inside the [x.For().AlwaysUnique().Use] lambda expression, examine inside a watch window the value of [s.ParentType] and drill down to the value your looking for. That's what I was using when using your source code to view the parent type of the injected class. – Bryan Bailliache May 16 '11 at 12:09

2 Answers2

0

You might want to have a look at Castle Dynamic proxy in order to solve it using AOP. There is an example of using it with Structure Map on the Structure Map Google Group.

Ayende has an example of AOP based logging using Log4Net and Windsor.

PHeiberg
  • 29,411
  • 6
  • 59
  • 81
  • Thanks for the links. Is there any way using StructureMap as this is what my application is currently using for DI? – jaffa May 10 '11 at 14:32
  • Yes, have a look at the example I linked to using StructureMap: http://bit.ly/mmAsN5 (it uses an older syntax for SM, but the principle is the same). Basically you use EnrichWith and wrap the instances you want to log with a DynamicProxy and implement a Logging interceptor. The interceptor in the example logs to console, but you could switch that to Log4Net. – PHeiberg May 10 '11 at 17:10
0

I use StructureMap in a lot of the code I generate and I have a StructureMap registry which I use to hook the logger into the context of the class that it is injected into.

For Reference, I'm using the 2.6.2 version of StructureMap but should be fine with 2.5+ where the new .For<>().Use<>() format is utilized.

public class CommonsRegistry : Registry
{
  public CommonsRegistry()
  {
    For<ILogger>().AlwaysUnique().Use(s => s.ParentType == null ? new Log4NetLogger(s.BuildStack.Current.ConcreteType) : new Log4NetLogger(s.ParentType.UnderlyingSystemType.Name));
    XmlConfigurator.ConfigureAndWatch(new FileInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetAssembly(GetType()).Location), "Log.config")));
  }
}

What this registry is doing is for anywhere the ILogger is injected, use the class that it's injected into is where the logging messages are logged to/context of.

*Also, in the second line (XmlConfigurator.ConfigureAndWatch) is where I tell Log4Net to get the logging information from the file "Log.config" instead of the application configuration file, you may or may not like that and can be omitted.

The code I use is a common IOC.Startup routine where I would pass if I would like to use the default registery.

ObjectFactory.Initialize(x =>
{
  x.AddRegistry<CommonsRegistry>();
  ...
}

This gives me the calling class name in the logging instance where messages are logged to automatically and all that is required is to inject the logger into the class.

class foo
{
  private readonly ILogger _log;

  public foo(ILogger log)
  {
    _log = log;
  }
}

Now the messages are logged as context/class "foo".

Bryan Bailliache
  • 1,173
  • 9
  • 10
  • Hi, How does this compare with the Enrich feature of the first suggestion? I'm trying to weigh the pros/cons for both options. Looks good though! – jaffa May 12 '11 at 08:28
  • The enrich documentation says "EnrichWith() -- Registers a Func that runs against the new object after creation and gives you the option of returning a different object than the original object" where my registry function is while the logger is being created, set the proper context (the parent class foo in my example). – Bryan Bailliache May 12 '11 at 12:57
  • @jaffa - as @Bryan says, the EnrichWith replaces the object with a new object. In the case of logging using DynamicProxy, you would create a wrapper class around the target, that perform the logging. The advantage of using AOP is that you don't have to add logging code to each place where you which to log, it would be intercepted at runtime and automatically logged. See Ayendes post for some further explanation. – PHeiberg May 12 '11 at 20:21
  • Updated the CommonsRegistry routine. – Bryan Bailliache May 15 '11 at 13:00