A discussion on the altdotnet list just came up around logging. After some various suggestions, I asked what was wrong with using optional dependencies and the built in logging facility in Castle Windsor. It seemed the most obvious answer
Of course, I completely forgot that when I first tried to figure this one out, it had me a little stumped and I took a while to find a good example of it. So here is how it happens.
Optional Dependencies in Windsor
When you ask the Windsor container for an instance of a class, it goes off and merrily tries to resolve all of the dependencies your class has. Firstly the constructor parameters, Windsor must find a constructor that it can provide all of the dependencies for - constructor parameters represent non-optional dependencies, they must be able to be fulfilled or Windsor will not give you an instance of the class back. You can have multiple constructors of course, and in this case Windsor will try and find the best match it can.
This lead to one of the early suggestions on the thread, putting the instance of the Logger as a parameter on the class constructor. To me this is a horrible idea, logging, along with things like security and auditing are cross cutting concerns, the class really shouldn't have to care about them - their behaviour lives elsewhere.
But Windsor has a really simple solution to this - optional dependencies. While Windsor must be able to fulfill at least one constructor signature, it will also try and resolve any public properties it can find on the class. These public properties may or may not be set by Windsor, if it has a suitable match for a property it will set it, otherwise it will ignore it.
So the first part of the solution to the problem is to put a public property on the class with the logger interface - now Windsor will populate this property if it has a match in the container.
The Logging Facility
Next is the magic bit, Castle already has simple integration for logging in the form of the Logging Facility.
While you could just put your own class, that implemented your own logging, and passed this onto log4net, by using the Castle faciltiy you save a lot of messing around, and get a great deal of flexibility. The Castle facility supports:
- log4net (requires Castle.Services.Logging.Log4netIntegration.dll)
- NLog (requires Castle.Services.Logging.NLogIntegration.dll)
- ConsoleLogger
- DiagnosticsLogger
- StreamLogger
- WebLogger (TraceContext)
- NullLogger (used as placeholder)
Of course in the thread, log4net was the target of choice, but Castle lets you switch this simply, and maintain a single interface.
How To Tie it Together
1) Any class you are going to resolve from the Windsor container that you want to have logging on, add a public property of ILogger:
public class MyClass
{
private ILogger logger;
public ILogger Logger
{
get
{
if (logger == null) logger = NullLogger.Instance;
return logger;
}
set { logger = value; }
}
}
A key thing here is that we use the NullLogger as the default value - if the facility wasn't put into the container, or if the class was not instantiated via Windsor, then this will mean that logging just doesn't happen, rather than it creating an exception. The NullLogger just swallows the message and carries on.
2) Setup the Logging Facility in the Windsor container
In the XML configuration:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<facility
id="logging"
type="Castle.Facilities.Logging.LoggingFacility, Castle.Facilities.Logging"
loggingApi="null|console|diagnostics|web|nlog|log4net|custom"
customLoggerFactory="type name that implements ILoggerFactory"
configFile="optional config file location" />
</configuration>
Or in the code intialization:
container.AddFacility(
"logging",
new LoggingFacility(LoggerImplementation.Console));
3) Use the Logger instance from the class:
Logger.Debug("Null shipment returned");
That is all there is to it, any object that is resolved via Windsor will now be inspected for ILogger, and if found it will get an instance of the chosen logger. A simple solution, and a perfect example of why an IoC container can simplify your code.
Posted
06-18-2008 7:58 PM
by
Jak Charlton