Usage guide¶
Installation¶
Install a local checkout with python -m pip install .. For the public
distribution, use python -m pip install python-traceprint. Python imports remain
traceprint. To develop or build these docs, run uv sync --dev.
Tracing print output¶
Passing the tracer as file avoids changing process streams:
from traceprint import Tracer
tracer = Tracer(use_colors=False, use_tabulation=True)
print("starting", file=tracer)
with tracer.span("load"):
print("loaded", 42, "records", file=tracer)
The default prefix includes a UTC timestamp, cumulative time since construction,
elapsed time since the last formatted message and the full label path. Elapsed
times use a monotonic performance clock. Metadata is added once per nonblank
output line. Blank lines pass through, partial writes appear immediately and
flush() does not end the current line. Use a newline to begin a fresh prefix.
To intercept ordinary print(), install explicitly and always restore streams:
tracer.install(stdout=True, stderr=False)
try:
print("hello", "world")
finally:
tracer.uninstall()
stderr=True routes intercepted stderr to the same tracer sink. There is no
independent stderr formatting path. Avoid overlapping installations by multiple
tracers: restoration uses the streams captured by each installation.
Spans and decorators¶
span() increases depth and adds a label inside its context, then restores
them on exit, including when the body raises. push() and pop() provide
manual equivalents when a context manager is inconvenient.
from traceprint import trace
@trace(tracer_instance=tracer, label="transform")
def transform() -> None:
print("transformed", file=tracer)
transform()
Without a label, the decorator uses the function name. Without a
tracer_instance, it uses the package’s global tracer. The decorator wraps
synchronous function calls; it does not track an async coroutine’s execution
across awaits or a generator’s iteration. Context state belongs to the tracer,
not an individual thread or async task. Keep tracer configuration stable while a
span or decorated call is active.
Configuration¶
tracer.configure(
use_colors=False,
use_tabulation=True,
print_transitions=True,
filter_levels=range(0, 3),
output_format="[{label_full}] {original_message}",
)
use_colors=None checks whether the output stream is a TTY. Explicit True
or False overrides detection. use_tabulation=True inserts four spaces per
depth level. With the default metadata color, indentation precedes the message;
with metacolor=None, it precedes the whole formatted line. Colors do not
change indentation placement.
filter_levels accepts allowed depths as a range or iterable, or a predicate
that returns True for allowed depths. None allows all depths. Filtering
suppresses output rather than changing nesting. disable() forwards raw text
and prevents depth changes; enable() resumes formatting. lock() prevents
depth changes until unlock().
The custom format accepts timestamp, cumulative, elapsed, label,
label_full and original_message. Include original_message to retain
the text. Timestamp syntax follows datetime.strftime. label is the last
label and label_full is the slash-separated stack; regular labeled messages
add />. Timing advances when a message is formatted, including direct
format() calls.
The format is applied to the first nonblank fragment of a line; subsequent fragments are appended immediately. Prefer prefix-based formats for incremental output, since a custom suffix would follow that first fragment.
Logging¶
Use a dedicated logger to avoid changing the application’s root logging setup:
import logging
from traceprint import TracerHandler
logger = logging.getLogger("example")
handler = TracerHandler(tracer_instance=tracer)
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.propagate = False
try:
logger.info("loaded %s records", 42)
finally:
logger.removeHandler(handler)
handler.close()
LoggerMixin provides a class-named logger and a self.log proxy controlled
by verbose. The transformer example shows it with empty scikit-learn-style
base classes. logging_passthrough=True attaches a handler targeting the
global tracer and disables propagation; this changes the shared class logger’s
handlers. Leave it off when the application configures logging itself.
Files¶
Mirror tracer output to a file, stripping terminal escape sequences:
from traceprint import TracerFileHandler
file_handler = TracerFileHandler("trace.log", mode="a")
tracer.add_file_handler(file_handler)
try:
print("saved", file=tracer)
tracer.flush()
finally:
tracer.remove_file_handler(file_handler)
file_handler.close()
Alternatively, attach TracerFileHandler(..., tracer_instance=tracer) directly
to a logger. It formats each record for the file. Use one route for a given
record to avoid duplicates. File logging and mirroring strip ANSI escapes.
Closing a handler owns file cleanup; uninstalling a tracer only restores process
streams. Raw output while the tracer is disabled bypasses mirrored file handlers.
Global labels¶
TRACEPRINT_INITIAL_LABELS=job/load seeds the global tracer’s label stack at
import time. Empty slash-separated components are ignored. It does not install
the tracer. Explicit Tracer instances do not read this variable.