Easy Python logging with daiquiri

I'll drink to that.

Tuesday 04 July 2017 Python, daiquiri Comments

After more than 10 years of writing Python, there's something I always have been annoyed with: logging.

Don't read me wrong: I like the Python logging subsystem. It's easy to use and works like a charm in most cases. If you never used it, logging in Python turns down to be as simple as:

import logging
 
logger = logging.getLogger()
logger.info("Something useful")


It could barely be easier. What annoys me is that if you run the example above, an error will happen. See by yourself:

>>> import logging
>>> logger = logging.getLogger()
>>> logger.error("Something useful")
No handlers could be found for logger "root"


Nothing is printed, except an error. No log file is created. Logging does not work "by default". I hate it.

Each time I write a new application, I need to remember how to set logging up. There's a full API, documented, that explains how to setup handlers, formatters, filters, or a record factory. And each time I need to dig into all that documentation to remember how to set some sane defaults (e.g. log to stderr in a format with a timestamp). I could use logging.basicConfig, but it's usually too basic (e.g. it does not print any timestamp).

Each time I go down the rabbit-hole of tweaking logging.

Here comes daiquiri

I finally took some time recently to bootstrap a tiny library to do this job for me. It's named daiquiri, and it does only one thing: configure the Python logging subsystem for modern Python applications.

It's small and the 1.0.0 version I just released contains 228 lines of code and 79 lines of tests. That's it!

Its promise is to setup a complete standard Python logging system with just one function call. Nothing more, nothing less. The interesting features are:

  • Logs to stderr by default.
  • Use colors if logging to a terminal.
  • Support file logging.
  • Use program name as the name of the logging file so providing just a directory for logging will work.
  • Support syslog.
  • Support journald.
  • JSON output support.
  • Support of arbitrary key/value context information providing.
  • Capture the warnings emitted by the warnings module.
  • Native logging of any exception.

And it's used by Gnocchi starting with version 4.0. That should say how long it's production ready, right? 😀

Enough selling. Let's see how it looks by default!

Basic working

Here's the basic usage of daiquiri:

import daiquiri
 
daiquiri.setup()


I told you I want it to be simple. Just doing this is already doing a better job than logging.basicConfig, since it'll do something useful by default:

>>> import daiquiri
>>> daiquiri.setup()
>>> logger = daiquiri.getLogger()
>>> logger.error("something wrong happened")
2017-07-04 18:03:04,929 [16876] ERROR root: something wrong happened


It does print the message on stderr using a useful formatting and a timestamp by default. Just what everybody wants, isn't it? If you run this on a terminal, the line will be printed in red as it is an error that is logged. Other colors will be used for different logging levels (green for debug, etc).

Better, daiquiri will log any exception in your program:

>>> import daiquiri
>>> daiquiri.setup()
>>> raise Exception("boom!")
2017-07-04 18:05:43,378 [16959] CRITICAL root: Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Exception: boom!


As soon as an exception is uncaught, it'll be logged as a critical log message.

More advanced features

If you want to tweak the default output, you can pass some arguments to daiquiri.setup. This function accepts a outputs argument that must be an iterable of daiquiri.Output objects. This is typically a list of daiquiri.File object to log to a file, daiquiri.Syslog to log to syslog or daiquiri.Stream to log to any stream (e.g. an opened file, stdout or stderr).

If you want to log via syslog but also to stderr, here's what you'll have to do:

daiquiri.setup(outputs=(
daiquiri.output.Syslog(),
daiquiri.output.STDERR,
))


If you want to log to a file, you can just specify a directory, daiquiri will guess the program name and creates the appropriate file:

# If the program name is foobar-server then the logging will
# be done to /var/log/foobar-server.log
daiquiri.setup(outputs=(
daiquiri.output.File(directory="/var/log"),
))


Those examples might be too easy. So let's log to journald and also to a network server using JSON output:

import socket
import daiquiri
 
# Let's connect to the server first
s = socket.socket()
# You can run a simple server in another terminal by typing `nc -l 2333`
s.connect(("localhost", 2333))
f = s.makefile()
 
daiquiri.setup(outputs=(
daiquiri.output.Journal(),
daiquiri.output.Stream(f, formatter=daiquiri.formatter.JSON_FORMATTER),
))
daiquiri.getLogger().error("oops", somekey=42, anotherkey="foobar")
# Server will receive:
# {"message": "oops", "somekey": 42, "anotherkey": "foobar"}


You can obviously extend it with your own formatter or outputs, the API is pretty simple. But the default should be usable for 99% of applications.

Let me know what you think and feel free to pip install and git clone it! The library is available at PyPI, the source is on GitHub and the documentation is published online.