# How Rich's Logging Integration Works with Python's Logging Module

> Learn how Rich integrates with Python logging. Use RichHandler for color-coded console logs and syntax-highlighted tracebacks.

- Repository: [Textualize/rich](https://github.com/Textualize/rich)
- Tags: deep-dive
- Published: 2026-03-06

---

**Rich ships a drop-in `RichHandler` class that intercepts standard Python `logging` records and renders them as color-coded console tables with optional syntax-highlighted tracebacks.**

Rich's logging integration bridges Python's standard `logging` module with Rich's advanced console formatting capabilities. Implemented in the `Textualize/rich` repository, this integration centers on the `RichHandler` class located in [`rich/logging.py`](https://github.com/Textualize/rich/blob/main/rich/logging.py), enabling developers to transform plain text log output into beautifully styled, structured tables without modifying existing logging instrumentation.

## Core Architecture

The integration relies on four primary components working in concert to process and display log records.

| Component | Role | Source File |
| --- | --- | --- |
| **`RichHandler`** | Subclass of `logging.Handler` that receives `LogRecord` objects, manages formatting, and delegates rendering | [`rich/logging.py`](https://github.com/Textualize/rich/blob/main/rich/logging.py) |
| **`LogRender`** | Callable responsible for assembling the final table layout combining time, level, message, and path metadata | [`rich/_log_render.py`](https://github.com/Textualize/rich/blob/main/rich/_log_render.py) |
| **`Console`** | The Rich console instance that performs the actual output to stdout or file-like objects; obtained via `get_console()` if not explicitly injected | [`rich/console.py`](https://github.com/Textualize/rich/blob/main/rich/console.py) |
| **Styles** | Pre-defined styles for log levels (e.g., `logging.level.info` → blue) and keywords stored in the default theme | [`rich/default_styles.py`](https://github.com/Textualize/rich/blob/main/rich/default_styles.py) |

### The Log Record Processing Pipeline

When a log record is emitted, `RichHandler.emit()` in [`rich/logging.py`](https://github.com/Textualize/rich/blob/main/rich/logging.py) executes a six-step pipeline:

1. **Record Receipt**: The standard `logging` library dispatches a record to `RichHandler.emit()`.
2. **Message Formatting**: `self.format(record)` applies any attached `logging.Formatter` to generate the base message string.
3. **Traceback Handling**: If `rich_tracebacks=True` and exception info exists, the handler constructs a `rich.traceback.Traceback` object.
4. **Rich Text Generation**: `render_message()` converts the plain string into a Rich `Text` object, applying markup (if enabled), the configured `Highlighter` (default `ReprHighlighter`), and keyword highlighting.
5. **Table Assembly**: `LogRender` creates a `Table.grid` with columns for time, level, message, and path, applying styles like `log.time`, `log.level`, and `log.path`.
6. **Console Output**: The handler prints the table via `self.console.print()`. If the console writes to a `NullFile` (e.g., `pythonw` on Windows), `handleError()` is invoked to prevent silent failures.

## Configuration Options

`RichHandler` exposes granular controls for customizing output appearance and behavior.

| Argument | Description | Default |
| --- | --- | --- |
| `level` | Minimum logging level to process | `logging.NOTSET` |
| `console` | Custom `Console` instance for theming or redirection | Global console |
| `show_time` / `show_level` / `show_path` | Toggle visibility of respective columns | `True` |
| `omit_repeated_times` | Collapse duplicate timestamps to blank fields | `True` |
| `highlighter` | `Highlighter` subclass for syntax highlighting | `ReprHighlighter` |
| `markup` | Enable Rich markup parsing in log messages | `False` |
| `rich_tracebacks` | Render exceptions with Rich's syntax-highlighted traceback | `False` |
| `tracebacks_extra_lines` / `tracebacks_theme` / `tracebacks_word_wrap` | Fine-grained traceback display controls | Various |
| `keywords` | List of words to highlight (e.g., HTTP verbs) | `["GET", "POST", ...]` |
| `enable_link_path` | Render file paths as clickable terminal links | `True` |

## Integration with the Standard Logging API

Rich's handler maintains full compatibility with Python's logging infrastructure while providing extension points for per-record customization.

- **Handler Registration**: Install via `logging.basicConfig(handlers=[RichHandler()])` or `logger.addHandler(RichHandler())`.
- **Formatter Support**: Any standard `logging.Formatter` works; the handler respects `format()` and `datefmt` settings.
- **Per-Record Overrides**: Pass `extra` dictionaries to modify behavior for single log calls, such as `extra={"markup": True}` to enable markup parsing or `extra={"highlighter": None}` to disable highlighting for specific records.

## Practical Implementation Examples

### Basic Setup

The minimal configuration requires only importing `RichHandler` and adding it to your logging handlers.

```python
import logging
from rich.logging import RichHandler

logging.basicConfig(
    level="INFO",
    format="%(message)s",
    handlers=[RichHandler()]
)

log = logging.getLogger("rich")
log.info("Server started on http://localhost:8000")
log.warning("[bold red]CPU load high!")  # Markup ignored by default

```

This renders a three-column table (time, level, message) using the color schemes defined in [`rich/default_styles.py`](https://github.com/Textualize/rich/blob/main/rich/default_styles.py).

### Enabling Markup and Custom Console

To parse Rich markup in log messages and control output dimensions.

```python
from rich.console import Console
from rich.logging import RichHandler
import logging

console = Console(force_terminal=True, width=100, color_system="truecolor")
handler = RichHandler(console=console, markup=True)
logging.basicConfig(level="DEBUG", handlers=[handler])

log = logging.getLogger("rich")
log.error("[bold red]ERROR:[/bold red] Disk full")

```

The word "ERROR" renders in bold red, and the custom console enforces specific width and color system constraints.

### Rendering Rich Tracebacks

Enable `rich_tracebacks` for syntax-highlighted exception displays.

```python
import logging
from rich.logging import RichHandler

handler = RichHandler(rich_tracebacks=True, tracebacks_extra_lines=2)
logging.basicConfig(level="ERROR", handlers=[handler])

log = logging.getLogger("rich")
try:
    1 / 0
except ZeroDivisionError:
    log.exception("Division failed")

```

The resulting output displays a colorized traceback beneath the log line, with two extra lines of context per frame.

### Per-Record Customization

Override handler defaults for individual log records using the `extra` parameter.

```python
log.error("User input: [red]dangerous[/red]")  # Markup ignored (default)

log.error("User input: [red]dangerous[/red]", extra={"markup": True})  # Markup applied

log.error("12345", extra={"highlighter": None})  # Disables ReprHighlighter

```

### Custom Keyword Highlighting

Highlight domain-specific terms by configuring the `keywords` list.

```python
handler = RichHandler(keywords=["CONNECT", "DISCONNECT"])
log = logging.getLogger("rich")
log.addHandler(handler)

log.info("CONNECT to server")      # "CONNECT" highlighted with logging.keyword style

log.info("DISCONNECT from server")

```

## Summary

- **`RichHandler`** in [`rich/logging.py`](https://github.com/Textualize/rich/blob/main/rich/logging.py) serves as the primary entry point, subclassing `logging.Handler` to intercept records.
- The handler delegates table construction to `LogRender` in [`rich/_log_render.py`](https://github.com/Textualize/rich/blob/main/rich/_log_render.py), which assembles time, level, message, and path columns.
- Configuration options include `rich_tracebacks`, `markup`, `highlighter`, and granular display toggles (`show_time`, `show_level`, `show_path`).
- Per-record customization is achieved via the `extra` dictionary, allowing temporary overrides of markup and highlighting behavior.
- Full compatibility with standard `logging` formatters and handler registration methods ensures seamless integration with existing codebases.

## Frequently Asked Questions

### Can I use RichHandler alongside other logging handlers?

Yes. `RichHandler` operates independently of other handlers. You can add it to a logger that already outputs to files or external services via `logger.addHandler(RichHandler())`, and it will only affect console output while other handlers process records according to their own configurations.

### Does RichHandler support logging to files?

While `RichHandler` itself writes to a Rich `Console` instance (typically stdout), you can redirect output by passing a file-like object to the console: `Console(file=open("output.log", "w"))`. However, for persistent file storage, standard `logging.FileHandler` remains the recommended approach, as Rich's formatted tables are optimized for terminal display rather than log file parsing.

### How do I disable the timestamp column entirely?

Set `show_time=False` when instantiating the handler: `RichHandler(show_time=False)`. This removes the time column from the rendered table. You can similarly disable the path column with `show_path=False` or the level column with `show_level=False` to create minimal output layouts.

### Why are my log messages not showing markup formatting?

By default, `markup=False` for security reasons. You must either enable it globally via `RichHandler(markup=True)` or enable it per-record using `log.info("Message", extra={"markup": True})`. Without these settings, Rich markup tags like `[bold red]` will appear as literal text in the output.