Skip to main content
https://img.shields.io/github/actions/workflow/status/adamchainz/flake8-logging/main.yml?branch=main&style=for-the-badge https://img.shields.io/badge/Coverage-100%25-success?style=for-the-badge https://img.shields.io/pypi/v/flake8-logging.svg?style=for-the-badge https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge pre-commit

A Flake8 plugin that checks for issues using the standard library logging module.

For a brief overview and background, see the introductory blog post.

Requirements

Python 3.8 to 3.12 supported.

Installation

First, install with pip:

python -m pip install flake8-logging

Second, if you define Flake8’s select setting, add the L prefix to it. Otherwise, the plugin should be active by default.


Linting a Django project? Check out my book Boost Your Django DX which covers Flake8 and many other code quality tools.


Rules

LOG001 use logging.getLogger() to instantiate loggers

The Logger Objects documentation section starts:

Note that Loggers should NEVER be instantiated directly, but always through the module-level function logging.getLogger(name).

Directly instantiated loggers are not added into the logger tree. This means that they bypass all configuration and their messages are only sent to the last resort handler. This can mean their messages are incorrectly filtered, formatted, and sent only to stderr. Potentially, such messages will not be visible in your logging tooling and you won’t be alerted to issues.

Use getLogger() to correctly instantiate loggers.

This rule detects any module-level calls to Logger().

Failing example:

import logging

logger = logging.Logger(__name__)

Corrected:

import logging

logger = logging.getLogger(__name__)

LOG002 use __name__ with getLogger()

The logging documentation recommends this pattern:

logging.getLogger(__name__)

__name__ is the fully qualified module name, such as camelot.spam, which is the intended format for logger names.

This rule detects probably-mistaken usage of similar module-level dunder constants:

  • __cached__ - the pathname of the module’s compiled versio˜, such as camelot/__pycache__/spam.cpython-311.pyc.

  • __file__ - the pathname of the module, such as camelot/spam.py.

Failing example:

import logging

logger = logging.getLogger(__file__)

Corrected:

import logging

logger = logging.getLogger(__name__)

LOG003 extra key '<key>' clashes with LogRecord attribute

The extra documentation states:

The keys in the dictionary passed in extra should not clash with the keys used by the logging system.

Such clashes crash at runtime with an error like:

KeyError: "Attempt to overwrite 'msg' in LogRecord"

Unfortunately, this error is only raised if the message is not filtered out by level. Tests may therefore not encounter the check, if they run with a limited logging configuration.

This rule detects such clashes by checking for keys matching the LogRecord attributes.

Failing example:

import logging

logger = logging.getLogger(__name__)

response = acme_api()
logger.info("ACME Response", extra={"msg": response.msg})

Corrected:

import logging

logger = logging.getLogger(__name__)

response = acme_api()
logger.info("ACME Response", extra={"response_msg": response.msg})

LOG004 avoid exception() outside of exception handlers

The exception() documentation states:

This function should only be called from an exception handler.

Calling exception() outside of an exception handler attaches None exception information, leading to confusing messages:

>>> logging.exception("example")
ERROR:root:example
NoneType: None

Use error() instead. To log a caught exception, pass it in the exc_info argument.

This rule detects exception() calls outside of exception handlers.

Failing example:

import logging

response = acme_api()
if response is None:
    logging.exception("ACME failed")

Corrected:

import logging

response = acme_api()
if response is None:
    logging.error("ACME failed")

LOG005 use exception() within an exception handler

Within an exception handler, the exception() method is preferable over logger.error(). The exception() method captures the exception automatically, whilst error() needs it to be passed explicitly in the exc_info argument. Both methods log with the level ERROR.

This rule detects error() calls within exception handlers, excluding those with a falsy exc_info argument.

Failing example:

try:
    acme_api()
except AcmeError as exc:
    logger.error("ACME API failed", exc_info=exc)

Corrected:

try:
    acme_api()
except AcmeError:
    logger.exception("ACME API failed")

Or alternatively, if the exception information is truly uninformative:

try:
    acme_api()
except DuplicateError:
    logger.error("ACME Duplicate Error", exc_info=False)

LOG006 redundant exc_info argument for exception()

The exception() method captures the exception automatically, making a truthy exc_info argument redundant.

This rule detects exception() calls within exception handlers with an exc_info argument that is truthy or the captured exception object.

Failing example:

try:
    acme_api()
except AcmeError:
    logger.exception("ACME API failed", exc_info=True)

Corrected:

try:
    acme_api()
except AcmeError:
    logger.exception("ACME API failed")

LOG007 use error() instead of exception() with exc_info=False

The exception() method captures the exception automatically. Disabling this by setting exc_info=False is the same as using error(), which is clearer and doesn’t need the exc_info argument.

This rule detects exception() calls with an exc_info argument that is falsy.

Failing example:

logger.exception("Left phalange missing", exc_info=False)

Corrected:

logger.error("Left phalange missing")

LOG008 warn() is deprecated, use warning() instead

The warn() method is a deprecated, undocumented alias for warning() warning() should always be used instead. The method was deprecated in Python 2.7, in commit 04d5bc00a2, and removed in Python 3.13, in commit dcc028d924.

This rule detects calls to warn().

Failing example:

logger.warn("Cheesy puns incoming")

Corrected:

logger.warning("Cheesy puns incoming")

LOG009 WARN is undocumented, use WARNING instead

The WARN constant is an undocumented alias for WARNING. Whilst it’s not deprecated, it’s not mentioned at all in the documentation, so the documented WARNING should always be used instead.

This rule detects any import or access of WARN.

Failing example:

import logging

logging.WARN

Corrected:

import logging

logging.WARNING

LOG010 exception() does not take an exception

Like other logger methods, the exception() method takes a string as its first argument. A common misunderstanding is to pass it an exception instead. Doing so is redundant, as exception() will already capture the exception object. It can also lead to unclear log messages, as the logger will call str() on the exception, which doesn’t always produce a sensible message.

This rule detects exception() calls with a first argument that is the current exception handler’s capture variable.

Failing example:

try:
    shuffle_deck()
except Exception as exc:
    logger.exception(exc)

Corrected:

try:
    shuffle_deck()
except Exception:
    logger.exception("Failed to shuffle deck")

LOG011 avoid pre-formatting log messages

Logger methods support string formatting for logging variable data, such as:

logger.info("Couldn’t chop %s", vegetable)

Log-aggregating tools, such as Sentry can group messages based on their unformatted message templates. Using a pre-formatted message, such as from an f-string, prevents this from happening. Tools have to rely on imperfect heuristics, which can lead to duplicate groups.

Additionally, the logging framework skips formatting messages that won’t be logged. Using a pre-formatted string, such as from an f-string, has no such optimization. This overhead can add up when you have a high volume of logs that are normally skipped.

This rule detects logger method calls with a msg argument that is one of:

  • an f-string

  • a call to str.format()

  • a string used with the modulus operator (%)

  • a concatenation of strings with non-strings

Failing examples:

logging.error(f"Couldn’t chop {vegetable}")
logging.error("Couldn’t chop {}".format(vegetable))
logging.error("Couldn’t chop %s" % (vegetable,))
logging.error("Couldn’t chop " + vegetable)

Corrected:

logging.error("Couldn’t chop %s", vegetable)

LOG012 formatting error: <n> <style> placeholders but <m> arguments

Logger methods support several string formatting options for messages. If there’s a mismatch between the number of parameters in the message and those provided, the call will error:

>>> logging.info("Sent %s to %s", letter)
--- Logging error ---
Traceback (most recent call last):
  File "/.../logging/__init__.py", line 1110, in emit
    msg = self.format(record)
          ^^^^^^^^^^^^^^^^^^^
...

  File "/.../logging/__init__.py", line 377, in getMessage
    msg = msg % self.args
          ~~~~^~~~~~~~~~~
TypeError: not enough arguments for format string
Call stack:
  File "<stdin>", line 1, in <module>
Message: ' %s to %s'
Arguments: ('Red Letter',)

This will only happen when the logger is enabled since loggers don’t perform string formatting when disabled. Thus a configuration change can reveal such errors.

Additionally, if no arguments are provided, parametrized messages are silently unformatted:

>>> logging.info("Sent %s to %s")
INFO:root:Sent %s to %s

This rule detects mismatches between the number of message parameters and those provided. At the moment, it only supports %-style formatting with at least one parameter.

Failing examples:

logging.info("Blending %s")
logging.info("Blending %s", fruit.name, fruit.size)

Corrected:

logging.info("Blending %s of size %r", fruit.name, fruit.size)

LOG013 formatting error: <missing/unreferenced> keys: <keys>

When using named %-style formatting, if the message references a missing key, the call will error:

>>> logging.error("Hi %(name)s", {"nam": "hacker"})
--- Logging error ---
Traceback (most recent call last):
  File "/.../logging/__init__.py", line 1160, in emit
    msg = self.format(record)
          ^^^^^^^^^^^^^^^^^^^
...

  File "/.../logging/__init__.py", line 392, in getMessage
    msg = msg % self.args
          ~~~~^~~~~~~~~~~
KeyError: 'name'
Call stack:
  File "<stdin>", line 1, in <module>
Message: 'Hi %(name)s'
Arguments: {'nam': 'hacker'}

This will only happen when the logger is enabled since loggers don’t perform string formatting when disabled. Thus a configuration change can reveal such errors.

This rule detects mismatches between the message parameter names and those provided. It only works if there’s a dict literal argument for the parameters.

Failing example:

logging.info("Blending %(fruit)s", {"froot": froot})
logging.info("Blending %(fruit)s", {"fruit": fruit, "colour": "yellow"})

Corrected:

logging.info("Blending %(fruit)s", {"fruit": fruit})

LOG014 avoid exc_info=True outside of exception handlers

Using exc_info=True outside of an exception handler attaches None as the exception information, leading to confusing messages:

>>> logging.warning("Uh oh", exc_info=True)
WARNING:root:Uh oh
NoneType: None

This rule detects logging calls with exc_info=True outside of exception handlers.

Failing example:

import logging

logging.warning("Uh oh", exc_info=True)

Corrected:

import logging

logging.warning("Uh oh")

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

flake8-logging-1.5.0.tar.gz (16.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

flake8_logging-1.5.0-py3-none-any.whl (11.0 kB view details)

Uploaded Python 3

File details

Details for the file flake8-logging-1.5.0.tar.gz.

File metadata

  • Download URL: flake8-logging-1.5.0.tar.gz
  • Upload date:
  • Size: 16.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.7

File hashes

Hashes for flake8-logging-1.5.0.tar.gz
Algorithm Hash digest
SHA256 6be28892c0464959a509e0cf37d3f2a9f1b092d82a98b42594c95d7ffd980b94
MD5 a3c6b43088214b5fdf17cfde6b9f4748
BLAKE2b-256 87da66bf5d5bd023831953174017ed60a8df45f487b6b5df9a0e17cb8f42bdb7

See more details on using hashes here.

File details

Details for the file flake8_logging-1.5.0-py3-none-any.whl.

File metadata

  • Download URL: flake8_logging-1.5.0-py3-none-any.whl
  • Upload date:
  • Size: 11.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.7

File hashes

Hashes for flake8_logging-1.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0e2b2b12dfc298d4b66ce1cf9d91d3ce2fd5052d8906b6afc46173fd77d40c35
MD5 4c953463a400b2bd88b1fcd9f90c3a10
BLAKE2b-256 5526a2afbae754821e35e24f8c817039b1d0491a2268f3399f60c99be0a43fd3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page