The Toothpick Logo

Quick Start

Setting up the logging system

If a tree falls in the woods, and there's no logger to hear it, does it make a noise?

To get started logging events, we need to define a logger. In order to do this, we need to think about the three aspects of logging:

  • Whether to log an event
  • Where to log an event
  • How to log an event

First, let's decide where to log our events. Toothpick defines a number of loggers, subclasses of AbstractLogger. For our example, we'll use two loggers: one logging to the Transcript, and one logging to an external file. We'll give each one a name, so that we can refer to them directly (although that's rarely if ever needed). We'll also provide the file logger with the name of the file we want to log to.

| logger1 logger2 |
logger1 := TranscriptLogger new
	name: 'My first Logger'.
logger2 := FileLogger new
	name: 'My second Logger';
	fileName: 'MyLog.txt'.

Next, we need to decide how to log events. We do this by defining a LoggingFormatter. For now, let's use the defaultFormatter, which will simply print out our message string.

| logger1 logger2 |
logger1 := TranscriptLogger new
	format: SimpleLoggingFormatter defaultFormat.
logger2 := FileLogger new
	fileName: 'MyLog.txt';
	format: SimpleLoggingFormatter defaultFormat.

Lastly, let's define which events we want to log. We do this by defining a LoggingPolicy. The simplest LoggingPolicy defines a category and a level. Events are deemed worthy of logging if they are in the same category, and have a level at or above the level defined in the Policy. (Note: for a list of levels, see below).

| logger1 logger2 |
logger1 := TranscriptLogger new
	format: SimpleLoggingFormatter defaultFormat;
	policy: (LoggingPolicy category: #debug level: #debug)).
logger2 := FileLogger new
	fileName: 'MyLog.txt';
	format: SimpleLoggingFormatter defaultFormat;
	policy: (LoggingPolicy category: #myapp level: #info).

Once we've defined our loggers, we just need to register them with the LoggingMultiplexer, which is the central manager of the loggers. The logging objects serve as the filtering, formatting and output end of your logged events; once they have been registered with the multiplexer you do not need to reference either from your code. The idea is that each logger will decide on its own whether/where/how to log your events, so the event generating code need not be cluttered with these matters.

Let us register these two loggers with the multiplexer:

| logger1 logger2 |
logger1 := TranscriptLogger new
	format: SimpleLoggingFormatter defaultFormat;
	policy: (LoggingPolicy category: #debug level: #debug)).
logger2 := FileLogger new
	fileName: 'MyLog.txt';
	format: SimpleLoggingFormatter defaultFormat;
	policy: (LoggingPolicy category: #myapp level: #info).
(LoggingMultiplexer current) addLogger: logger1; addLogger: logger2; startAllLoggers.

The system is now set up such that events of the category #debug are sent to the Transcript and events of category #myapp are sent to the file ’MyLog.txt’. We also started the loggers, and in the file ’MyLog.txt’ there is now a header line showing when logging started. (You may find it convenient to open the file in a webbrowser or editor so you can simply hit the ’refresh’ button after events have been appended.)

You can stop logging by evaluating

	LoggingMultiplexer stopAllLoggers

and you can reset the logging framework by evaluating

	LoggingMultiplexer reset
If needed, there are also mechanisms for starting and stopping individual loggers by naming them and accessing them by name in the multiplexer.
[top]  [home]

Sending events into the logging system

Sending events into the logging system is handled by the instance creation protocol on the class side of LoggingEvent:

LoggingEvent 
	category: #debug 
	level: #warn 
	message: 'using obsolete protocol'.
LoggingEvent 
	category: #myapp 
	level: #debug 
	message: 'reading ini files'.

Executing the above two statements should result in a line on the Transcript showing the warning about an obsolete message being sent, while in the log file we created a line showing that initialization of your app has started. This simple demonstration shows how the ’what’ and ’where’ are handled by ToothPick: #debug events go to the Transcript, #myapp events go to the file and events in other categories are ignored.

[top]  [home]

Categories and levels

You can freely choose the category symbol for your logging events, in contrast the logging level symbols are taken from a fixed set that matches the conventions of several operating systems. This fixed set of levels is declared in

LoggingEvent class>>#allLevels
	^#(#all #debug #info #notice #warn #error #crit 
	#alert #emerg #panic #fatal #off #none)

With the standard LoggingPolicy, events are logged if their category matches the category of the Policy, and if the level is equal to, or higher than, the level of the Policy. This means that e.g., a LoggingPolicy with a level of #debug will log an event with level #info, while a LoggingPolicy with a level of #error will not. Both will log events with a level #fatal.

[top]  [home]

Adding convenience methods

Using symbols to indicate category and logging level in your application code has several drawbacks. If you misspell a symbol no exception is raised and no message is logged. This may go unnoticed, particularly for rare logging events. Also, if you want to know which of your application methods send messages of a certain category and level you cannot do that easily using ’senders of’.

To avoid these problems, implement convenience protocol that is specific to your application on the class side of LoggingEvent, along the lines of the example provided for the #debug category. After doing this we can rewrite the earlier example logging events as follows:

	LoggingEvent logWarn: 'using obsolete protocol'.
	LoggingEvent myappLogDebug: 'reading ini files'.

Both problems are now solved, ’senders of’ creates useful reponses and misspelling methods is detected earlier than misspelling symbols.

[top]  [home]

Changing what is logged

You can change what is logged in several ways, the simplest is by putting a custom 'isInteresting' block into your Logging policy, the other provided method is by using composition using the CompositeLoggingPolicy. If you want more you can simply subclass one of the LoggingPolicies and customize it for your needs. For more information, see the page for LoggingPolicy.




Published Thursday, October 19, 2006
supported dialects