Focused, opt-in runtime call tracing for a single execution context.
Project description
tracepatch
Focused, opt-in runtime call tracing for a single execution context.
tracepatch is a debugging tool. It records function calls, arguments, return values, and timing for one specific scope (a request handler, a CLI command, a background task) and produces a readable call tree. It is not a replacement for OpenTelemetry, structured logging, or APM dashboards. Think of it as a scalpel: you point it at one execution path that is misbehaving, and it tells you exactly what happened. It is recommended you leave tracepatch installed in production and have it ready to use when you need it, since it has zero overhead when not active.
Features
- Pure Python, no external dependencies for core tracing (optional TOML support requires
tomlion Python <3.11). - Zero overhead when inactive. Safe to leave installed in production; the profiling hook is only active inside a
trace()block. - Works in synchronous and asynchronous code with full async/await support.
- Context-isolated tracing using
contextvars- concurrent async tasks don't interfere with each other. - Built-in safety limits (
max_depth,max_calls) that automatically disable tracing if exceeded. - TOML configuration support via
tracepatch.tomlorpyproject.tomlfiles. - Powerful CLI (
tph/tracepatch) for viewing logs, displaying trees, and managing test environments. - Automated test setup - automatically instrument functions for tracing with
tph setup. - Git-aware safety checks - warns about staged changes before setup/disable operations.
- Human-readable ASCII call tree output with timing information.
- JSON export for further processing and custom analysis.
- Automatic caching of traces to
.tracepatch_cache/for later review.
Installation
pip install tracepatch
Or install from source:
pip install .
Quick start
Synchronous
from tracepatch import trace
def fetch_user(user_id):
return {"id": user_id, "name": "Alice"}
def handle_request():
user = fetch_user(42)
return user
with trace() as t:
handle_request()
print(t.tree())
Output:
└── __main__.handle_request() [0.03ms]
└── __main__.fetch_user(user_id=42) -> {'id': 42, 'name': 'Alice'} [0.01ms]
Asynchronous
import asyncio
from tracepatch import trace
async def fetch_user(user_id):
return {"id": user_id, "name": "Alice"}
async def handle_request():
user = await fetch_user(42)
return user
async def main():
async with trace() as t:
await handle_request()
print(t.tree())
asyncio.run(main())
JSON export
with trace() as t:
handle_request()
t.to_json("trace.json")
The JSON file contains a structured representation of the call tree with timing in milliseconds, suitable for custom analysis scripts.
Configuration
The trace() constructor accepts the following keyword arguments:
| Parameter | Default | Description |
|---|---|---|
ignore_modules |
[] |
List of module name prefixes to exclude from the trace. |
max_depth |
30 |
Maximum call nesting depth. Deeper calls are silently skipped. |
max_calls |
10000 |
Maximum total calls to record. Tracing freezes when exceeded. |
max_repr |
120 |
Maximum character length for repr() of arguments and return values. |
TOML Configuration Files
Instead of passing parameters directly, you can configure tracepatch via TOML files. tracepatch will automatically search for:
tracepatch.tomlin the current or parent directories[tool.tracepatch]section inpyproject.toml
Example tracepatch.toml:
# Modules to ignore during tracing (by prefix)
ignore_modules = ["urllib3", "requests", "http"]
# Maximum call nesting depth to record
max_depth = 50
# Maximum total calls to record before disabling tracing
max_calls = 20000
# Maximum character length for repr() of arguments and return values
max_repr = 200
# Enable automatic caching of traces
cache = true
# Custom cache directory (default: .tracepatch_cache)
# cache_dir = ".traces"
# Default label for traces (appears in logs)
default_label = "my-app"
# Automatically save traces to cache
auto_save = true
# Display settings
show_args = true
show_return = true
tree_style = "ascii" # or "unicode"
# Test setup configuration (optional)
# Used by 'tph setup' command to generate test runners
[[test.files]]
path = "mymodule.py"
functions = ["my_function", "process_data"]
[[test.inputs]]
function = "my_function"
args = [42, "test"]
kwargs = {user_id = "abc123"}
Using config files in code:
from tracepatch import trace, load_config
# Load configuration from TOML files
config, config_path = load_config()
# Use the configuration
with trace(**config.to_trace_kwargs()) as t:
my_function()
print(t.tree())
Command-Line Interface
tracepatch includes a powerful CLI for viewing traces and managing test environments. Two commands are available: tracepatch and the shorter alias tph (recommended).
List cached traces
$ tph logs
Lists all trace logs in .tracepatch_cache/ with timestamps, labels, and call counts.
Options:
--cache-dir DIR- Specify custom cache directory--limit N- Limit number of results (default: 50)
View trace metadata
$ tph view .tracepatch_cache/trace_20260212_143022_123456.json
Shows detailed metadata:
- Timestamp and label
- Call count and limit status
- Configuration used (max_depth, max_calls, etc.)
- Root function summary
Display call tree
$ tph tree .tracepatch_cache/trace_20260212_143022_123456.json
Renders the full call tree with timing from a saved trace file.
Options:
--max-depth N- Limit tree depth for readability--show-args / --no-args- Toggle argument display--show-return / --no-return- Toggle return value display
Show configuration
$ tph config
Displays current configuration loaded from TOML files. Shows:
- Configuration file location (or defaults if none found)
- All tracing settings
- Test configuration if present
If no config file is found, shows helpful instructions on how to create one.
Options:
--file PATH- Check specific config file
Help
$ tph help
Shows comprehensive usage information with examples for all commands.
Automated test setup workflow
tracepatch can automatically instrument and test specific functions from your configuration. This is especially useful for backend Python projects where functions might not work in isolation.
Quick start
# Set up test environment (reads tracepatch.toml)
$ tph setup
# Run the generated test file
$ python _tracepatch_filetotest.py
# Clean up when done
$ tph disable
Configuration
First, create a tracepatch.toml with test configuration:
# Basic tracing settings
max_depth = 50
max_calls = 20000
cache = true
default_label = "test-run"
# Specify which files and functions to test
[[test.files]]
path = "mymodule.py"
functions = ["my_function", "process_data", "calculate"]
[[test.files]]
path = "database.py"
functions = ["connect", "query"]
# Optional: Provide custom test inputs
[[test.inputs]]
function = "my_function"
args = [42, "test"]
kwargs = {user_id = "abc123"}
[[test.inputs]]
function = "calculate"
args = [10, 20]
kwargs = {}
Running tph setup
The setup command:
-
Checks Git status (if Git is available)
- ⚠️ Warns if you have staged changes and asks for confirmation
- ℹ️ Notes if you have unstaged changes
- Continues safely if no Git or no changes
-
Validates configuration
- Checks that all configured files exist
- Verifies function signatures using AST parsing
- Reports missing files or functions
-
Creates necessary files
- Generates
__init__.pyif needed (makes directory a Python package) - Creates
_tracepatch_filetotest.pywith wrapper functions - Auto-generates default arguments for functions without custom inputs
- Generates
-
Saves setup state
- Stores state in
.tracepatch_cache/setup_state.json - Records all created and modified files
- Important: Don't delete
.tracepatch_cache/- needed for cleanup!
- Stores state in
Running the test file
The generated _tracepatch_filetotest.py:
- Imports and wraps your functions
- Creates wrapper functions that enable tracing
- Calls each function with configured or auto-generated inputs
- Displays results with ✓ (success) or ✗ (exception) indicators
- Shows full call tree with timing
- Saves trace to cache for later review
$ python _tracepatch_filetotest.py
Testing functions from tracepatch configuration...
Testing functions from mymodule.py:
✓ my_function returned: 123
✓ process_data returned: [1, 2, 3]
✗ calculate raised: TypeError: missing required argument
======================================================================
TRACE RESULTS
======================================================================
Total calls: 47
Trace saved to: .tracepatch_cache/trace_20260212_143802_618610_test-run.json
View with: tph tree .tracepatch_cache/trace_20260212_143802_618610_test-run.json
[Call tree output...]
Running tph disable
The disable command:
-
Checks Git status (if Git is available)
- ⚠️ Warns if you have staged changes
- Asks for confirmation before proceeding
-
Cleans up generated files
- Removes
_tracepatch_filetotest.py - Removes
__init__.pyif it was created by setup - Restores any modified files to their original state
- Removes
-
Preserves traces
- Keeps
.tracepatch_cache/directory and all trace logs - Only removes the setup state file
- Traces remain available for analysis with
tph logs,tph tree, etc.
- Keeps
Error handling
If you run tph setup without a configuration file:
❌ Error: No tracepatch configuration file found!
What to do:
1. Create a 'tracepatch.toml' file in your project directory
2. OR add a [tool.tracepatch] section to your pyproject.toml
Example tracepatch.toml:
[Full example shown...]
If your config file exists but has no test configuration:
❌ Error: No test files configured!
Configuration loaded from: /path/to/tracepatch.toml
But no [[test.files]] section was found.
Add this to your config file:
[[test.files]]
path = "your_file.py"
functions = ["function_name"]
Usage Examples
Filtering noise
with trace(ignore_modules=["logging", "urllib3", "ssl"]) as t:
handle_request()
print(t.tree())
Limiting scope
with trace(max_depth=5, max_calls=500) as t:
handle_request()
if t.was_limited:
print("Warning: trace was truncated due to limits")
print(t.tree())
Python API Reference
trace(**kwargs)
Context manager (sync and async). Returns itself. Configuration via keyword arguments listed above.
t.tree() -> str
Returns a human-readable ASCII call tree string with timing information for each call.
t.to_json(path) -> None
Writes the trace to a JSON file. path can be a string, a pathlib.Path,
or a writable file object.
t.call_count -> int
Number of calls recorded.
t.was_limited -> bool
True if the trace was cut short because max_calls was exceeded.
t.roots -> list[TraceNode]
Direct access to the root TraceNode objects for programmatic traversal.
load_config(path=None, search_parents=True) -> tuple[TracepatchConfig, Path | None]
Load configuration from TOML files.
Parameters:
path: Optional explicit path to a TOML filesearch_parents: If True, search parent directories for config files
Returns:
- Tuple of (TracepatchConfig, config file path or None)
Example:
from tracepatch import load_config, trace
config, config_path = load_config()
if config_path:
print(f"Loaded from {config_path}")
with trace(**config.to_trace_kwargs()) as t:
my_function()
TracepatchConfig
Configuration dataclass with the following attributes:
ignore_modules: List of module prefixes to excludemax_depth: Maximum call nesting depthmax_calls: Maximum total calls to recordmax_repr: Maximum repr() lengthcache: Enable automatic cachingcache_dir: Custom cache directorydefault_label: Default label for tracesauto_save: Automatically save tracesshow_args: Show arguments in outputshow_return: Show return values in outputtree_style: Tree rendering style ("ascii" or "unicode")
Methods:
to_trace_kwargs(): Convert to kwargs dict fortrace()constructor
trace.logs(cache_dir=None, limit=50) -> list[dict]
Class method to list recent trace logs from .tracepatch_cache/.
Parameters:
cache_dir: Optional parent directory containing.tracepatch_cache/limit: Maximum number of entries to return (default: 50)
Returns:
- List of dicts with metadata for each trace (timestamp, label, call_count, was_limited, file path)
Example:
from tracepatch import trace
for entry in trace.logs():
print(f"{entry['timestamp']} - {entry['label']}: {entry['call_count']} calls")
trace.load(path) -> dict
Class method to load a complete trace log from a JSON file.
Parameters:
path: Path to a trace JSON file
Returns:
- The full JSON contents as a dict
Example:
from tracepatch import trace
data = trace.load(".tracepatch_cache/trace_20260212_143022_123456.json")
print(f"Trace had {data['call_count']} calls")
Documentation
For detailed guides and examples, see the documentation in the docs/ directory:
- CLI Guide - Comprehensive guide to all CLI commands with examples
- Configuration Guide - Complete reference for TOML configuration options
- Reading Logs - Working with trace logs from Python and the command line
Quick Links
- CLI Commands: See CLI Guide for
tph logs,tph tree,tph setup, etc. - TOML Configuration: See Configuration Guide for all settings
- Test Setup: See CLI Guide for automated testing workflow
- Log Format: See Reading Logs for JSON structure details
How it works
tracepatch uses sys.settrace to install a lightweight tracing callback
that fires on function call, return, and exception events in the current
thread. On a 'call' event, the global trace function checks a
contextvars.ContextVar to find the active collector for the current
execution context. If no collector is active (the common case in production),
the callback returns None immediately and Python does not trace that frame
further, so overhead is negligible.
When a trace() block is entered, a new collector is created and stored in
the ContextVar. The collector records call events into a tree of TraceNode
objects. When the block exits, the profiling hook is removed (reference
counted, so nested traces work correctly) and the ContextVar is reset.
Because isolation is done through contextvars rather than thread-locals,
concurrent asyncio tasks each get their own independent trace even though
they share a single thread.
Limitations
sys.settracecaptures Python-level calls in the thread while active. C-level functions (builtins, C extensions) are not captured.- There is measurable overhead while a trace block is active. This is a debugging tool meant for targeted use, not always-on instrumentation.
- Traces are scoped to a single thread. If your code spawns threads, only the originating thread is traced by default.
License
MIT. See LICENSE for the full text.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tracepatch-0.2.0.tar.gz.
File metadata
- Download URL: tracepatch-0.2.0.tar.gz
- Upload date:
- Size: 31.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b13e72e2a946478f2c68a8c7267b0d0324cb62933b610572bf0225210f51bb8f
|
|
| MD5 |
55bfe6d506f3d4b74f97f324f917d313
|
|
| BLAKE2b-256 |
c9c8ecd6976ee45505741ce448fc29bbf767bdcb34d5040e58c8c9f7beb6a6eb
|
Provenance
The following attestation bundles were made for tracepatch-0.2.0.tar.gz:
Publisher:
publish.yml on levinismynameirl/tracepatch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tracepatch-0.2.0.tar.gz -
Subject digest:
b13e72e2a946478f2c68a8c7267b0d0324cb62933b610572bf0225210f51bb8f - Sigstore transparency entry: 943966179
- Sigstore integration time:
-
Permalink:
levinismynameirl/tracepatch@884e0bea6a60644f0b22c4e8dbde0184569d8064 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/levinismynameirl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@884e0bea6a60644f0b22c4e8dbde0184569d8064 -
Trigger Event:
release
-
Statement type:
File details
Details for the file tracepatch-0.2.0-py3-none-any.whl.
File metadata
- Download URL: tracepatch-0.2.0-py3-none-any.whl
- Upload date:
- Size: 25.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a88bc12caa4545a63452ea48bfb21b6a8cc062a1ed59f4ece1c43f6e6698641
|
|
| MD5 |
a0864aafbf778c44e0baecbbf064d65b
|
|
| BLAKE2b-256 |
edf3d2ba3875d1ba8afe9c9bea1b8f6d7066d745d33d3e6562e4f54ccc0232a3
|
Provenance
The following attestation bundles were made for tracepatch-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on levinismynameirl/tracepatch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tracepatch-0.2.0-py3-none-any.whl -
Subject digest:
5a88bc12caa4545a63452ea48bfb21b6a8cc062a1ed59f4ece1c43f6e6698641 - Sigstore transparency entry: 943966184
- Sigstore integration time:
-
Permalink:
levinismynameirl/tracepatch@884e0bea6a60644f0b22c4e8dbde0184569d8064 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/levinismynameirl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@884e0bea6a60644f0b22c4e8dbde0184569d8064 -
Trigger Event:
release
-
Statement type: