DEV Community

Akhilesh Mishra
Akhilesh Mishra

Posted on • Originally published at livingdevops.com

I Ditched Python Built-In Logging For Loguru — You Should Too

A Complete Guide to better logging in Python with loguru

Photo by bady abbas on Unsplash

I have been using Python's built-in logging module for years, I have always found it quite boring. It cannot be customized beyond a point.

I wanted to include some color coding in my log messages, but had to use a third-party package, for it. I wished Python built-in logging module has options to customise logs with ease.

Then I stumbled upon loguru and found it interesting. I spent some time with this yesterday and fell in love with it.

By the end of this blog post, you will fall for it too.

Understanding Log Levels

When talking about logging, loglevel is an important term, they are like a severity scale for your messages. By assigning different levels to log messages, it becomes easier to focus on critical issues while reducing noise from less important events while troubleshooting or monitoring.

While the Python builtin module comes with DEBUG, INFO, WARNING, ERROR, and CRITICAL, loguru include 2 more log levels, TRACE and SUCCESS.

Here is the table to remember log levels in order of increasing priority:

Level Priority
TRACE 5
DEBUG 10
INFO 20
SUCCESS 25
WARNING 30
ERROR 40
CRITICAL 50

Enough with the talking, let me show you what we can do with loguru and why I fell in love with it.

Installing Loguru

pip install loguru
Enter fullscreen mode Exit fullscreen mode

Basic Usage

To use Loguru, import the logger from loguru module and use it.

from loguru import logger

logger.trace("Hi, This is Akhilesh Mishra")
logger.debug("I will show how to use loguru for better logging in python")
logger.info(" I love how the logs look")
logger.warning("Different color of each section of log")
logger.error("Easy to get started with")
logger.critical( "So many options to choose from")
logger.success(" You see what i am talking about")
Enter fullscreen mode Exit fullscreen mode

Image description
Output format: date | level | file location: scope: line number - message

You can see that the trace output is not printed. Default log level for loguru is debug.

Changing the Default Log Level

We can use logger's add() function to change the default loglevel, update the log's formatting.

import sys
from loguru import logger

logger.add(sys.stderr, level="TRACE")
logger.trace("Hi, This is Akhilesh Mishra")
Enter fullscreen mode Exit fullscreen mode

Image description

Change the Formatting with Loguru

Unlike Python's built-in logging module, you can add a handler, update formatting, and change the log level in just one line — with add() function.

import sys
from loguru import logger

logger.remove() # remove the old formatting 
logger.add( sys.stdout, format="{time}::{level} --- {message}", level="INFO")

logger.debug(" Add a handler, update formatting, and change the loglevel")
logger.info(" one function to rule them all")
logger.success(" logger.add()")
Enter fullscreen mode Exit fullscreen mode

Image description

Pretty Logging with Colors

You can change the color of these output messages by using HTML like syntax.

logger.remove()
logger.add(
    sys.stdout,
    format=" <yellow>{time} </yellow>:: <green> <bold> {level} </bold> </green>--- <blue> {message} </blue>",
)

logger.info("Set the colors you want to use for logs")
logger.success(" How easy it was???")
Enter fullscreen mode Exit fullscreen mode

Image description

Change the Time Formatting

import sys
from loguru import logger

logger.remove()
# MMMM D, YYYY > HH:mm:ss!UTC : UTC time
logger.add(sys.stderr, format="{time:MMMM D, YYYY > HH:mm:ss!UTC} | {level} | <level>{message} </level>")

logger.warning(" Use time format-> {time:MMMM D, YYYY > HH:mm:ss!UTC}")
logger.success("Use the default color from level: <level>{message} </level>")
Enter fullscreen mode Exit fullscreen mode

You can use more options with time, check the loguru documentation for more time formatting options.

Image description

Sending Logs to a File

Loguru sends logs to the console by default, but you can configure it to send logs to a file.

logger.add("log_file_demo.log")
Enter fullscreen mode Exit fullscreen mode

Let's use formatted logs:

from loguru import logger

logger.remove()
logger.add("file_{time}.log", format="{time:MMMM D, YYYY > HH:mm:ss} | {level} | <level>{message} </level>")

logger.info("using file logging")
logger.success(" will send logs to the file")
Enter fullscreen mode Exit fullscreen mode

Image description

Log Rotation, Retention, and Compression

Loguru also allows you to rotate/retain/compress logs with time and size filters.

logger.add("log_rotate.log", rotation="500 MB")    # Automatically rotate too big file
logger.add("log_rotate2.log", rotation="12:00")     # New file is created each day at noon
logger.add("log_rotate3.log", rotation="1 week")    # Once the file is too old, it's rotated

logger.add("log_retention.log", retention="10 days")  # Cleanup after some time

logger.add("log_retention2.log", compression="zip")    # Save some loved space
Enter fullscreen mode Exit fullscreen mode

JSON Logging

Loguru supports logging in JSON format with serialize=True option.

import sys
from loguru import logger

logger.remove(0)
logger.add(
    sys.stderr,
    format="{time:MMMM D: YYYY:: HH:mm:ss!UTC} | {level} | {message}",
    serialize=True,
)
logger.warning(" Its addictive, use with caution !")
logger.success("I know you started liking loguru")
Enter fullscreen mode Exit fullscreen mode

Image description

Adding Context to Log Messages

Suppose you want to add some additional information to the log message for context, you can use the bind() method.

You can add directive extra to the format in your logger add() method to add custom entries to the logs output.

import sys
from loguru import logger

# Remove the default logger
logger.remove(0)

# Add a new logger that outputs to sys.stderr
logger.add(
    sys.stderr,
    format=" {level} | <level>{message}</level> | {extra} ",
)

# Create a new logger with some initial context
context_logger = logger.bind(author="Akhilesh", type="demo")

# Log an info message with the current context
context_logger.info("You can pass context with logs!")
Enter fullscreen mode Exit fullscreen mode

Image description

You can further customize the context:

# Bind additional context to the logger and log a warning message
context_logger.bind(blog_type="Tutorial").warning(
    "You can use extra attributes to bind context!"
)
# Log a success message with additional context provided during formatting
context_logger.success(
    "Use kwargs to add context during formatting: {platform}", platform="Medium"
)
Enter fullscreen mode Exit fullscreen mode

Image description

Using Context Managers

We can also use the Python context manager to modify a context-local state temporarily with contextualize():

import sys
from loguru import logger

logger.remove(0)

logger.add(
    sys.stderr,
    format=" {level} | <level>{message}</level> | {extra} ",
)

context_logger = logger.bind(blog_id=45)

def do_something():
    context_logger.debug("doing something")

with logger.contextualize(scope="From context manager"):
    do_something()

do_something()
Enter fullscreen mode Exit fullscreen mode

Image description

Filtering Logs

Combine bind() and filter for fine-grained control over your logs.

from loguru import logger

logger.add("special.log", filter=lambda record: "special" in record["extra"])
logger.debug("This message is not logged to the file")
logger.bind(special=True).info("This message, though, is logged to the file!")
Enter fullscreen mode Exit fullscreen mode

Image description

Dynamic Values with patch()

The patch() method allows you to attach dynamic values to be attached to each new message:

import sys
from loguru import logger
from datetime import datetime

logger.remove(0)
logger.add(sys.stderr, format="{extra[utc]} - {level}- {message}")
logger = logger.patch(lambda record: record["extra"].update(utc=datetime.now()))

logger.info("using patch method from loguru")
Enter fullscreen mode Exit fullscreen mode

Image description

Create Custom Log Levels

Loguru allows you to create your own log level with level() function.

import sys
from loguru import logger

logger.remove(0)

m_level = logger.level("Medium", no=45, color="<yellow><bold>", icon="/\\/\\")
n_level = logger.level("Nedium", no=45, color="<blue><bold>", icon="|\\|")

logger.add(sys.stderr, format=" <level> {level.icon} :: {message} </level>")

logger.log("Medium", "This is my custom log level")
logger.log("Nedium", "I like having optional log levels")
Enter fullscreen mode Exit fullscreen mode

Image description

Logging Exceptions

Logging exceptions is crucial for tracking bugs, but it's not helpful if you don't know the cause. Loguru makes it easier by showing the entire stack trace, including variable values so you can identify the problem.

from loguru import logger

logger.remove(0)

# Caution, "diagnose=True" is the default and may leak sensitive data in prod
logger.add("loguru.log", backtrace=True, diagnose=True)

def func(a, b):
    return a / b

def nested(c):
    try:
        func(5, c)
    except ZeroDivisionError:
        logger.exception("Did you just??")

nested(0)
Enter fullscreen mode Exit fullscreen mode

Image description
You can also use it with context manager by using logger.catch() decorator.

from loguru import logger

logger.remove(0)

# Caution, "diagnose=True" is the default and may leak sensitive data in prod
logger.add("loguru.log", backtrace=False, diagnose=False)

@logger.catch()
def func(a, b):
    return a / b

func(5,0)
Enter fullscreen mode Exit fullscreen mode

Image description
By default the level you will see ERROR level, but you can customize it to use different levels.

Image description

Final Words

If you got to this point, you know how nice loguru is. If you love customization and making things pretty, you will love it.

Let me know what you think about it in the comments. If you found it useful, don't forget to like and follow for more Python tutorials!

Connect with the author on LinkedIn

Top comments (0)