BrightOpen.LogAway
Logging abstraction library, like LibLog perhaps in some respects, but quite different. It prompts a shift in how logging is conducted while not requiring too much changes in existing codes regardless of what logging framework you used before.
What is the aim of the project?
To redefine logging (in C# by implementation an generally by inspiration):
public interface ILog
{
void Write(ILogEvent logEvent);
}The client side of such an interface should make no assumptions about how the events are handled.
From the client's perspective, they are throw-away objects. The ILog interface is a black hole you can throw anything that implements ILogEvent into and it will swallow that without complaints.
The implementation side of this interface should make no assumptions about how these events are created.
From the implementation's perspective, they are immutable objects. The ILogEvent only requires that the log event can dump it's content.
Why?
Libraries and frameworks should not depend on specific logging implementations. Instead, they should provide flexible extension points where logging implementation can be injected. This can be done with LibLog or other abstractions already. What we aim for is maximum flexibility, separation of concerns, testability.
Existing logging frameworks have fancy methods on their main log interface,
usually one set for each formatted and non-formatted logging.
Sometimes another set for lambdas.
Each common logging level has a method in each set.
All this seems to impose arbitrary restrictions and obstructions on both the client and implementation while not providing
a generic enough way to interfere with them. When there's a common Log or Write method, it still does a lot of
magic and still expects irrelevant information to be provided. Log4Net, to name just one, either requires a message to be serialized upfront or else doesn't allow the DateTime stamp to be set externally when creating a LoggingEvent.
It is difficult then to extend, modify, adapt the logging patterns and implementations,
and we end up having to choose the right framewrork or find or implement an abstraction library to suit our needs.
Another motivation comes from support personel work experience. When a software is delivered and working it is DONE. It is futile for a support engineer to request logging levels to be readjusted, wording of events to be changed and so on. Rightfully so, why should a valid implementation of a requirement change only because of logging? Processing a log event or not based on it's context is a deployment and configuration concern.
Yet another good motivation is that we want to have a very high probability and possibility that an event clearly identifies a point in the code without much performance penalty. This is surely achievable with some frameworks, but it is a little obscure perhaps or expensive. As a support engineer, I'd like this to be the prominnent feature of lib/app logging so that developers need not bother with it.
How?
namespace Test.LogAway.Examples.Basics
{
using BrightOpen.LogAway;
using Xunit;
public class IntroLogging
{
private readonly ILog _log;
public IntroLogging()
{
// In your lib/app, you'll probably want log to be injected: (ILog log)
var log = null as ILog;
// Here we allow it to be null in which case we default to console: .OrConsole()
// Then we take a class specific instance of the loger: .For(this)
// so that the name is available in all log events
this._log = log.OrConsole().For(this);
}
[Fact]
public IntroLogging MakeTheWorldABetterPlace()
{
using (var log = _log.Local().Section())
{
// if your code is semantically structured, you can move away from verbose blabber and focus on facts
// that can be easily processed and analised while maintaining readability for humans:
log.Write("Done", new { By = "superhero" });
}
return this;
// Message: Enter; Line: 25; Name: MakeTheWorldABetterPlace, Test.LogAway.Examples.Basics.IntroLogging, ; File: W:\rob\BrightOpen.LogAway\src\Test.LogAway.Examples\Basics\IntroLogging.cs;
// Done: { By = superhero }; Line: 25; Name: MakeTheWorldABetterPlace, Test.LogAway.Examples.Basics.IntroLogging, ; File: W:\rob\BrightOpen.LogAway\src\Test.LogAway.Examples\Basics\IntroLogging.cs;
// Message: Exit; Line: 25; Name: MakeTheWorldABetterPlace, Test.LogAway.Examples.Basics.IntroLogging, ; File: W:\rob\BrightOpen.LogAway\src\Test.LogAway.Examples\Basics\IntroLogging.cs;
}
}
}Each client side has full control over how events are generated. In this example, the client uses a Local logger to wrap the ILog instance to add the context of the class, code section and write a string identified event. The Local logger will also emit section boundary entrance and exit events. It will collect event names out of the context: Client, MakeTheWorldABetterPlace, Done.
In a larger application or library, this Local logger would probably be abstracted and injected or not...
Notice, the client needs not to concern himself what logging level to use because that is a concern of the log event receiver to decide. This can be achieved using conventions and/or specific filtering rules. For instance any new event could be assigned a warning level by default, any event with a name ending "Enter", "Exit" or "Trace", could be set to trace level. Events containing exceptions by default on error level. Then specific namespaces to default to info level and after all this overrides for specific events.
The ILog implementation has full control over how events are handled. It can do none or many things, for example:
- Ignore the event altogether - a quiet log implementation
- Dump the event to console right away - a simple log implementation
- Queue the event in some sort of buffer to be processed later - an asynchronous log implementation
- Process the event using configured filters or transformation rules - a configurable log implementation
- Delegate logging to configured third party logging dispatchers - a logging adapter log implementation
Regarding configured logging:
- One filter might add a unique event stamp to each event consisting of sequencial number, process id, host name, host start time, process start time if you like. That means the event provided is wrapped in an adapter event that adds this kind of context before passing it on.
- Another filter may simply silence all events - such a peacemaker filter - based on a boolean state configurable at runtime.
- Yet another filter would conditionally include a console logger - a runtime debugging filter that is.
Principles
Beside the already mentioned separation of event generation and processing:
- LogEvents should be treated as immutable, modifications are done by decoration
Conclusion
Not to be mistaken, I consider Log4Net an excellent logging framework. It is both fortunate and not that it carries a lot of legacy forward. Yes, Log4Net would let you change logging configuration at runtime either through a watched config file or by clumsily modifying the assumed Hierarchy repository programatically. Nothing like the IoC enabled easy breezy approach proposed here. There are many logging options out there and it would be pretentious to claim that this is the only one. However, we will strive to be the change we want to see in the world.
Log away...