Loggers

Bound Loggers

The center of structlog is the immutable log wrapper BoundLogger.

_images/BoundLogger.svg

What it does is:

  • Store a context dictionary with key-value pairs that should be part of every log entry,

  • store a list of processors that are called on every log entry,

  • and store a logger that it’s wrapping. This can be logging.Logger but absolutely doesn’t have to.

To manipulate the context dictionary, it offers to:

  • Recreate itself with (optional) additional context data: bind() and new().

  • Recreate itself with less context data: unbind().

In any case, the original bound logger or its context are never mutated.

Finally, if you call any other method on BoundLogger, it will:

  1. Make a copy of the context – now it becomes the event dictionary,

  2. Add the keyword arguments of the method call to the event dict.

  3. Add a new key event with the value of the first positional argument of the method call to the event dict.

  4. Run the processors on the event dict. Each processor receives the result of its predecessor.

  5. Finally it takes the result of the final processor and calls the method with the same name that got called on the bound logger on ther wrapped logger1. For flexibility, the final processor can return either a string that is passed directly as a positional parameter, or a tuple (args, kwargs) that are passed as wrapped_logger.log_method(*args, **kwargs).

1

Since this is slightly magicy, structlog comes with concrete loggers for the Standard Library Logging and Twisted that offer you explicit APIs for the supported logging methods but behave identically like the generic BoundLogger otherwise. Of course, you are free to implement your own bound loggers too.

Creation

You won’t be instantiating it yourself though. In practice you will configure structlog as explained in the next chapter and then just call structlog.get_logger().

In some rare cases you may not want to do that. For that times there is the structlog.wrap_logger() function that can be used to wrap a logger without any global state (i.e. configuration):

>>> from structlog import wrap_logger
>>> class PrintLogger(object):
...     def msg(self, message):
...         print(message)
>>> def proc(logger, method_name, event_dict):
...     print("I got called with", event_dict)
...     return repr(event_dict)
>>> log = wrap_logger(PrintLogger(), processors=[proc], context_class=dict)
>>> log2 = log.bind(x=42)
>>> log == log2
False
>>> log.msg("hello world")
I got called with {'event': 'hello world'}
{'event': 'hello world'}
>>> log2.msg("hello world")
I got called with {'x': 42, 'event': 'hello world'}
{'x': 42, 'event': 'hello world'}
>>> log3 = log2.unbind("x")
>>> log == log3
True
>>> log3.msg("nothing bound anymore", foo="but you can structure the event too")
I got called with {'foo': 'but you can structure the event too', 'event': 'nothing bound anymore'}
{'foo': 'but you can structure the event too', 'event': 'nothing bound anymore'}

As you can see, it accepts one mandatory and a few optional arguments:

logger

The one and only positional argument is the logger that you want to wrap and to which the log entries will be proxied. If you wish to use a configured logger factory, set it to None.

processors

A list of callables that can filter, mutate, and format the log entry before it gets passed to the wrapped logger.

Default is [StackInfoRenderer, format_exc_info(), TimeStamper, ConsoleRenderer].

context_class

The class to save your context in. Particularly useful for thread local context storage.

On Python versions that have ordered dictionaries (Python 3.6+, PyPy) the default is a plain dict. For everything else it’s collections.OrderedDict.

Additionally, the following arguments are allowed too:

wrapper_class

A class to use instead of BoundLogger for wrapping. This is useful if you want to sub-class BoundLogger and add custom logging methods. BoundLogger’s bind/new methods are sub-classing friendly so you won’t have to re-implement them. Please refer to the related example for how this may look.

initial_values

The values that new wrapped loggers are automatically constructed with. Useful for example if you want to have the module name as part of the context.

Note

Free your mind from the preconception that log entries have to be serialized to strings eventually. All structlog cares about is a dictionary of keys and values. What happens to it depends on the logger you wrap and your processors alone.

This gives you the power to log directly to databases, log aggregation servers, web services, and whatnot.

Printing and Testing

To save you the hassle and slowdown of using standard library’s logging for standard out logging, structlog ships a PrintLogger that can log into arbitrary files – including standard out (which is the default if no file is passed into the constructor):

>>> from structlog import PrintLogger
>>> PrintLogger().info("hello world!")
hello world!

Additionally – mostly for unit testing – structlog also ships with a logger that just returns whatever it gets passed into it: ReturnLogger.

>>> from structlog import ReturnLogger
>>> ReturnLogger().msg(42) == 42
True
>>> obj = ["hi"]
>>> ReturnLogger().msg(obj) is obj
True
>>> ReturnLogger().msg("hello", when="again")
(('hello',), {'when': 'again'})