Skip to content

Logging

What is the Lilya LoggingConfig

In building applications, logging is one of the most crucial parts of observability, debugging, and monitoring.

Originally, Lilya relied on developers manually configuring their own logging systems. However, to provide a consistent, extensible, and framework-integrated way to handle logging, Lilya introduced a first-class logging system built around the LoggingConfig concept.

The goals of introducing LoggingConfig were:

  • Consistency: Applications have a predictable and reliable way of setting up logging.
  • Flexibility: Support standard Python logging, Loguru, Structlog, or any custom backend.
  • Simplicity: Only one central logger (lilya.logging.logger) needs to be imported and used across the entire app.
  • Extensibility: Developers can easily subclass and provide their own custom logging configurations.

Lilya now automatically configures a global logger instance, based on the LoggingConfig provided during startup.

How LoggingConfig Works

LoggingConfig is an abstract base class that defines how to configure the logging system.

Lilya provides:

  • StandardLoggingConfig — for classic Python logging.

You can also implement your own custom logging backend by subclassing LoggingConfig.

When setup_logging(logging_config) is called during app startup (you don't have access to this as this is internal), Lilya:

  1. Applies the provided LoggingConfig.
  2. Binds the global lilya.logging.logger to the correct backend.

Note

After configuration, importing and using from lilya.logging import logger will always point to the correct logging system automatically.

Available Logging Backends

Standard Python Logging

Default behavior if nothing is specified.

from lilya.apps import Lilya

app = Lilya()

Or inside Lilya (with an example of a loguru logger):

import sys

from typing import Any
import loguru

from lilya.apps import Lilya
from lilya.logging import LoggingConfig


class LoguruConfig(LoggingConfig):
    def __init__(self, level: str, **kwargs):
        super().__init__(level=level, **kwargs)

    def configure(self):
        loguru.logger.remove()
        loguru.logger.add(
            sink=sys.stdout,
            level=self.level,
            format="{time} {level} {message}",
            colorize=True,
        )

    def get_logger(self) -> Any:
        return loguru.logger


logging_config = LoguruConfig(level="INFO")

app = Lilya(logging_config=logging_config)

Defining a Custom Logging Configuration

You can easily define your own LoggingConfig by subclassing it:

import sys

from typing import Any

import loguru

from lilya.logging import LoggingConfig


class CustomLoggingConfig(LoggingConfig):
    def __init__(self, level: str, **kwargs):
        super().__init__(level=level, **kwargs)
        self.options = kwargs

    def configure(self):
        """
        Configures the logger by removing any existing handlers and adding a new one.
        """
        loguru.logger.remove()
        loguru.logger.add(
            sink=sys.stdout,
            level=self.level,
            format="{time} {level} {message}",
            colorize=True,
        )

    def get_logger(self) -> Any:
        """
        Returns the logger instance.
        """
        return loguru.logger

When creating a custom logger you must declare the following methods:

  • configure() - This method is called to set up the logging configuration.
  • get_logger() - This method should return the logger instance.

These methods are called during the application startup process and will make sure the logger is set up correctly using a common interface.

Usage:

from lilya.apps import Lilya

app = Lilya(logging_config=CustomLoggingConfig())

Using logging_config via Settings

If you are using settings classes to configure your app, you can pass the logging configuration there too:

from dataclasses import dataclass

from lilya.conf import Settings
from lilya.logging import LoggingConfig

from myapp.logging import LoguruLoggingConfig


@dataclass
class CustomSettings(Settings):
    @property
    def logging_config(self) -> LoggingConfig:
        return LoguruLoggingConfig(level="DEBUG")

Then when initializing Lilya:

from lilya.apps import Lilya

app = Lilya(settings_config=AppSettings())

Tip

You can also initialise Lilya settings via ESMERALD_SETTINGS_MODULE as you can see in settings documentation. This is useful for separating your settings from the application instance.

Lilya will automatically extract and configure the logger from your settings class.

Accessing the Global Logger

Anywhere in your project, simply do:

from lilya.logging import logger

logger.info("This is a log message.")
logger.error("This is an error log.")

No matter what backend you use (standard, Loguru, Structlog), the logger will behave correctly.

Important Notes

  • If you need to reconfigure logging during runtime (e.g., in tests), you should explicitly teardown/reset first.
  • If no logging_config is provided, Lilya defaults to a safe StandardLoggingConfig.

Conclusion

LoggingConfig provides a powerful, flexible, and unified way to configure logging in Lilya applications.

It ensures that regardless of the logging backend used, your app's logging behavior is predictable, extensible, and simple to maintain.