Skip to content

Logging

Quality Score

Overall Score: 9.0/10 ⭐ Outstanding

  • Technical Accuracy: 30/35
  • Code Quality: 22/25
  • Educational Value: 24/25
  • Documentation: 14/15

Last reviewed: June 22, 2026

Logging is a very important part of any application. It allows you to track the code execution and to debug the application. Python has a built-in logging module that allows you to log messages to the console, to a file, or to a remote server. In contrast to the print function, the logging module is more complete, allowing you to configure the log level, the log format, and the log destination.

Logging is based in handlers. A handler is an object that receives the log messages and decides what to do with them. The logging module has several built-in handlers, such as StreamHandler, FileHandler, RotatingFileHandler or TimedRotatingFileHandler. But you can create your own handler by inherit the Handler class. Notifiers is a 3pp library that provides with extra handlers with the ability to send notifications to different services.

Any handler can have different configurations, such as the log level or the log format. The log level is used to filter the log messages. The log format is used to format the log messages. You can use the built-in log formats or create your own format by using the Formatter class. Also, a logger can have filters to filter the log messages before they are sent to the handlers. This way you can have more control over the log messages, like modifying or discarding them.

Best practices

  • Set different log levels for different environments. For example, you may set DEBUG level in development and ERROR level in production.
  • Set a specific format for the log messages, including the timestamp or the log level. Using a standard format makes it easier to read the log messages.
  • Use the extra parameter to pass the data to the log message.
  • Use pipelines | to separate the different parts of the log message. It can be useful to filter the log messages, or even to parse them.
  • To include variables in your log message aside from extra, don't use format or f-string in the log call. Instead use the %s, like logger.info('Variable: %s', value).
  • Use logging.exception to log an exception message and the stack trace.
  • Set the different logger instance you are going to use with logging.getLogger. This way you can configure the logger in one place and use it in different modules.

logging library

This is the built-in Python logging library. It is very flexible and allows you to configure the log level, the log format, and the log destination.

Each logger has a name, and the loggers are organized in a tree-like structure. The root logger is the top-level logger, and all other loggers are children of the root logger.

src.intermediate.logging.default_logging(level)

Example of default logging configuration with basic usage.

Shows how to use Python's built-in logging module without custom configuration. Logs messages at all severity levels to demonstrate the default output format.

Parameters:

Name Type Description Default
level int

The logging level to set. Must be one of the standard logging levels: DEBUG (10), INFO (20), WARNING (30), ERROR (40), or CRITICAL (50).

required

Raises:

Type Description
IndexError

If the provided level is not a valid logging level.

Source code in src/intermediate/logging/custom_logging.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def default_logging(level: int):
    """Example of default logging configuration with basic usage.

    Shows how to use Python's built-in logging module without custom
    configuration. Logs messages at all severity levels to demonstrate
    the default output format.

    Args:
        level: The logging level to set. Must be one of the standard
            logging levels: DEBUG (10), INFO (20), WARNING (30), ERROR (40),
            or CRITICAL (50).

    Raises:
        IndexError: If the provided level is not a valid logging level.
    """
    if level not in logging._levelToName:
        raise IndexError("Invalid level to set logging.")

    logging.debug("DEBUG logging")
    logging.info("INFO logging")
    logging.warning("WARNING logging")
    logging.error("ERROR logging")
    logging.critical("CRITICAL logging")

src.intermediate.logging.custom_logging_format(format, datefmt)

Example of custom logging format and date format configuration.

Shows how to customize the logging output by specifying a format string for the log message and a date format string. Additional configuration options like filename and filemode can be used with basicConfig to write logs to files.

Parameters:

Name Type Description Default
format str

Format string for log messages. Can include fields like %(levelname)s, %(message)s, %(asctime)s, etc.

required
datefmt str

Format string for timestamps using time.strftime() format codes (e.g., '%Y-%m-%d %H:%M:%S').

required
Note

Additional basicConfig parameters: - filename: File path for logging with FileHandler - filemode: Mode to open the log file (e.g., 'a' for append)

Source code in src/intermediate/logging/custom_logging.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def custom_logging_format(format: str, datefmt: str):
    """Example of custom logging format and date format configuration.

    Shows how to customize the logging output by specifying a format
    string for the log message and a date format string. Additional
    configuration options like filename and filemode can be used with
    basicConfig to write logs to files.

    Args:
        format: Format string for log messages. Can include fields like
            %(levelname)s, %(message)s, %(asctime)s, etc.
        datefmt: Format string for timestamps using time.strftime() format
            codes (e.g., '%Y-%m-%d %H:%M:%S').

    Note:
        Additional basicConfig parameters:
        - filename: File path for logging with FileHandler
        - filemode: Mode to open the log file (e.g., 'a' for append)
    """
    logging.basicConfig(
        level=logging.INFO,
        format=format,
        datefmt=datefmt,
    )
    logger = logging.getLogger(__name__)

    logger.info("INFO logging formatted")

src.intermediate.logging.lazy_logging_format()

Example of lazy logging format.

Avoid using concatenation or f-strings in logging calls. This function demonstrates how to use lazy formatting in logging, where the log message is formatted only if the message is actually going to be logged. This can improve performance when logging is disabled for certain levels.

Source code in src/intermediate/logging/custom_logging.py
68
69
70
71
72
73
74
75
76
77
78
79
def lazy_logging_format():
    """Example of lazy logging format.

    Avoid using concatenation or f-strings in logging calls. This function
    demonstrates how to use lazy formatting in logging, where the log message is
    formatted only if the message is actually going to be logged. This can
    improve performance when logging is disabled for certain levels.
    """
    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger(__name__)

    logger.info("Using lazy formatting: %s", "INFO logging formatted")

src.intermediate.logging.CustomFilter

Bases: Filter

Custom logging filter to mask sensitive information in log records.

This filter extends logging.Filter to automatically detect and mask sensitive information such as passwords and email addresses in log records. It uses regular expressions to identify sensitive data and applies appropriate masking functions.

Attributes:

Name Type Description
keys_to_mask

Dictionary mapping attribute names to their masking configuration. Each configuration contains a regex pattern and the name of the masking function to apply.

Source code in src/intermediate/logging/filtering.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
class CustomFilter(logging.Filter):
    """Custom logging filter to mask sensitive information in log records.

    This filter extends logging.Filter to automatically detect and mask
    sensitive information such as passwords and email addresses in log
    records. It uses regular expressions to identify sensitive data and
    applies appropriate masking functions.

    Attributes:
        keys_to_mask: Dictionary mapping attribute names to their masking
            configuration. Each configuration contains a regex pattern and
            the name of the masking function to apply.
    """

    keys_to_mask = {
        "email": {
            "pattern": r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
            "mask_func": "mask_email",
        },
        "password": {
            "pattern": r".+",
            "mask_func": "mask_password",
        },
    }

    def filter(self, record: LogRecord) -> bool:
        """Override filter method to mask sensitive information.

        Iterates through all attributes in the log record and applies masking
        to any attributes that match keys in keys_to_mask. The masking uses
        regex patterns to identify sensitive data and applies the configured
        masking function.

        Args:
            record: The LogRecord instance to filter and potentially modify.
                Contains all information about the log event including message,
                level, and custom attributes.

        Returns:
            Always returns True to indicate the record should be logged.
            The record may be modified in-place with masked values.
        """
        for key in record.__dict__.keys():
            if key in self.keys_to_mask:
                if isinstance(record.__dict__[key], str):
                    config = self.keys_to_mask[key]
                    mask_func = getattr(self, config["mask_func"])
                    record.__dict__[key] = re.sub(
                        config["pattern"],
                        mask_func,
                        record.__dict__[key],
                    )
        return True

    def mask_password(self, match_obj: re.Match) -> str:
        """Mask password completely with asterisks.

        Replaces the entire password with asterisks, with the masked
        string having the same length as the original password. This
        provides complete concealment while preserving information about
        password length.

        Args:
            match_obj: Regular expression match object containing the
                password string to mask.

        Returns:
            String of asterisks with length equal to the original password.
        """
        return "*" * len(match_obj.group(0))

    def mask_email(self, match_obj: re.Match) -> str:
        """Mask email address local part while keeping domain visible.

        Replaces the entire local part (before @) with asterisks while
        keeping the domain visible. This provides some privacy while
        maintaining context about the email domain.

        Args:
            match_obj: Regular expression match object containing the
                email address to mask.

        Returns:
            Masked email string in format '*****@domain.com' where the
            number of asterisks matches the length of the original local part.
        """
        local_part, domain = match_obj.group(0).split("@")
        masked_local = "*" * (len(local_part))
        return f"{masked_local}@{domain}"

Always customize the logging configuration to your needs. The default configuration is very basic and may not be suitable for your application, specially to debug it.

loguru library

Loguru is a third-party library that simplifies the logging configuration to the bare minimum, such as log level and log format. But you can also can configure much more easily, such as:

  • color customization.
  • log rotation, retention and compression.
  • custom log levels.
  • lazy evaluation of log messages.

src.intermediate.logging.default_loguru()

Example of default loguru configuration with basic usage.

Shows how to use the loguru library without custom configuration. Loguru provides colorized output, better formatting, and easier configuration compared to the standard logging module. Logs messages at all severity levels to demonstrate the default output.

Source code in src/intermediate/logging/custom_logging.py
82
83
84
85
86
87
88
89
90
91
92
93
94
def default_loguru():
    """Example of default loguru configuration with basic usage.

    Shows how to use the loguru library without custom configuration.
    Loguru provides colorized output, better formatting, and easier
    configuration compared to the standard logging module. Logs messages
    at all severity levels to demonstrate the default output.
    """
    logger_loguru.debug("DEBUG loguru")
    logger_loguru.info("INFO loguru")
    logger_loguru.warning("WARNING loguru")
    logger_loguru.error("ERROR loguru")
    logger_loguru.critical("CRITICAL loguru")

src.intermediate.logging.custom_loguru_format_and_level(format, level)

Example of custom loguru configuration with format and level.

Shows how to add a custom sink to loguru with specific formatting and log level. Loguru uses the add() method to configure where logs go (sink) and how they are formatted. Sinks can be stdout, files, or custom handlers.

Parameters:

Name Type Description Default
format str

Format string for log messages. Can include fields like {level}, {message}, {time}, {name}, etc. Uses Python's string formatting syntax.

required
level str

Minimum log level as a string (e.g., 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL').

required
Note

The sink parameter in logger.add() can be: - sys.stdout or sys.stderr for console output - A file path string for file logging - A logging.Handler instance for custom handling

For more information, see: https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.add

Source code in src/intermediate/logging/custom_logging.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def custom_loguru_format_and_level(format: str, level: str):
    """Example of custom loguru configuration with format and level.

    Shows how to add a custom sink to loguru with specific formatting
    and log level. Loguru uses the add() method to configure where logs
    go (sink) and how they are formatted. Sinks can be stdout, files,
    or custom handlers.

    Args:
        format: Format string for log messages. Can include fields like
            {level}, {message}, {time}, {name}, etc. Uses Python's
            string formatting syntax.
        level: Minimum log level as a string (e.g., 'DEBUG', 'INFO',
            'WARNING', 'ERROR', 'CRITICAL').

    Note:
        The sink parameter in logger.add() can be:
        - sys.stdout or sys.stderr for console output
        - A file path string for file logging
        - A logging.Handler instance for custom handling

        For more information, see:
        https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.add
    """
    logger_loguru.add(sys.stdout, format=format, level=level)

    logger_loguru.debug("DEBUG loguru formatted")
    logger_loguru.info("INFO loguru formatted")
    logger_loguru.warning("WARNING loguru formatted")
    logger_loguru.error("ERROR loguru formatted")
    logger_loguru.critical("CRITICAL loguru formatted")

References