Python’s cgitb module generates rich, annotated tracebacks showing source code lines, local variable values, and full exception chains — originally for CGI scripts, but useful for any application error reporting. import cgitb. Enable globally: cgitb.enable() — installs as sys.excepthook; unhandled exceptions produce an HTML report. cgitb.enable(display=True, logdir=None, context=5, format="html") — display=False suppresses browser output; logdir writes to .txt files in that directory; context controls lines of source shown. Text format: cgitb.enable(format="text") — plain text instead of HTML. Manual use: cgitb.html(exc_info_tuple, context=5) → HTML string; cgitb.text(exc_info_tuple, context=5) → plain-text string. Exception hook: cgitb.handler(exc_info_tuple) — same as the installed excepthook. cgitb.reset() — outputs a closing </div> tag after partial HTML output. cgitb.Hook — the class installed by enable(). Note: deprecated 3.11, removed 3.13 — include compatibility guard. Claude Code generates rich exception reporters, development debug pages, structured traceback loggers, and CI failure annotators.
CLAUDE.md for cgitb
## cgitb Stack
- Stdlib: import cgitb (deprecated 3.11, removed 3.13 — guard with try/except)
- Global: cgitb.enable() # HTML to browser on exception
- cgitb.enable(format="text") # plain text
- cgitb.enable(logdir="/var/log") # write to .txt files + stdout
- Manual: html = cgitb.html(sys.exc_info(), context=5)
- text = cgitb.text(sys.exc_info(), context=5)
- Note: Great for development; do NOT use in production — exposes source code
cgitb Rich Traceback Reporter Pipeline
# app/cgitbutil.py — enable, manual text/HTML, logging, structured reporter
from __future__ import annotations
import io
import logging
import os
import sys
import traceback
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Any
logger = logging.getLogger(__name__)
# Guard for Python 3.13+ where cgitb is removed
try:
import cgitb as _cgitb
_CGITB_AVAILABLE = True
except ImportError:
_CGITB_AVAILABLE = False
# ─────────────────────────────────────────────────────────────────────────────
# 1. Enable globally
# ─────────────────────────────────────────────────────────────────────────────
def enable_html_tracebacks(
context: int = 5,
logdir: str | None = None,
display: bool = True,
) -> None:
"""
Install cgitb as sys.excepthook to generate HTML tracebacks on exception.
Only useful in HTTP-serving CGI scripts or development servers.
Example:
enable_html_tracebacks(logdir="/var/log/myapp")
"""
if not _CGITB_AVAILABLE:
logger.warning("cgitb not available; using default traceback handler")
return
_cgitb.enable(display=display, logdir=logdir,
context=context, format="html")
def enable_text_tracebacks(
context: int = 5,
logdir: str | None = None,
) -> None:
"""
Install cgitb as sys.excepthook to generate annotated plain-text tracebacks.
Example:
enable_text_tracebacks()
raise ValueError("something exploded")
"""
if not _CGITB_AVAILABLE:
logger.warning("cgitb not available; using default traceback handler")
return
_cgitb.enable(display=True, logdir=logdir, context=context, format="text")
# ─────────────────────────────────────────────────────────────────────────────
# 2. Manual traceback generation
# ─────────────────────────────────────────────────────────────────────────────
def get_text_traceback(
exc_info: tuple | None = None,
context: int = 5,
) -> str:
"""
Generate a detailed plain-text traceback string from an exc_info tuple.
If exc_info is None, uses sys.exc_info() (call from inside an except block).
Falls back to stdlib traceback.format_exc() if cgitb unavailable.
Example:
try:
risky_operation()
except Exception:
text = get_text_traceback()
logger.error("Operation failed:\n%s", text)
"""
if exc_info is None:
exc_info = sys.exc_info()
if not _CGITB_AVAILABLE:
return traceback.format_exc()
try:
return _cgitb.text(exc_info, context=context)
except Exception:
return traceback.format_exc()
def get_html_traceback(
exc_info: tuple | None = None,
context: int = 5,
) -> str:
"""
Generate a complete HTML traceback page from an exc_info tuple.
Falls back to plain-text wrapped in <pre> if cgitb unavailable.
Example:
try:
risky_operation()
except Exception:
html = get_html_traceback()
send_debug_email(html)
"""
if exc_info is None:
exc_info = sys.exc_info()
if not _CGITB_AVAILABLE:
text = traceback.format_exc()
return f"<pre>{text}</pre>"
try:
return _cgitb.html(exc_info, context=context)
except Exception:
text = traceback.format_exc()
return f"<pre>{text}</pre>"
# ─────────────────────────────────────────────────────────────────────────────
# 3. File-based traceback logger
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class TracebackLogEntry:
timestamp: str
exc_type: str
exc_message: str
log_file: Path | None
text_report: str
def __str__(self) -> str:
return (f"[{self.timestamp}] {self.exc_type}: "
f"{self.exc_message[:80]}")
class TracebackFileLogger:
"""
Writes annotated traceback reports to a log directory.
Works with or without cgitb.
Example:
tbl = TracebackFileLogger("/var/log/myapp/tracebacks")
try:
risky_operation()
except Exception:
entry = tbl.log()
print(f"Traceback written to {entry.log_file}")
"""
def __init__(
self,
logdir: str | Path,
context: int = 5,
max_files: int = 100,
) -> None:
self.logdir = Path(logdir)
self.logdir.mkdir(parents=True, exist_ok=True)
self.context = context
self.max_files = max_files
def log(self, exc_info: tuple | None = None) -> TracebackLogEntry:
"""
Log the current exception to a file. Call from inside an except block.
Example:
try:
risky()
except Exception:
entry = tbl.log()
"""
if exc_info is None:
exc_info = sys.exc_info()
exc_type, exc_value, _ = exc_info
ts = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
exc_name = exc_type.__name__ if exc_type else "UnknownError"
fname = f"{ts}_{exc_name}.txt"
log_path = self.logdir / fname
text_report = get_text_traceback(exc_info, context=self.context)
log_path.write_text(text_report, encoding="utf-8")
self._cleanup_old_files()
return TracebackLogEntry(
timestamp=ts,
exc_type=exc_name,
exc_message=str(exc_value) if exc_value else "",
log_file=log_path,
text_report=text_report,
)
def _cleanup_old_files(self) -> None:
"""Remove the oldest log files when max_files is exceeded."""
files = sorted(self.logdir.glob("*.txt"), key=lambda p: p.stat().st_mtime)
while len(files) > self.max_files:
files.pop(0).unlink(missing_ok=True)
# ─────────────────────────────────────────────────────────────────────────────
# 4. Structured exception reporter (extracts locals for inspection)
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class FrameInfo:
filename: str
lineno: int
function: str
locals_: dict[str, str] # repr() of local variables
def __str__(self) -> str:
locals_str = " ".join(f"{k}={v}" for k, v in list(self.locals_.items())[:5])
return (f" File {self.filename!r}, line {self.lineno}, in {self.function}\n"
f" locals: {locals_str}")
@dataclass
class StructuredTraceback:
exc_type: str
exc_message: str
frames: list[FrameInfo]
chained: "StructuredTraceback | None" = None
def summary(self) -> str:
return (f"{self.exc_type}: {self.exc_message}\n"
+ "\n".join(str(f) for f in self.frames[-3:]))
def structured_traceback(exc_info: tuple | None = None) -> StructuredTraceback:
"""
Extract a StructuredTraceback with local variable values from an exc_info.
Works without cgitb by using the inspect module directly.
Example:
try:
risky()
except Exception:
st = structured_traceback()
for frame in st.frames:
print(frame)
"""
import inspect
if exc_info is None:
exc_info = sys.exc_info()
exc_type, exc_value, exc_tb = exc_info
if exc_type is None:
return StructuredTraceback("", "", [])
frames: list[FrameInfo] = []
tb = exc_tb
while tb is not None:
frame = tb.tb_frame
locals_repr = {
k: repr(v)[:120]
for k, v in frame.f_locals.items()
if not k.startswith("__")
}
frames.append(FrameInfo(
filename=frame.f_code.co_filename,
lineno=tb.tb_lineno,
function=frame.f_code.co_name,
locals_=locals_repr,
))
tb = tb.tb_next
chained = None
if exc_value and exc_value.__cause__:
try:
chained = structured_traceback(
(type(exc_value.__cause__), exc_value.__cause__,
exc_value.__cause__.__traceback__)
)
except Exception:
pass
return StructuredTraceback(
exc_type=exc_type.__name__,
exc_message=str(exc_value),
frames=frames,
chained=chained,
)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import tempfile
print("=== cgitb demo ===")
if not _CGITB_AVAILABLE:
print(" cgitb not available (Python 3.13+); using fallback implementations")
# ── synthesize an exception with local variables ───────────────────────────
def level3(data: dict) -> None:
result = data["missing_key"] # KeyError
def level2(items: list) -> None:
config = {"timeout": 30, "retries": 3}
level3(config)
def level1() -> None:
items = [1, 2, 3]
level2(items)
# ── get_text_traceback ─────────────────────────────────────────────────────
print("\n--- get_text_traceback ---")
try:
level1()
except Exception:
text = get_text_traceback()
# Print first 30 lines
lines = text.splitlines()
for line in lines[:30]:
print(f" {line}")
if len(lines) > 30:
print(f" ... ({len(lines) - 30} more lines)")
# ── get_html_traceback ────────────────────────────────────────────────────
print("\n--- get_html_traceback ---")
try:
level1()
except Exception:
html = get_html_traceback()
print(f" HTML length: {len(html)} chars")
print(f" starts with: {html[:60]!r}")
# ── TracebackFileLogger ────────────────────────────────────────────────────
print("\n--- TracebackFileLogger ---")
with tempfile.TemporaryDirectory() as tmp:
tbl = TracebackFileLogger(tmp, context=3)
for i in range(3):
try:
x = 1 / (i - 1) # ZeroDivisionError on i=1
except Exception:
entry = tbl.log()
print(f" [{i}] {entry}")
print(f" file: {entry.log_file.name}")
files = list(Path(tmp).glob("*.txt"))
print(f" {len(files)} traceback file(s) written")
# ── structured_traceback ──────────────────────────────────────────────────
print("\n--- structured_traceback ---")
try:
try:
1 / 0
except ZeroDivisionError as e:
raise ValueError("bad input") from e
except Exception:
st = structured_traceback()
print(f" exc_type: {st.exc_type}")
print(f" message: {st.exc_message}")
for frame in st.frames:
print(f" {frame.filename}:{frame.lineno} {frame.function}")
if st.chained:
print(f" caused by: {st.chained.exc_type}: {st.chained.exc_message}")
print("\n=== done ===")
For the rich (PyPI) alternative — from rich.traceback import install; install() produces beautifully colored, syntax-highlighted tracebacks with local variable values in any terminal, and rich.console.Console().print_exception() can generate rich reports without modifying sys.excepthook — use rich for modern terminal applications and CLI tools where you want syntax highlighting and clean variable display without HTML; use cgitb (or the fallback implementations above) when generating HTML exception reports for development web servers or when you want file-based traceback logging in a stdlib-only environment. For the traceback alternative — stdlib traceback.format_exc(), traceback.print_exc(), traceback.TracebackException.from_exception(), and traceback.format_exception() provide structured access to the full exception chain with chained exception support — use traceback when you need structured programmatic access to exception metadata, or when cgitb is unavailable (Python 3.13+); the structured_traceback() function above uses inspect directly to add local variable extraction that plain traceback does not expose. The Claude Skills 360 bundle includes cgitb skill sets covering enable_html_tracebacks()/enable_text_tracebacks() global hooks, get_text_traceback()/get_html_traceback() manual reporters, TracebackFileLogger with timestamped file output, and StructuredTraceback / structured_traceback() for programmatic exception inspection. Start with the free tier to try rich traceback patterns and cgitb pipeline code generation.