colorlog adds ANSI color to Python’s standard logging output. pip install colorlog. Quick: import colorlog; colorlog.basicConfig(level="DEBUG"); logger = colorlog.getLogger(__name__); logger.info("Hello"). Full: handler = colorlog.StreamHandler(); handler.setFormatter(colorlog.ColoredFormatter("%(log_color)s%(levelname)s%(reset)s:%(name)s:%(message)s")); logger.addHandler(handler). Colors: log_colors={"DEBUG":"cyan","INFO":"green","WARNING":"yellow","ERROR":"red","CRITICAL":"red,bg_white"}. Custom level: colorlog.addLevelName(25, "SUCCESS"); logger.log(25, "Done"). Secondary colors: secondary_log_colors={"message":{"ERROR":"red","CRITICAL":"red"}}. %(log_color)s, %(reset)s, %(message_log_color)s format tokens. Style escape: %(bold_cyan)s, %(bg_white)s, %(white)s. Color names: black white red green yellow blue purple cyan bold_* bg_*. Reset: reset=True appends ANSI reset at line end — default True. File + console: add logging.FileHandler (without ColoredFormatter) alongside StreamHandler. logging.config.dictConfig with () key for custom formatter class. Logger factory: colorlog.getLogger(name). TTY check: colorlog.escape_codes.escape_codes — strip if not TTY. colorlog.TTYColoredFormatter — auto-disables in non-TTY. Claude Code generates colorlog setups for development servers, CLI tools, and debug logging pipelines.
CLAUDE.md for colorlog
## colorlog Stack
- Version: colorlog >= 6.8 | pip install colorlog
- Quick: colorlog.basicConfig(level="DEBUG", format="%(log_color)s%(levelname)s:%(name)s:%(message)s")
- Handler: ColoredFormatter("%(log_color)s%(levelname)-8s%(reset)s %(message)s")
- Colors: log_colors={"DEBUG":"cyan","INFO":"green","WARNING":"yellow","ERROR":"red","CRITICAL":"bold_red"}
- TTY-safe: TTYColoredFormatter — falls back to plain text in non-TTY
- File+color: ColoredFormatter on StreamHandler; plain Formatter on FileHandler
colorlog Colored Logging Pipeline
# app/logging_config.py — colorlog setup, dict config, and dual-output logging
from __future__ import annotations
import logging
import logging.config
import sys
from pathlib import Path
from typing import Any
import colorlog
# ─────────────────────────────────────────────────────────────────────────────
# 1. Colour map
# ─────────────────────────────────────────────────────────────────────────────
DEFAULT_LOG_COLORS: dict[str, str] = {
"DEBUG": "cyan",
"INFO": "green",
"WARNING": "yellow",
"ERROR": "red",
"CRITICAL": "bold_red,bg_white",
}
DEFAULT_SECONDARY_COLORS: dict[str, dict[str, str]] = {
"message": {
"WARNING": "yellow",
"ERROR": "red",
"CRITICAL": "red",
},
}
# ─────────────────────────────────────────────────────────────────────────────
# 2. Formatter factories
# ─────────────────────────────────────────────────────────────────────────────
def make_color_formatter(
fmt: str = "%(log_color)s%(levelname)-8s%(reset)s %(name)s: %(message)s",
datefmt: str | None = "%H:%M:%S",
log_colors: dict[str, str] | None = None,
secondary_log_colors: dict[str, dict[str, str]] | None = None,
reset: bool = True,
style: str = "%",
) -> colorlog.ColoredFormatter:
"""
Create a colorlog formatter with sane defaults.
Use %(asctime)s in fmt to include timestamp.
"""
return colorlog.ColoredFormatter(
fmt=fmt,
datefmt=datefmt,
reset=reset,
log_colors=log_colors or DEFAULT_LOG_COLORS,
secondary_log_colors=secondary_log_colors or DEFAULT_SECONDARY_COLORS,
style=style,
)
def make_plain_formatter(
fmt: str = "%(asctime)s %(levelname)-8s %(name)s: %(message)s",
datefmt: str = "%Y-%m-%d %H:%M:%S",
) -> logging.Formatter:
"""Plain (no-color) formatter — for file output or non-TTY."""
return logging.Formatter(fmt=fmt, datefmt=datefmt)
def make_tty_formatter(
fmt: str = "%(log_color)s%(levelname)-8s%(reset)s %(name)s: %(message)s",
log_colors: dict[str, str] | None = None,
) -> colorlog.TTYColoredFormatter:
"""
TTY-aware formatter — colors in terminals, plain text in pipes/files.
Automatically detects sys.stderr.isatty().
"""
return colorlog.TTYColoredFormatter(
fmt=fmt,
log_colors=log_colors or DEFAULT_LOG_COLORS,
reset=True,
stream=sys.stderr,
)
# ─────────────────────────────────────────────────────────────────────────────
# 3. Handler setup
# ─────────────────────────────────────────────────────────────────────────────
def make_console_handler(
level: int = logging.DEBUG,
formatter: logging.Formatter | None = None,
stream=sys.stderr,
) -> logging.StreamHandler:
"""Colored StreamHandler for stderr (or stdout)."""
handler = colorlog.StreamHandler(stream)
handler.setLevel(level)
handler.setFormatter(formatter or make_color_formatter())
return handler
def make_file_handler(
path: str | Path,
level: int = logging.DEBUG,
formatter: logging.Formatter | None = None,
mode: str = "a",
encoding: str = "utf-8",
) -> logging.FileHandler:
"""Plain-text FileHandler (no ANSI codes in log files)."""
handler = logging.FileHandler(str(path), mode=mode, encoding=encoding)
handler.setLevel(level)
handler.setFormatter(formatter or make_plain_formatter())
return handler
# ─────────────────────────────────────────────────────────────────────────────
# 4. Logger factory
# ─────────────────────────────────────────────────────────────────────────────
def get_logger(
name: str,
level: int | str = logging.DEBUG,
log_file: str | Path | None = None,
propagate: bool = False,
) -> logging.Logger:
"""
Create (or get) a named logger with a colored console handler
and optional plain-text file handler.
Usage:
logger = get_logger(__name__)
logger.info("Starting up")
"""
logger = logging.getLogger(name)
if isinstance(level, str):
level = getattr(logging, level.upper(), logging.DEBUG)
logger.setLevel(level)
logger.propagate = propagate
if not logger.handlers:
logger.addHandler(make_console_handler(level))
if log_file:
logger.addHandler(make_file_handler(log_file, level))
return logger
def setup_root_logger(
level: int | str = logging.INFO,
log_file: str | Path | None = None,
include_thread: bool = False,
) -> None:
"""
Configure the root logger with a colored handler.
Call once at application startup.
"""
if isinstance(level, str):
level = getattr(logging, level.upper(), logging.INFO)
fmt = (
"%(log_color)s%(levelname)-8s%(reset)s "
+ ("%(threadName)-10s " if include_thread else "")
+ "%(name)s: %(message)s"
)
root = logging.getLogger()
root.setLevel(level)
if not root.handlers:
root.addHandler(make_console_handler(level, make_color_formatter(fmt=fmt)))
if log_file:
root.addHandler(make_file_handler(log_file, level))
# ─────────────────────────────────────────────────────────────────────────────
# 5. dictConfig integration
# ─────────────────────────────────────────────────────────────────────────────
def make_dict_config(
app_name: str = "app",
level: str = "DEBUG",
log_file: str | None = None,
) -> dict[str, Any]:
"""
Build a logging.config.dictConfig dict with colorlog.
Pass to logging.config.dictConfig(make_dict_config(...)).
"""
handlers = {
"console": {
"()": "colorlog.StreamHandler",
"formatter": "colored",
"stream": "ext://sys.stderr",
"level": level,
},
}
if log_file:
handlers["file"] = {
"class": "logging.FileHandler",
"formatter": "plain",
"filename": log_file,
"mode": "a",
"encoding": "utf-8",
"level": level,
}
config = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"colored": {
"()": "colorlog.ColoredFormatter",
"format": "%(log_color)s%(levelname)-8s%(reset)s %(name)s: %(message)s",
"datefmt": "%H:%M:%S",
"reset": True,
"log_colors": DEFAULT_LOG_COLORS,
"secondary_log_colors": DEFAULT_SECONDARY_COLORS,
},
"plain": {
"format": "%(asctime)s %(levelname)-8s %(name)s: %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
},
},
"handlers": handlers,
"loggers": {
app_name: {
"handlers": list(handlers.keys()),
"level": level,
"propagate": False,
},
},
"root": {
"handlers": ["console"],
"level": "WARNING",
},
}
return config
def configure_from_dict(app_name: str = "app", level: str = "DEBUG", log_file: str | None = None) -> None:
"""Apply the dictConfig immediately."""
logging.config.dictConfig(make_dict_config(app_name, level, log_file))
# ─────────────────────────────────────────────────────────────────────────────
# 6. Per-level format (LevelFormatter)
# ─────────────────────────────────────────────────────────────────────────────
def make_level_formatter() -> colorlog.LevelFormatter:
"""
Different format strings per log level.
DEBUG: show function and line; ERROR: show exc_info flags.
"""
return colorlog.LevelFormatter(
fmt={
"DEBUG": "%(log_color)s%(levelname)-8s%(reset)s %(name)s:%(funcName)s:%(lineno)d — %(message)s",
"INFO": "%(log_color)s%(levelname)-8s%(reset)s %(name)s — %(message)s",
"WARNING": "%(log_color)s%(levelname)-8s%(reset)s %(name)s — %(message)s",
"ERROR": "%(log_color)s%(levelname)-8s%(reset)s %(name)s:%(lineno)d — %(message)s",
"CRITICAL": "%(log_color)s%(levelname)-8s%(reset)s %(name)s:%(lineno)d — %(message)s",
},
log_colors=DEFAULT_LOG_COLORS,
)
# ─────────────────────────────────────────────────────────────────────────────
# 7. FastAPI / Flask / Django snippets
# ─────────────────────────────────────────────────────────────────────────────
def setup_fastapi_logging(level: str = "DEBUG") -> None:
"""
Configure colorized logging for a FastAPI app.
Call before creating the FastAPI() instance.
"""
setup_root_logger(level=level)
# Quieten noisy uvicorn access log but keep error log colored
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
logging.getLogger("uvicorn.error").setLevel(logging.INFO)
def setup_django_logging() -> dict[str, Any]:
"""
Return a LOGGING dict for Django settings.py.
Assign: LOGGING = setup_django_logging()
"""
return {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"colored": {
"()": "colorlog.ColoredFormatter",
"format": "%(log_color)s%(levelname)-8s%(reset)s %(name)s: %(message)s",
"log_colors": DEFAULT_LOG_COLORS,
},
},
"handlers": {
"console": {
"class": "colorlog.StreamHandler",
"formatter": "colored",
},
},
"root": {
"handlers": ["console"],
"level": "DEBUG",
},
}
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== Root logger setup ===")
setup_root_logger(level=logging.DEBUG)
root_log = logging.getLogger("demo")
root_log.debug("Debug message — cyan")
root_log.info("Info message — green")
root_log.warning("Warning message — yellow")
root_log.error("Error message — red")
try:
raise ValueError("something broke")
except Exception:
root_log.exception("Exception with traceback — red")
print("\n=== get_logger helper ===")
log = get_logger("myapp.service", level=logging.DEBUG)
log.debug("Service starting up")
log.info("Connected to database")
log.warning("Deprecated endpoint called")
log.error("Failed to process request id=42")
print("\n=== TTY formatter (auto-strips in pipes) ===")
tty_logger = logging.getLogger("tty_demo")
tty_logger.setLevel(logging.DEBUG)
tty_handler = colorlog.StreamHandler()
tty_handler.setFormatter(make_tty_formatter())
tty_logger.addHandler(tty_handler)
tty_logger.info("This uses TTYColoredFormatter")
tty_logger.warning("Only colored in a real terminal")
print("\n=== Per-level formatter ===")
lf_logger = logging.getLogger("levelformat")
lf_logger.setLevel(logging.DEBUG)
lf_handler = colorlog.StreamHandler()
lf_handler.setFormatter(make_level_formatter())
lf_logger.addHandler(lf_handler)
lf_logger.debug("debug shows function+line")
lf_logger.info("info is compact")
lf_logger.error("error shows line number")
print("\n=== dictConfig ===")
configure_from_dict("myapp", level="DEBUG")
cfg_log = logging.getLogger("myapp")
cfg_log.info("Configured via dictConfig")
cfg_log.critical("Critical alert!")
For the loguru alternative — loguru provides a modern drop-in replacement for the stdlib logging module with colored output built-in, structured records, and zero-config setup (from loguru import logger; logger.info("hi")); colorlog is for teams that must stay on stdlib logging (because of third-party libraries that call logging.getLogger) and want to add color without replacing the logging infrastructure — use colorlog to color an existing stdlib-logging setup, use loguru for new projects where you control all logging calls. For the rich.logging alternative — rich.logging.RichHandler renders log lines with full rich markup, clickable tracebacks, and colored level badges that look better than raw ANSI; colorlog integrates with the plain stdlib logging.config.dictConfig / fileConfig ecosystem without pulling rich in — the right choice when you need a standards-compatible logging setup. The Claude Skills 360 bundle includes colorlog skill sets covering ColoredFormatter with log_colors dict, secondary_log_colors for message coloring, TTYColoredFormatter auto-TTY detection, LevelFormatter per-level format strings, make_color_formatter()/make_plain_formatter() factories, get_logger() named logger factory, setup_root_logger() application startup, make_dict_config() for logging.config.dictConfig, file handler combo (color stdout + plain file), setup_fastapi_logging(), and setup_django_logging() LOGGING dict. Start with the free tier to try colored logging code generation.