Logging should be lazy (because programmers are)

Alessio Izzo
flowe.ita
Published in
4 min readOct 15, 2021

--

Logging is one of the most important thing in programming. I like to call it an “art”. This is because if you are good at logging things, you can save a lot of (your or your teammates) time when:

  • something goes wrong in production
  • you are working on a new piece of code
  • you are refactoring some old program that is somehow complicated

That said, there are some best practices in the art of logging. We would like to show some performance-related issues that you can easily implement in your everyday code.

We are going to use Python for this purpose.

Python logging

Here is the hello world for logging in python

Here, we use warning as log level because this is the default level set in python when you use the logging module (which use an instance of the logger class called root). In fact, if you try to run

Nothing is displayed, because the info level is below the warning level and no message is displayed.

This is the definition of the warning function in the logging module

The same structure is applied for the other log levels definitions. So basically, we have to pass the message as the first argument, but we can pass more arguments after this one.

This is the key in the lazy logging pattern. We’ll see later why is that so important.

Talk is cheap

Let’s show some code.

So the RaiseWhenPrinted class is raising an exception whenever you try to call its __str__ method. This happens for example when you want to log the instance of this object raise_when_printed. Let’s do it

So even if the logging level is below info, the exception is raised. This means that the __str__ method of the RaiseWhenPrinted instance was called even if the log level is not set to info.

Here is where lazy logging comes in. Let’s modify our code

In this case, no exception is raised. This means that the __str__ method was not called. So we made our log lazy, because it is not been evaluated unless we actually need it.

In fact, if we run

we receive the full stack trace of the exception, which means that the __str__ method was called as expected.

Why is that?

The explanation is quite simple (and very elegant in my opinion).

Let’s recall the definition of the info log level function (which is the same as the warning definition)

So if we write our log command in the code using the format method to concatenate strings (same apply for f-strings)

we are basically telling the interpreter that the msg argument is the string "Logging {}.format(raise_when_printed)"that is evaluated immediately at runtime.

Instead, writing the command in the lazy way

tells the interpreter that the msg argument is "Logging %s”which is a common string and the other argument is the raise_when_printed instance. Here, the __str__ method has not been called yet.

When the program reaches this line, the info function is called and internally it checks if the log level is the one set. If not, nothing happens. Otherwise the string is formed.

This is basically the definition of laziness in programming.

Time it!

Let’s talk about performances. Here we use the timeit library. This library can help us calculating how much time a command takes to execute.

So basically we create our two logging commands lazy and not_lazy because timeit wants a string as first argument (it calls exec on that string). The setup variable is important because it tells timeit which commands should be run to set up the environment. If we run the two commands we get

So the difference is not so big, but we must notice that the string to log is super easy. With this in mind, the difference is quite big.

Summing up

Lazy logging is not a common practice in python, even if this is the example provided in the official documentation.

I cannot say that in Flowe we log everything in this way, but we are aware of this and are gradually refactoring our code by transforming our logs in lazy logs.

But if you are starting a brand new project, you should definitely consider using this approach.

More on the topic (aka references)

--

--