mrkeyoor.com_
Thu 06 Aug 10:56 UTC
PyPICLI & Toolingupdated 06 Aug 2026

colorlog

colorlog adds colour to Python's standard logging module and does nothing else. Its main export is ColoredFormatter, a drop-in subclass of logging.Formatter that makes extra attributes available inside your format string: %(log_color)s picks a colour based on the record's level, %(reset)s clears it, and named codes like %(blue)s or %(bg_white)s let you colour individual fields. You keep your existing loggers, handlers, filters, and dictConfig setup; you only swap the formatter. Because it is a plain Formatter it also works from logging.config.dictConfig and fileConfig, which is how most applications wire it up. It has been around since 2012, it is packaged by Debian, Arch, Fedora, Gentoo and conda-forge, and the author states plainly in the README that the project is in maintenance mode and will not take changes that break backwards compatibility.

Verdict

The right answer when you want the standard logging module to stay exactly as it is and just be readable in a terminal. If you want anything beyond level-based colour, this project is explicitly not going to grow into it, so pick rich or structlog instead.

API stability5/5The ColoredFormatter constructor has taken the same arguments across the whole 6.x line, TTYColoredFormatter is kept as an alias to the main class for old code, and the README commits to rejecting changes that would break existing users.
Docs4/5One README that documents every constructor argument, every colour name, secondary colours, dictConfig, fileConfig, and custom levels, with a runnable example script in the repository. It does not explain that TTY detection requires passing stream= yourself, which is the single most common surprise.
Maintenance4/56.12.0 was published on 23 July 2026 with the repository pushed the same day, and the issue tracker sits at zero open issues, so bugs do get fixed. Discount it for the author's own statement that the project is in maintenance mode and will not accept new features.
Ecosystem4/5Around 15M installs a week, 967 stars, and official packages in Debian, Arch, Fedora, Gentoo, openSUSE and conda-forge, so it is available almost everywhere. There is no plugin scene, because a formatter does not need one.

Use it if

  • You already use the standard logging module with dictConfig or fileConfig and want coloured console output without changing how logging is configured anywhere else
  • You want level-based colouring specifically: WARNING yellow, ERROR red, CRITICAL bold red, with a one-line formatter swap
  • You need a dependency with essentially no weight: pure Python, no dependencies at all except colorama on Windows
  • You are shipping a CLI or a long-running service where operators read the console and you want errors to stand out, but you are not ready to rewrite your logging into structured events
  • Your deployment installs from distribution packages rather than PyPI, where colorlog is already available
Skip it if

Setup reality

pip install colorlog is instant and pulls nothing on Linux and macOS, and only colorama on Windows, where importing colorlog calls colorama.init(strip=False) as an import-time side effect. The trap everyone hits is TTY detection. ColoredFormatter only checks whether output is a terminal when you pass the stream to it explicitly, so a formatter attached to a StreamHandler with no stream= argument happily writes escape codes into a piped log file. Pass stream=sys.stderr (matching whatever the handler writes to) if you want automatic disabling, or set no_color=True. The NO_COLOR and FORCE_COLOR environment variables are honoured by presence, not by value, so NO_COLOR=0 still turns colour off, and FORCE_COLOR wins over NO_COLOR. Finally, if you set a format string yourself you have to include %(reset)s where you want the colour to stop; the automatic reset is only appended at the end of the whole line, so a colour applied to the level name bleeds into the message unless you say otherwise.

Patterns

Attach a coloured handler to a loggerquickstart-handler

import colorlog

handler = colorlog.StreamHandler()
handler.setFormatter(colorlog.ColoredFormatter())

logger = colorlog.getLogger("example")
logger.addHandler(handler)
logger.setLevel("INFO")
logger.warning("disk almost full")

colorlog.StreamHandler and colorlog.getLogger are re-exports of the standard library versions, so this is ordinary logging with one formatter swapped.

One-call setup for a scriptbasic-config

import colorlog

colorlog.basicConfig(
    level="DEBUG",
    format="%(log_color)s%(levelname)-8s%(reset)s %(message)s",
)
colorlog.getLogger(__name__).info("started")

This calls logging.basicConfig and then replaces the formatter on the handler it created. Like the standard basicConfig, it does nothing if the root logger already has handlers.

Pick your own format string and level colourscustom-format-and-colors

from colorlog import ColoredFormatter

formatter = ColoredFormatter(
    "%(log_color)s%(levelname)-8s%(reset)s %(blue)s%(name)s%(reset)s %(message)s",
    log_colors={
        "DEBUG": "cyan",
        "INFO": "green",
        "WARNING": "yellow",
        "ERROR": "red",
        "CRITICAL": "red,bg_white",
    },
)

Combine codes with a comma inside log_colors (red,bg_white), not in the format string. The defaults are white for DEBUG, green for INFO, yellow for WARNING, red for ERROR and bold_red for CRITICAL.

Wire it up through dictConfigdict-config

import logging.config

logging.config.dictConfig({
    "version": 1,
    "formatters": {
        "colored": {
            "()": "colorlog.ColoredFormatter",
            "format": "%(log_color)s%(levelname)-8s%(reset)s %(message)s",
        }
    },
    "handlers": {
        "console": {"class": "logging.StreamHandler", "formatter": "colored"}
    },
    "root": {"handlers": ["console"], "level": "INFO"},
})

The "()" key is the standard dictConfig way to name a formatter factory. Using "class" instead silently gives you a plain logging.Formatter and your colour codes render as literal text.

Turn colour off automatically when pipedtty-detection

import sys
import logging
from colorlog import ColoredFormatter

handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(
    ColoredFormatter(
        "%(log_color)s%(levelname)-8s%(reset)s %(message)s",
        stream=sys.stderr,
    )
)

The formatter checks isatty() on the stream you hand it, not on the handler's stream. Leave stream= out and escape codes go into your redirected log file.

Force colour on or off explicitlyrespect-no-color

formatter = ColoredFormatter(fmt, no_color=args.no_color, force_color=args.color)

# Equivalent from the shell:
#   NO_COLOR=1 myapp      # never colour
#   FORCE_COLOR=1 myapp   # colour even when piped, e.g. into a CI log viewer

Both environment variables are tested for presence only, so NO_COLOR= with an empty value still disables colour, and FORCE_COLOR takes precedence over NO_COLOR.

Colour the message differently from the levelsecondary-log-colors

formatter = ColoredFormatter(
    "%(log_color)s%(levelname)-8s%(reset)s %(message_log_color)s%(message)s",
    secondary_log_colors={
        "message": {"ERROR": "red", "CRITICAL": "bold_red"}
    },
)

Each key in secondary_log_colors creates a <key>_log_color attribute. Levels missing from the inner mapping get no colour rather than an error, so INFO messages stay plain here.

Use a different format string per levelper-level-format

import colorlog

formatter = colorlog.LevelFormatter(
    fmt={
        "DEBUG": "%(log_color)s%(message)s (%(module)s:%(lineno)d)",
        "INFO": "%(log_color)s%(message)s",
        "WARNING": "%(log_color)sWRN: %(message)s",
        "ERROR": "%(log_color)sERR: %(message)s (%(module)s:%(lineno)d)",
        "DEFAULT": "%(log_color)s%(levelname)s: %(message)s",
    }
)

LevelFormatter is not a Formatter subclass, it holds one ColoredFormatter per level. Records at a level you did not list fall back to the DEFAULT entry, or to the built-in default format if you omit it.

Colour a custom levelcustom-log-level

import logging, colorlog

TRACE = 5
logging.addLevelName(TRACE, "TRACE")

formatter = colorlog.ColoredFormatter(log_colors={"TRACE": "light_black"})
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger = logging.getLogger("example")
logger.addHandler(handler)
logger.setLevel(TRACE)
logger.log(TRACE, "entering loop")

Passing log_colors replaces the defaults rather than merging with them, so include DEBUG through CRITICAL in the same dict if you still want them coloured.

Use str.format style instead of percent stylebrace-style-format

formatter = colorlog.ColoredFormatter(
    "{log_color}{levelname:<8}{reset} {message}",
    style="{",
)

style must match the format string or logging's own validation raises at construction time. The $ template style works too, with ${log_color}.

Use 256-colour and background codesextended-colors

formatter = colorlog.ColoredFormatter(
    "%(log_color)s%(levelname)-8s%(reset)s %(fg_244)s%(asctime)s%(reset)s %(message)s",
    datefmt="%H:%M:%S",
    log_colors={"WARNING": "fg_214", "ERROR": "bold_red", "CRITICAL": "white,bg_red"},
)

fg_N and bg_N take 0 to 255. The light_* names use non-standard bright codes whose rendering varies a lot between terminals, so avoid them for anything a user must be able to read.

Colour the console, keep the file cleancolour-console-plain-file

import logging, sys, colorlog

console = logging.StreamHandler(sys.stderr)
console.setFormatter(colorlog.ColoredFormatter(
    "%(log_color)s%(levelname)-8s%(reset)s %(message)s", stream=sys.stderr))

file = logging.FileHandler("app.log")
file.setFormatter(logging.Formatter(
    "%(asctime)s %(levelname)-8s %(name)s %(message)s"))

logging.basicConfig(level="INFO", handlers=[console, file])

Two handlers with two formatters is the only correct way to do this. Sharing one ColoredFormatter between a console and a file handler puts escape codes in the file.

Alternatives

PackageRegistryPick it when
richPyPIYou want a console library with a logging handler, plus tables, tracebacks, and progress bars in the same toolkit
structlogPyPIYou are moving to key-value or JSON events and want colour only as a development-time renderer
coloredlogsPyPIYou want a one-call install() that colourises the root logger and per-field styling configured by environment variables
loguruPyPIYou are willing to leave the standard logging module behind for a single-object API with colour, rotation, and sinks built in