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.
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.
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
- You want pretty tracebacks, tables, progress bars, or syntax-highlighted output: rich's RichHandler does all of that and colorlog only colours the text you already format
- You are moving to structured logging: colouring a human-readable line is the opposite direction from emitting JSON events, and structlog or the standard library's JSON formatting fits better (the README itself points at structlog)
- You expect new features: the author has said twice in the README that this is maintenance mode, that supporting a wide range of Python versions makes the codebase hard to change, and that feature requests may not be accepted
- You log to a file, a systemd journal, or a log aggregator: escape codes are noise there, and colorlog does not strip them unless you configure it to (see the setup notes on stream detection)
- You want a batteries-included logging replacement with rotation, sinks, and structured context out of the box: loguru is that, colorlog is a formatter
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 viewerBoth 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
| Package | Registry | Pick it when |
|---|---|---|
| rich | PyPI | You want a console library with a logging handler, plus tables, tracebacks, and progress bars in the same toolkit |
| structlog | PyPI | You are moving to key-value or JSON events and want colour only as a development-time renderer |
| coloredlogs | PyPI | You want a one-call install() that colourises the root logger and per-field styling configured by environment variables |
| loguru | PyPI | You are willing to leave the standard logging module behind for a single-object API with colour, rotation, and sinks built in |