Twiggy 0.4.4

A more Pythonic logger
Twiggy is an early-stage project to build a more Pythonic logging package.


Make display work:

>>> import sys; sys.stderr = sys.stdout

Setup is simple

In your

>>> import twiggy
>>> twiggy.quick_setup()

Logging Messages

>>> from twiggy import *

Main interface is the the magic log:

>>> log #doctest:+ELLIPSIS
< twiggy.Logger.Logger object at 0x... >

It works out of the box, using standard levels:

>>> log.debug('You may not care')
DEBUG:You may not care
>>> log.error('OMFG! Pants on fire!')
ERROR:OMFG! Pants on fire!

It supports a variety of format strings, defaulting to new-style:

>>>'I wear {0} on my {where}', 'pants', where='legs')
INFO:I wear pants on my legs

Old style works fine though:

>>> log.options(style='percent').info('I like %s', "bikes")
INFO:I like bikes

As do templates:

>>> log.options(style='dollar').info('$what kill', what='Cars')
INFO:Cars kill

You can name your loggers:

>>> mylog ='alfredo')
>>> mylog.debug('hello')

But the name has no relation to the object; it's just for human use:

>>> mylog is'alfredo')

Emitting Messages

Emitters are loosely coupled:

>>> twiggy.emitters #doctest:+ELLIPSIS
{'*': < twiggy.Emitter.Emitter object at 0x... >}

You can set a min_level on Emitters:

>>> twiggy.emitters['*'].min_level = twiggy.Levels.INFO
>>> log.debug("Help, help I'm being suppressed")
>>>"I'm not quite dead yet")
INFO:I'm not quite dead yet

You can filter on regexes, or with arbitrary functions:

>>> twiggy.emitters['*'].filter = ".*pants.*"
>>>"Got my {0} on", "pants")
INFO:Got my pants on
>>>"Got my {0} on", "shirt")

Let's reset all that:

>>> twiggy.emitters['*'].filter = True
>>> twiggy.emitters['*'].min_level = twiggy.Levels.DEBUG

Better output

Newlines are suppressed by default; that can be turned off per-message:


>>> log.options(suppress_newlines=False).info('we\ndeal')

Exceptions are prefixed. Can also pass exc_info. Use '\n' as a prefix to fold into a single line:

>>> try:
... 1/0
... except:
... log.trace('error').warning('oh noes') #doctest:+ELLIPSIS
WARNING:oh noes
TRACE Traceback (most recent call last):
TRACE File "< doctest notes.txt[...] >", line 2, in
TRACE ZeroDivisionError: integer division or modulo by zero

Method Chaining

I like this chained style a lot.

>>>'benito').info('hi there')
INFO:benito:hi there

It makes structured logging easy:

>>> log.fields(paths=42).info('Going for a walk')
INFO:paths=42:Going for a walk

Short cut. Great for runtime statistics gathering.

>>> log.struct(paths=42, dolphins='thankful')

Partial binding can be useful for webapps:

>>> per_request_log = log.fields(request_id='12345')
>>> per_request_log.fields(rows=100, user='frank').info('frobnicating database')
INFO:request_id=12345:rows=100:user=frank:frobnicating database
>>> per_request_log.fields(bytes=5678).info('sending page over tubes')
INFO:bytes=5678:request_id=12345:sending page over tubes

Chained style is awesome:

>>>'donjuan').fields(pants='sexy').info("hello, {who} want to {what}?", who='ladies', what='dance')
INFO:donjuan:pants=sexy:hello, ladies want to dance?


Any functions in args/fields are called and the value substitued:

>>> import os
>>> from twiggy.lib import thread_name
>>> thread_name()
>>> log.fields(pid=os.getpid).info("I'm in thread {0}", thread_name) #doctest:+ELLIPSIS
INFO:pid=1076:I'm in thread MainThread

This can be useful with partially-bound loggers, which let's us do some cool stuff:

>>> class ThreadTracker(object):
... def __init__(self, obj):
... self.__obj = obj
... # a partially bound logger
... self.__log ="tracker").fields(obj_id=id(obj), thread=thread_name)
... self.__log.debug("started tracking")
... def __getattr__(self, attr):
... self.__log.debug("accessed {0}", attr)
... return getattr(self.__obj, attr)
>>> class Bunch(object):
... pass
>>> foo = Bunch()
>>> = 42
>>> tracked = ThreadTracker(foo)
DEBUG:tracker:obj_id=14063980:thread=MainThread:started tracking
DEBUG:tracker:obj_id=14063980:thread=MainThread:accessed bar
>>> import threading
>>> t=threading.Thread(target = lambda: * 2, name = "TheDoubler")
>>> t.start()
DEBUG:tracker:obj_id=14063980:thread=TheDoubler:accessed bar

If you really want to log a callable, repr() it or wrap it in lambda.


As an optimization, a min_level can be set on loggers:

>>> mylog.min_level = twiggy.Levels.INFO
>>>"You see this")
INFO:alfredo:You see this
>>> mylog.debug("This is hidden")

They also take a filter that operates on format_spec. The use case is efficiently shutting off specific messages in a library which is doing something stupid:

>>> mylog.filter = lambda s: "shenanigans" not in s
>>>"Starting silliness")
INFO:alfredo:Starting silliness
>>> for i in xrange(3): # for large values of 3
..."I call shenanigans!")
>>>"End silliness")
INFO:alfredo:End silliness

last updated on:
July 14th, 2011, 10:45 GMT
developed by:
Peter Fein
license type:
BSD License 
ROOT \ System \ Logging


In a hurry? Add it to your Download Basket!

user rating



Add your review!