Python
logging
color output
programming
terminal customization

How can I color Python logging output?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

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:

python
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.

Custom Colored Formatter

python
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:

python
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

bash
pip install colorlog
python
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

bash
pip install rich
python
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:

python
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:

python
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:

python
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

Course illustration
Course illustration

All Rights Reserved.