Introduction
Python's logging module outputs plain text by default. To add color, you create a custom Formatter that wraps log messages with ANSI escape codes based on the log level. ANSI codes like \033[31m (red) and \033[0m (reset) are interpreted by most modern terminals. For a simpler approach, the colorlog package provides a drop-in colored formatter without writing custom code.
ANSI Escape Codes Basics
Terminals interpret special character sequences to change text color:
1# Format: \033[{code}m
2# Common codes:
3# 30 = Black, 31 = Red, 32 = Green, 33 = Yellow
4# 34 = Blue, 35 = Magenta, 36 = Cyan, 37 = White
5# 0 = Reset all formatting
6
7print("\033[31mThis is red\033[0m")
8print("\033[32mThis is green\033[0m")
9print("\033[1;33mThis is bold yellow\033[0m")
The \033[0m reset code is essential — without it, all subsequent output stays colored.
1import logging
2
3class ColoredFormatter(logging.Formatter):
4 COLORS = {
5 logging.DEBUG: "\033[36m", # Cyan
6 logging.INFO: "\033[32m", # Green
7 logging.WARNING: "\033[33m", # Yellow
8 logging.ERROR: "\033[31m", # Red
9 logging.CRITICAL: "\033[1;31m", # Bold Red
10 }
11 RESET = "\033[0m"
12
13 def format(self, record):
14 color = self.COLORS.get(record.levelno, self.RESET)
15 message = super().format(record)
16 return f"{color}{message}{self.RESET}"
17
18# Set up the logger
19logger = logging.getLogger(__name__)
20logger.setLevel(logging.DEBUG)
21
22handler = logging.StreamHandler()
23handler.setFormatter(ColoredFormatter("%(asctime)s %(levelname)-8s %(message)s"))
24logger.addHandler(handler)
25
26# Test it
27logger.debug("Debug message") # Cyan
28logger.info("Info message") # Green
29logger.warning("Warning message") # Yellow
30logger.error("Error message") # Red
31logger.critical("Critical message") # Bold Red
Coloring Only the Level Name
For a cleaner look, color just the level name instead of the entire line:
1class LevelColoredFormatter(logging.Formatter):
2 COLORS = {
3 "DEBUG": "\033[36m",
4 "INFO": "\033[32m",
5 "WARNING": "\033[33m",
6 "ERROR": "\033[31m",
7 "CRITICAL": "\033[1;31m",
8 }
9 RESET = "\033[0m"
10
11 def format(self, record):
12 color = self.COLORS.get(record.levelname, self.RESET)
13 record.levelname = f"{color}{record.levelname}{self.RESET}"
14 return super().format(record)
15
16handler = logging.StreamHandler()
17handler.setFormatter(LevelColoredFormatter(
18 "%(asctime)s %(levelname)-18s %(name)s: %(message)s"
19))
20# Note: -18s accounts for the invisible ANSI codes in the padded width
Using the colorlog Package
1import colorlog
2import logging
3
4handler = colorlog.StreamHandler()
5handler.setFormatter(colorlog.ColoredFormatter(
6 "%(log_color)s%(asctime)s %(levelname)-8s%(reset)s %(message)s",
7 log_colors={
8 "DEBUG": "cyan",
9 "INFO": "green",
10 "WARNING": "yellow",
11 "ERROR": "red",
12 "CRITICAL": "bold_red",
13 }
14))
15
16logger = logging.getLogger(__name__)
17logger.addHandler(handler)
18logger.setLevel(logging.DEBUG)
19
20logger.info("Colored logging with colorlog")
colorlog handles terminal detection, Windows compatibility, and provides named colors instead of raw ANSI codes.
Using the rich Library
1import logging
2from rich.logging import RichHandler
3
4logging.basicConfig(
5 level=logging.DEBUG,
6 format="%(message)s",
7 handlers=[RichHandler(rich_tracebacks=True)]
8)
9
10logger = logging.getLogger(__name__)
11
12logger.debug("Debug message")
13logger.info("Info message")
14logger.warning("Warning message")
15logger.error("Error message")
16logger.critical("Critical message")
rich provides syntax-highlighted tracebacks, automatic markup, and works on all platforms.
Disabling Colors for File Output
Colors should only apply to terminal output, not log files:
1import sys
2
3# Colored handler for terminal
4console_handler = logging.StreamHandler(sys.stderr)
5console_handler.setFormatter(ColoredFormatter(
6 "%(asctime)s %(levelname)-8s %(message)s"
7))
8
9# Plain handler for file
10file_handler = logging.FileHandler("app.log")
11file_handler.setFormatter(logging.Formatter(
12 "%(asctime)s %(levelname)-8s %(message)s"
13))
14
15logger = logging.getLogger()
16logger.addHandler(console_handler)
17logger.addHandler(file_handler)
18logger.setLevel(logging.DEBUG)
Or auto-detect whether output is a terminal:
1import sys
2
3class SmartColoredFormatter(logging.Formatter):
4 # ... (same COLORS dict as before)
5
6 def format(self, record):
7 message = super().format(record)
8 if hasattr(sys.stderr, 'isatty') and sys.stderr.isatty():
9 color = self.COLORS.get(record.levelno, self.RESET)
10 return f"{color}{message}{self.RESET}"
11 return message # No colors when redirected to file
Windows Support
ANSI codes work natively in Windows Terminal and PowerShell (Windows 10+). For older Windows consoles:
1import os
2import sys
3
4# Enable ANSI on Windows 10+
5if sys.platform == "win32":
6 os.system("") # Enables ANSI escape sequences in cmd.exe
7
8# Or use colorama for full Windows compatibility
9import colorama
10colorama.init() # Translates ANSI codes for older Windows consoles
Common Pitfalls
ANSI codes in log files: If your logger writes to both console and file, the file contains raw escape sequences like \033[31m that look like garbage. Use separate formatters — colored for StreamHandler, plain for FileHandler.
Broken padding with colored level names: ANSI escape codes are invisible characters that are counted in string width. "%(levelname)-8s" pads to 8 characters, but the ANSI codes add 9+ invisible characters, misaligning columns. Increase the pad width to compensate (e.g., -18s).
Colors not appearing in some terminals: CI/CD environments, Docker containers, and some IDEs do not support ANSI codes. Check sys.stderr.isatty() before adding colors, or use libraries like colorlog that handle this automatically.
Forgetting the reset code \033[0m: Without the reset code at the end, all subsequent terminal output stays colored. This can affect other programs or shell prompts.
Using colorama.init(autoreset=True) with logging: autoreset=True resets color after every print() call, but it does not work with logging because the logger writes through a different code path. Use explicit reset codes in your formatter instead.
Summary
Create a custom logging.Formatter that wraps messages with ANSI escape codes per log level
Use \033[31m for red (ERROR), \033[33m for yellow (WARNING), \033[32m for green (INFO)
Always append \033[0m to reset formatting after each message
Use colorlog or rich packages for cross-platform colored logging without custom code
Apply colors only to terminal output (StreamHandler), not file output (FileHandler)
Check sys.stderr.isatty() to auto-detect terminal vs pipe/redirect