Tutorial

Using Sawmill is straightforward:

>>> import sawmill
>>> from sawmill.log import Log
>>> sawmill.configure()
>>> sawmill.root.handle(Log(message='Hello World!'))
Hello World!

Voila! You asked Sawmill to configure() itself in a classic fashion and then called the main handle method with a Log instance. The resulting message appeared on sys.stderr.

Note

sawmill.configure() is a helper function that attempts to configure Sawmill in a way that is useful to most applications. However, there is no requirement to use this helper or the default configurators (see Configuration).

Loggers

If you find that you are regularly inputting the same information for each log instance then you can create a Logger() to hold common information. Any values you set on the logger will automatically propagate into the log messages you generate using the log() method:

>>> logger = Logger(name='my.logger')
>>> logger.log(message='Hi there')
my.logger:Hi there
>>> logger['level'] = 'info'
>>> logger.log(message='Some information')
my.logger:info:Hi there

Sawmill comes with some different logger implementations to handle common scenarios, but you can also define your own. Here is the Classic logger in action that mimics the standard Python logger behaviour:

>>> from sawmill.logger.classic import Classic as Logger
>>> logger = Logger('my.logger')
>>> logger.info('An informational message')
my.logger:info:An informational message
>>> logger.error('An error message')
my.logger:error:An error message

Handlers

So, how are those messages ending up on sys.stderr? This is because the configure function adds a Stream handler configured to output all messages to standard error. It does this by registering the handler with the root handler which, by default, is a Distribute handler. The distribute handler simply relays all the logs it receives to other handlers registered with it.

Let’s add another stream handler to the root handler, but this time outputting to a StringIO instance:

>>> from StringIO import StringIO
>>> from sawmill.handler.stream import Stream
>>> my_stream = StringIO()
>>> my_handler = Stream(stream=my_stream)

All that you have to do to register a handler with a distribute handler is set it with a unique key on the handlers dictionary of the distribute handler:

>>> sawmill.root.handlers['my_handler'] = my_handler

Now we can log as normal using our logger from before:

>>> logger.info('Some more information.')
my.logger:info:Some more information.

Same as before, but take a look at my_stream:

>>> print my_stream.getvalue()
{'name': 'my.logger', 'level': 'info', 'message': 'Some more information.'}

The reason it contains just a string representation of the log (dictionary) is because no formatter has been set on our custom handler.

Formatters

A formatter takes a list of Log instances and returns a corresponding list of formatted data that a handler can output. Typically the returned data will be a string, but it is important to note that it does not have to be. The only condition is that the returned data works with the handler’s output method.

Note

Due to the tight contract between a formatter and handler you cannot use every formatter with every handler. Instead check the documentation for which ones work well together.

Add a Template formatter to the handler created above:

>>> from sawmill.formatter.template import Template
>>> my_formatter = Template('{level}:{message}\n')
>>> my_handler.formatter = my_formatter

Now logging a message will result in the formatter being called for the handler my_handler:

>>> my_stream.truncate(0)
>>> logger.info('Yet more information.')
>>> print my_stream.getvalue()
info:Yet more information.

Filterers

A filterer controls whether a log should be handled by a particular handler. A typical usage of a filterer is to restrict a particular handler to only handle serious errors. Add a Level filterer to my_handler so that it only handles error messages (or greater):

>>> from sawmill.filterer.level import Level
>>> my_handler.filterer = Level(min='error', max=None)

Note

The level values available and their respective order is set, by default, according to the sawmill.levels array.

Now try logging an info level message:

>>> my_stream.truncate(0)
>>> logger.info('I will not appear in the stringio instance.')
my.logger:info:I will not appear in the stringio instance.

Whilst the log was still handled by the default stream handler (that does not filter info level messages) it was not handled by my_handler:

>>> print my_stream.getvalue()

If you wanted a group of handlers to have the same filterer you could set them up under a distribute handler and then set the filterer on that handler. For example, here is how to limit all the handlers using a filterer on the root handler:

>>> sawmill.root.filterer = Level(min='error', max=None)
>>> logger.info('I will not appear anywhere.')

You can also quickly combine different filterers for more complex effects:

>>> from sawmill.filterer.pattern import Pattern
>>> sawmill.root.filterer &= Pattern('my\..*', mode=Pattern.EXCLUDE)

The above would filter any log that had too low a level or had a name value that started with ‘my.’.