painted renders the two diagnostic surfaces every program already produces — log records and uncaught tracebacks — as structured Blocks disclosed by zoom, not format strings. A log level is a declared severity; a traceback is a record tree, and capturing it is the declaration. Both render through the same core the rest of painted uses, so a diagnostic looks like the rest of your output.
Two independent opt-ins. Add PaintedHandler to a logger to render its records; call install() to catch what escapes. Neither is on by default — painted renders diagnostics only when you declare it should.
import logging, painted
logging.getLogger().addHandler(painted.PaintedHandler())
painted.install() # uncaught tracebacks render too
A record's levelno resolves to the Severity of the greatest threshold floor it clears, and Severity drives the palette role the row is styled in. The default mapping mutes DEBUG onto INFO (the journalctl principle — routine noise stays quiet) and folds CRITICAL onto ERROR (the palette's loudest role). Pass your own thresholds to change where the lines fall — a custom mapping changes the output, or it wouldn't be worth declaring.
from painted import PaintedHandler
from painted.views import Severity
handler = PaintedHandler(thresholds={
logging.INFO: Severity.INFO,
logging.WARNING: Severity.WARNING,
logging.ERROR: Severity.ERROR,
})
render_traceback captures a live exception (or renders a TracebackException you already captured) and projects it at a zoom level: MINIMAL is type + message + the innermost frame on one line; SUMMARY adds the frame stack with chains summarized; DETAILED adds source with a caret; FULL adds wider source, redacted locals, and fully expanded groups. suppress folds the frames you didn't write to a single muted line.
ValueError: invalid literal for int() with base 10: 'eight' at _ The above exception was the direct cause of the following excep… RuntimeError: could not start server │ _doc_pages.py:435 in _traceback_gallery
The default traceback is a wall of text you scan by eye; a log line is a format string you re-invent per project. painted renders both from the structure already in the data — the frame tree, the level, the extra fields — so severity reads at a glance, the failing line carries a caret, and a worker thread's log looks like the main thread's. Nothing is invented; the rendering derives from what was declared.
The delivery glue lives at the package root, not in painted.cli. A log handler and an excepthook are not argv-driven, and the CLI layer holds a frozen tripwire — nothing in cli/ may import the renderer. Root modules may (the precedent is inplace.py), so diagnostics mounts render_traceback there without touching the CLI seam. Full design: docs/DIAGNOSTICS_DESIGN.md.
The three surfaces share one substrate. render_traceback's locals route through the same shape_lens show() uses — hardened here to be cycle-safe and schema-aware, so a cyclic local can't crash the error renderer. PaintedHandler composes render_traceback for exc_info rather than re-deriving frame structure. One hardening, two deliverers.