There comes a time in every programmers life when all the fancy symbolic debuggers let us down, and we have to fall back to that old standby, logging. Or to give it its full name “printf debugging”!
Actually there are lots of reasons why logging can be useful. Sometimes we have what I like to call a “heisenbug” - where the very act of stopping in a debugger to look at the problem causes the problem to disappear. Sometimes we have other timing issues. Sometimes we simply have too much data to analyse in real time, and we need a chance to process it or manually sift through it later.
In these cases the typical solution is to insert print statements into our code which output some text to the console or a file. In C this is typically printf(), or perhaps fprintf(). In Objective-C, we use NSLog instead. Problems ========
There are a few problems with logging code though:
Some of these problems can be solved, but it can get messy.
A typical solution is to define one or more macros which do the logging. By varying the definitions of the macros we can turn the logging on or off. This is a bit clumsy. You have to know that the macros exist, and where they are defined, and you have to recompile the program to change something. In a big program this may not be trivial - you may not even have source code for every framework.
In some solutions the logging macros that are defined include some concept of a level - for instance “warning”, “error”, and so on. This might allow us to vary the logging at runtime by only allowing through the log messages that match (or perhaps fall under) a given level. This can help, but it’s still clumsy. Logging messages don’t necessarily fall neatly into levels. Furthermore, messages from different modules will match the same level, so if you ask for a level you get everything at that level.
The ECLogging module sets out to solve these problems in a different way.
All logging is sent to a named channel, and there can be as many named channels as are needed. Named channels are intended to group logging messages by their purpose or their module, rather that by some concept of “level”.
Logging channels can of course be turned on and off. This can be done at runtime, and the particular configuration of channels that are enabled is saved on each machine, so that a programmer can set things up with only the channels that they want to see enabled. In fact, because of this, all channels default to being disabled, so you don’t get spammed with log output that you didn’t ask for.
Because channels in ECLogging are actual named objects, the logging system can iterate through them and present a user interface for their configuration.
Channels also come in two flavours - log and debug. Log channels exist in all builds. Debug channels only exist in debug builds, and the code associated with them is completely compiled away in release builds.
So log output goes in to the system via channels, but where does it come out?
The answer is handlers.
The logging system contains a number of predefined handlers, which know how to output to the console, the standard output and error streams, and to files on the disc. Custom handlers can be defined and added to do anything conceivable with log output - send it to another process over the network, for example.
By default, all channel output is sent to a global set of handlers. By choosing the handlers in this set you can easily influence where most of the logging output goes.
However, any logging channel can be configured to use it’s own custom set of handlers. In this way you can determine that some channels just go to the console, some just go to disc, and so on.
In use, the system is very similar to NSLog, except that you have to first define a channel, then direct output to it.
ECDefineLogChannel(MyChannel);
@implementation MyClass
- (void)myMethod
{
ECLog(MyChannel, @"some message with params %@ %d", self, 123);
}
@end
Add to this a small amount of code in your application delegate to initialise the system and install the handlers, and you’re good to go.
Drop in some additional iOS or Mac OS X user interface items, and you’ve got a way of configuring channels at runtime. Alternatively, you can just turn channels on and off in code, or by setting NSUserDefaults values.
ECLogging can be found on github, including some decent documentation.
The ECLoggingExamples projects illustrate how to use the ECLogging module on both the Mac and iOS. They should be built from a workspace which also includes the ECLogging project.
Both of these samples are quite basic, and should definitely be regarded as a hint in the right direction, not a definitive illustration of everything that’s possible.
The library design is quite mature - it’s a meme I’ve implemented many times over the last decade or more, in different environments.
The implementation is quite immature, for the simple reason that I’m doing bits when I need them. It does work though, and I use it extensively, both in my own products and others that I contract on.
If anyone shows any interest in it, no doubt I’ll be motivated to get my finger out and improve it further!
[Updated Feb 2012 to fix github links, which have moved]