traceact 0.1.1__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- traceact-0.2.0/PKG-INFO +187 -0
- traceact-0.2.0/README.md +157 -0
- {traceact-0.1.1 → traceact-0.2.0}/USAGE.md +227 -1
- {traceact-0.1.1 → traceact-0.2.0}/pyproject.toml +11 -1
- {traceact-0.1.1 → traceact-0.2.0}/traceact/__init__.py +3 -0
- {traceact-0.1.1 → traceact-0.2.0}/traceact/budget.py +33 -0
- traceact-0.2.0/traceact/sinks.py +481 -0
- traceact-0.2.0/traceact/viewer/__init__.py +15 -0
- traceact-0.2.0/traceact/viewer/cli.py +187 -0
- traceact-0.2.0/traceact/viewer/instance.py +169 -0
- traceact-0.2.0/traceact/viewer/reader.py +199 -0
- traceact-0.2.0/traceact/viewer/server.py +410 -0
- traceact-0.2.0/traceact/viewer/static/app.js +845 -0
- traceact-0.2.0/traceact/viewer/static/index.html +188 -0
- traceact-0.2.0/traceact/viewer/static/styles.css +654 -0
- traceact-0.2.0/traceact.egg-info/PKG-INFO +187 -0
- {traceact-0.1.1 → traceact-0.2.0}/traceact.egg-info/SOURCES.txt +10 -1
- traceact-0.2.0/traceact.egg-info/entry_points.txt +2 -0
- traceact-0.1.1/PKG-INFO +0 -99
- traceact-0.1.1/README.md +0 -70
- traceact-0.1.1/traceact/sinks.py +0 -177
- traceact-0.1.1/traceact.egg-info/PKG-INFO +0 -99
- {traceact-0.1.1 → traceact-0.2.0}/LICENSE +0 -0
- {traceact-0.1.1 → traceact-0.2.0}/MANIFEST.in +0 -0
- {traceact-0.1.1 → traceact-0.2.0}/setup.cfg +0 -0
- {traceact-0.1.1 → traceact-0.2.0}/traceact/config.py +0 -0
- {traceact-0.1.1 → traceact-0.2.0}/traceact/context.py +0 -0
- {traceact-0.1.1 → traceact-0.2.0}/traceact/decorators.py +0 -0
- {traceact-0.1.1 → traceact-0.2.0}/traceact/helpers.py +0 -0
- {traceact-0.1.1 → traceact-0.2.0}/traceact/ids.py +0 -0
- {traceact-0.1.1 → traceact-0.2.0}/traceact/trace.py +0 -0
- {traceact-0.1.1 → traceact-0.2.0}/traceact.egg-info/dependency_links.txt +0 -0
- {traceact-0.1.1 → traceact-0.2.0}/traceact.egg-info/requires.txt +0 -0
- {traceact-0.1.1 → traceact-0.2.0}/traceact.egg-info/top_level.txt +0 -0
traceact-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: traceact
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: X-ray vision for your code. Lightweight action-level tracing for Python.
|
|
5
|
+
Author: Mohammed Shehu
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/traceact/traceact
|
|
8
|
+
Project-URL: Repository, https://github.com/traceact/traceact
|
|
9
|
+
Project-URL: Documentation, https://github.com/traceact/traceact/blob/main/USAGE.md
|
|
10
|
+
Project-URL: Changelog, https://github.com/traceact/traceact/blob/main/CHANGELOG.md
|
|
11
|
+
Project-URL: Issues, https://github.com/traceact/traceact/issues
|
|
12
|
+
Project-URL: Author, https://mohammedshehu.com
|
|
13
|
+
Keywords: tracing,observability,debugging,devtools,ai-agents
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
22
|
+
Classifier: Topic :: System :: Logging
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# TraceAct
|
|
32
|
+
|
|
33
|
+
[](https://pypi.org/project/traceact/)
|
|
34
|
+
[](https://pypi.org/project/traceact/)
|
|
35
|
+
[](https://opensource.org/licenses/MIT)
|
|
36
|
+
|
|
37
|
+
X-ray vision for Python code.
|
|
38
|
+
|
|
39
|
+
TraceAct is a lightweight Python package for action-level tracing. It records the full story of what happens when a function runs — every step taken, resource touched, event recorded, and failure encountered — so you or your agent can understand what actually happened.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install traceact
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick start
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from traceact import traced_action, configure, TraceConfig, JsonlSink
|
|
51
|
+
|
|
52
|
+
configure(
|
|
53
|
+
config=TraceConfig(sink_mode="blocking"),
|
|
54
|
+
sinks=[JsonlSink("data/traces.jsonl")],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@traced_action(action="note.create", kind="app", actor="user")
|
|
58
|
+
def create_note(title, body):
|
|
59
|
+
...
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Each traced function call produces one JSON object appended to the JSONL file. Open the viewer to explore it live.
|
|
63
|
+
|
|
64
|
+
## Manual tracing
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from traceact import ActionTrace
|
|
68
|
+
|
|
69
|
+
with ActionTrace.start(action="note.create", kind="app") as trace:
|
|
70
|
+
trace.input({"title": "Hello"})
|
|
71
|
+
trace.step("Validated input")
|
|
72
|
+
trace.event(kind="db", operation="insert", target="notes")
|
|
73
|
+
trace.output({"note_id": "note_123"})
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## The viewer
|
|
77
|
+
|
|
78
|
+
TraceAct includes a local web viewer. No extra install — it ships with the package.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
traceact view data/traces.jsonl
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
This starts a server at `http://127.0.0.1:8765` and opens your browser. The viewer tails the file live: traces appear as your app writes them.
|
|
85
|
+
|
|
86
|
+
### Source types
|
|
87
|
+
|
|
88
|
+
| What you pass | What happens |
|
|
89
|
+
|---|---|
|
|
90
|
+
| A `.jsonl` file | Tails that file live |
|
|
91
|
+
| A folder | Merges all `.jsonl` files inside (e.g. per-process shards) |
|
|
92
|
+
| Nothing | Opens empty; use the in-app modal to add a source |
|
|
93
|
+
|
|
94
|
+
### CLI flags
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
traceact view [SOURCE] [--port N] [--host HOST] [--no-browser] [--new]
|
|
98
|
+
traceact show [SOURCE] ... # identical alias of view
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
| Flag | Default | Effect |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `--port N` | `8765` | Port to serve on |
|
|
104
|
+
| `--host HOST` | `127.0.0.1` | Interface to bind (localhost only by default) |
|
|
105
|
+
| `--no-browser` | off | Start the server without opening a browser tab |
|
|
106
|
+
| `--new` | off | Force a fresh instance even if one is already running |
|
|
107
|
+
|
|
108
|
+
### Port selection
|
|
109
|
+
|
|
110
|
+
The viewer auto-increments the port if the requested one is taken. If you ask for `8765` and it's in use, it tries `8766`, `8767`, and so on up to 20 times before giving up. Pass `--port` to start from a different base.
|
|
111
|
+
|
|
112
|
+
### Single-instance behaviour
|
|
113
|
+
|
|
114
|
+
Running `traceact view` a second time reuses an already-running viewer rather than starting a second server. The new source (if given) is added to the running viewer and a browser tab is opened on it. This means you can call `traceact view path/to/new-file.jsonl` from multiple terminal tabs during a session and they all feed into one viewer.
|
|
115
|
+
|
|
116
|
+
Pass `--new` to bypass this and force a second independent instance.
|
|
117
|
+
|
|
118
|
+
### macOS launcher
|
|
119
|
+
|
|
120
|
+
Double-click `launch.command` in the repo root to open TraceAct from Finder without a terminal. It checks for a running instance first, then creates a `.venv/`, installs or upgrades `traceact`, and opens the browser.
|
|
121
|
+
|
|
122
|
+
### Adding sources in the app
|
|
123
|
+
|
|
124
|
+
The source modal (click the source name in the header) lets you:
|
|
125
|
+
|
|
126
|
+
- **Choose file / Choose folder** — opens a native macOS picker; returns the real filesystem path for live tailing
|
|
127
|
+
- **Drag and drop** a `.jsonl` file — saved as a static snapshot in `~/.traceact/imports/`
|
|
128
|
+
- **Type a path** — collapsible fallback for pasting an absolute path
|
|
129
|
+
|
|
130
|
+
## Concepts
|
|
131
|
+
|
|
132
|
+
| Concept | Meaning |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `Trace` | The full record of one action (function call) |
|
|
135
|
+
| `Step` | A human-readable timeline marker within a trace |
|
|
136
|
+
| `Event` | A structured operation: db, http, file, model, job, etc. |
|
|
137
|
+
| `Touch` | A resource involved in the trace (auto-derived from events) |
|
|
138
|
+
| `Sink` | Where completed traces are written (`JsonlSink`, `ConsoleSink`) |
|
|
139
|
+
|
|
140
|
+
## Wiring into a web app
|
|
141
|
+
|
|
142
|
+
If your app has its own UI, add a backend route to launch or connect to the viewer, then call it from a button:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
# FastAPI — launch_or_connect is blocking, so use run_in_executor
|
|
146
|
+
import asyncio
|
|
147
|
+
from traceact.viewer.instance import launch_or_connect
|
|
148
|
+
|
|
149
|
+
@router.get("/api/launch-viewer")
|
|
150
|
+
async def launch_viewer():
|
|
151
|
+
loop = asyncio.get_event_loop()
|
|
152
|
+
url = await loop.run_in_executor(None, launch_or_connect,
|
|
153
|
+
"data/traces/traces.jsonl")
|
|
154
|
+
return {"url": url}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
// Frontend button
|
|
159
|
+
document.getElementById("btn-viewer").addEventListener("click", async () => {
|
|
160
|
+
const btn = document.getElementById("btn-viewer");
|
|
161
|
+
btn.disabled = true;
|
|
162
|
+
try {
|
|
163
|
+
const { url } = await fetch("/api/launch-viewer").then(r => r.json());
|
|
164
|
+
window.open(url, "_blank", "noopener");
|
|
165
|
+
} finally {
|
|
166
|
+
btn.disabled = false;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`launch_or_connect` checks for a running viewer first (via `~/.traceact/viewer.json` + a health probe). If one is found, it adds your source to it and returns the URL immediately — no new process. If nothing is running, it spawns the viewer as a background subprocess and waits up to 3 s for it to be ready.
|
|
172
|
+
|
|
173
|
+
## Requirements
|
|
174
|
+
|
|
175
|
+
Python 3.9+. No runtime dependencies.
|
|
176
|
+
|
|
177
|
+
## Full reference
|
|
178
|
+
|
|
179
|
+
See [USAGE.md](USAGE.md) for complete API documentation: all decorator and context manager parameters, helper methods (`trace.db`, `trace.http`, `trace.file`, `trace.model`), input capture, parent/child traces, sinks, budget configuration, the trace record schema, test isolation, and the full viewer server API.
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MIT
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
Built by [Mo Shehu](https://mohammedshehu.com).
|
traceact-0.2.0/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# TraceAct
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/traceact/)
|
|
4
|
+
[](https://pypi.org/project/traceact/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
X-ray vision for Python code.
|
|
8
|
+
|
|
9
|
+
TraceAct is a lightweight Python package for action-level tracing. It records the full story of what happens when a function runs — every step taken, resource touched, event recorded, and failure encountered — so you or your agent can understand what actually happened.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install traceact
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from traceact import traced_action, configure, TraceConfig, JsonlSink
|
|
21
|
+
|
|
22
|
+
configure(
|
|
23
|
+
config=TraceConfig(sink_mode="blocking"),
|
|
24
|
+
sinks=[JsonlSink("data/traces.jsonl")],
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@traced_action(action="note.create", kind="app", actor="user")
|
|
28
|
+
def create_note(title, body):
|
|
29
|
+
...
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Each traced function call produces one JSON object appended to the JSONL file. Open the viewer to explore it live.
|
|
33
|
+
|
|
34
|
+
## Manual tracing
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from traceact import ActionTrace
|
|
38
|
+
|
|
39
|
+
with ActionTrace.start(action="note.create", kind="app") as trace:
|
|
40
|
+
trace.input({"title": "Hello"})
|
|
41
|
+
trace.step("Validated input")
|
|
42
|
+
trace.event(kind="db", operation="insert", target="notes")
|
|
43
|
+
trace.output({"note_id": "note_123"})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## The viewer
|
|
47
|
+
|
|
48
|
+
TraceAct includes a local web viewer. No extra install — it ships with the package.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
traceact view data/traces.jsonl
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This starts a server at `http://127.0.0.1:8765` and opens your browser. The viewer tails the file live: traces appear as your app writes them.
|
|
55
|
+
|
|
56
|
+
### Source types
|
|
57
|
+
|
|
58
|
+
| What you pass | What happens |
|
|
59
|
+
|---|---|
|
|
60
|
+
| A `.jsonl` file | Tails that file live |
|
|
61
|
+
| A folder | Merges all `.jsonl` files inside (e.g. per-process shards) |
|
|
62
|
+
| Nothing | Opens empty; use the in-app modal to add a source |
|
|
63
|
+
|
|
64
|
+
### CLI flags
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
traceact view [SOURCE] [--port N] [--host HOST] [--no-browser] [--new]
|
|
68
|
+
traceact show [SOURCE] ... # identical alias of view
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| Flag | Default | Effect |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `--port N` | `8765` | Port to serve on |
|
|
74
|
+
| `--host HOST` | `127.0.0.1` | Interface to bind (localhost only by default) |
|
|
75
|
+
| `--no-browser` | off | Start the server without opening a browser tab |
|
|
76
|
+
| `--new` | off | Force a fresh instance even if one is already running |
|
|
77
|
+
|
|
78
|
+
### Port selection
|
|
79
|
+
|
|
80
|
+
The viewer auto-increments the port if the requested one is taken. If you ask for `8765` and it's in use, it tries `8766`, `8767`, and so on up to 20 times before giving up. Pass `--port` to start from a different base.
|
|
81
|
+
|
|
82
|
+
### Single-instance behaviour
|
|
83
|
+
|
|
84
|
+
Running `traceact view` a second time reuses an already-running viewer rather than starting a second server. The new source (if given) is added to the running viewer and a browser tab is opened on it. This means you can call `traceact view path/to/new-file.jsonl` from multiple terminal tabs during a session and they all feed into one viewer.
|
|
85
|
+
|
|
86
|
+
Pass `--new` to bypass this and force a second independent instance.
|
|
87
|
+
|
|
88
|
+
### macOS launcher
|
|
89
|
+
|
|
90
|
+
Double-click `launch.command` in the repo root to open TraceAct from Finder without a terminal. It checks for a running instance first, then creates a `.venv/`, installs or upgrades `traceact`, and opens the browser.
|
|
91
|
+
|
|
92
|
+
### Adding sources in the app
|
|
93
|
+
|
|
94
|
+
The source modal (click the source name in the header) lets you:
|
|
95
|
+
|
|
96
|
+
- **Choose file / Choose folder** — opens a native macOS picker; returns the real filesystem path for live tailing
|
|
97
|
+
- **Drag and drop** a `.jsonl` file — saved as a static snapshot in `~/.traceact/imports/`
|
|
98
|
+
- **Type a path** — collapsible fallback for pasting an absolute path
|
|
99
|
+
|
|
100
|
+
## Concepts
|
|
101
|
+
|
|
102
|
+
| Concept | Meaning |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `Trace` | The full record of one action (function call) |
|
|
105
|
+
| `Step` | A human-readable timeline marker within a trace |
|
|
106
|
+
| `Event` | A structured operation: db, http, file, model, job, etc. |
|
|
107
|
+
| `Touch` | A resource involved in the trace (auto-derived from events) |
|
|
108
|
+
| `Sink` | Where completed traces are written (`JsonlSink`, `ConsoleSink`) |
|
|
109
|
+
|
|
110
|
+
## Wiring into a web app
|
|
111
|
+
|
|
112
|
+
If your app has its own UI, add a backend route to launch or connect to the viewer, then call it from a button:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
# FastAPI — launch_or_connect is blocking, so use run_in_executor
|
|
116
|
+
import asyncio
|
|
117
|
+
from traceact.viewer.instance import launch_or_connect
|
|
118
|
+
|
|
119
|
+
@router.get("/api/launch-viewer")
|
|
120
|
+
async def launch_viewer():
|
|
121
|
+
loop = asyncio.get_event_loop()
|
|
122
|
+
url = await loop.run_in_executor(None, launch_or_connect,
|
|
123
|
+
"data/traces/traces.jsonl")
|
|
124
|
+
return {"url": url}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
// Frontend button
|
|
129
|
+
document.getElementById("btn-viewer").addEventListener("click", async () => {
|
|
130
|
+
const btn = document.getElementById("btn-viewer");
|
|
131
|
+
btn.disabled = true;
|
|
132
|
+
try {
|
|
133
|
+
const { url } = await fetch("/api/launch-viewer").then(r => r.json());
|
|
134
|
+
window.open(url, "_blank", "noopener");
|
|
135
|
+
} finally {
|
|
136
|
+
btn.disabled = false;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`launch_or_connect` checks for a running viewer first (via `~/.traceact/viewer.json` + a health probe). If one is found, it adds your source to it and returns the URL immediately — no new process. If nothing is running, it spawns the viewer as a background subprocess and waits up to 3 s for it to be ready.
|
|
142
|
+
|
|
143
|
+
## Requirements
|
|
144
|
+
|
|
145
|
+
Python 3.9+. No runtime dependencies.
|
|
146
|
+
|
|
147
|
+
## Full reference
|
|
148
|
+
|
|
149
|
+
See [USAGE.md](USAGE.md) for complete API documentation: all decorator and context manager parameters, helper methods (`trace.db`, `trace.http`, `trace.file`, `trace.model`), input capture, parent/child traces, sinks, budget configuration, the trace record schema, test isolation, and the full viewer server API.
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
Built by [Mo Shehu](https://mohammedshehu.com).
|
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
# TraceAct Usage Reference
|
|
2
2
|
|
|
3
|
-
Full API documentation for TraceAct
|
|
3
|
+
Full API and CLI documentation for TraceAct.
|
|
4
|
+
|
|
5
|
+
## Package layout
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
traceact/
|
|
9
|
+
__init__.py — public exports
|
|
10
|
+
trace.py — ActionTrace class, core lifecycle
|
|
11
|
+
decorators.py — @traced_action decorator (sync + async)
|
|
12
|
+
config.py — TraceConfig, configure(), reset_config()
|
|
13
|
+
budget.py — TraceBudget, TraceBudget.production() preset
|
|
14
|
+
context.py — ContextVar for active trace, SKIP sentinel
|
|
15
|
+
sinks.py — JsonlSink (thread-safe), ConsoleSink, AsyncSink (not yet public)
|
|
16
|
+
helpers.py — TraceHelpersMixin (trace.db, trace.http, trace.file, trace.model)
|
|
17
|
+
ids.py — ID generation (trc_, evt_, stp_, corr_ prefixes)
|
|
18
|
+
|
|
19
|
+
viewer/
|
|
20
|
+
cli.py — `traceact view` / `traceact show` CLI entry point; --new flag
|
|
21
|
+
server.py — stdlib ThreadingHTTPServer; SPA + REST + SSE
|
|
22
|
+
reader.py — SourceReader: JSONL snapshot + live byte-offset tail
|
|
23
|
+
instance.py — single-instance coordination (state file + HTTP probe);
|
|
24
|
+
launch_or_connect() for embedding in app backends
|
|
25
|
+
static/
|
|
26
|
+
index.html — single-page app shell
|
|
27
|
+
styles.css — design system (dark theme, CSS custom properties)
|
|
28
|
+
app.js — all frontend logic (no framework, no build step)
|
|
29
|
+
```
|
|
4
30
|
|
|
5
31
|
## Contents
|
|
6
32
|
|
|
@@ -21,6 +47,8 @@ Full API documentation for TraceAct v0.1.0.
|
|
|
21
47
|
15. [TraceConfig fields](#traceconfig-fields)
|
|
22
48
|
16. [Test isolation](#test-isolation)
|
|
23
49
|
17. [Trace record schema](#trace-record-schema)
|
|
50
|
+
18. [Viewing traces](#viewing-traces)
|
|
51
|
+
19. [Integrating the viewer into your app](#integrating-the-viewer-into-your-app)
|
|
24
52
|
|
|
25
53
|
---
|
|
26
54
|
|
|
@@ -486,6 +514,24 @@ def run_agent():
|
|
|
486
514
|
...
|
|
487
515
|
```
|
|
488
516
|
|
|
517
|
+
### Production preset
|
|
518
|
+
|
|
519
|
+
The package default records every trace (`sample_rate=1.0`), which is right for development and first-run — you trace a function, run it once, and the trace is there. For high-volume production, `TraceBudget.production()` opts into a lighter footprint:
|
|
520
|
+
|
|
521
|
+
```python
|
|
522
|
+
from traceact import configure, TraceBudget
|
|
523
|
+
|
|
524
|
+
configure(budget=TraceBudget.production())
|
|
525
|
+
# equivalent to TraceBudget(sample_rate=0.1, always_trace_errors=True)
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
It records roughly 10% of successful traces while never dropping a failure. Only `sample_rate` and `always_trace_errors` are set; every other field inherits from the package default or a parent trace. Override a single field on top of the preset if needed:
|
|
529
|
+
|
|
530
|
+
```python
|
|
531
|
+
budget = TraceBudget.production()
|
|
532
|
+
budget.sample_rate = 0.25 # record 25% instead of 10%
|
|
533
|
+
```
|
|
534
|
+
|
|
489
535
|
---
|
|
490
536
|
|
|
491
537
|
## TraceConfig fields
|
|
@@ -619,6 +665,186 @@ The full JSON object written to the JSONL sink:
|
|
|
619
665
|
|
|
620
666
|
---
|
|
621
667
|
|
|
668
|
+
## Viewing traces
|
|
669
|
+
|
|
670
|
+
TraceAct ships with a local, dependency-free web viewer. Installing the package gives you both the SDK and the `traceact` command — there is no separate viewer package to install.
|
|
671
|
+
|
|
672
|
+
```bash
|
|
673
|
+
pip install traceact
|
|
674
|
+
traceact view data/traces/traces.jsonl
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
This opens a browser at `http://127.0.0.1:8765` showing a live trace log, a trace map, and an inspector. The viewer tails the source, so traces appear as your app writes them.
|
|
678
|
+
|
|
679
|
+
### Command
|
|
680
|
+
|
|
681
|
+
```bash
|
|
682
|
+
traceact view [SOURCE] # open the viewer
|
|
683
|
+
traceact show [SOURCE] # alias of view (identical)
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
`view` and `show` are interchangeable aliases of the same command.
|
|
687
|
+
|
|
688
|
+
`SOURCE` is optional and may be:
|
|
689
|
+
|
|
690
|
+
- a `.jsonl` file — `traceact view data/traces.jsonl`
|
|
691
|
+
- a folder of `.jsonl` files — `traceact view data/traces/` (merges every file inside, e.g. per-process shards or several apps' files)
|
|
692
|
+
- omitted — `traceact view` opens empty and prompts you to add a source
|
|
693
|
+
|
|
694
|
+
The viewer reads any line that parses as JSON and looks like a trace; malformed entries are skipped, so files being appended to concurrently are safe to read.
|
|
695
|
+
|
|
696
|
+
### Flags
|
|
697
|
+
|
|
698
|
+
| Flag | Default | Meaning |
|
|
699
|
+
|---|---|---|
|
|
700
|
+
| `--port N` | `8765` | Port to serve on. Auto-increments if the port is taken. |
|
|
701
|
+
| `--host HOST` | `127.0.0.1` | Interface to bind. Localhost only by default. |
|
|
702
|
+
| `--no-browser` | off | Start the server without opening a browser tab. |
|
|
703
|
+
| `--new` | off | Force a new viewer instance even if one is already running. |
|
|
704
|
+
|
|
705
|
+
You can also run it as a module: `python -m traceact.viewer.cli view SOURCE`.
|
|
706
|
+
|
|
707
|
+
### Single-instance behaviour
|
|
708
|
+
|
|
709
|
+
Running `traceact view` a second time — even for a different source — reuses a viewer that is already running rather than spawning a second server. The new source is added to the running viewer and a browser tab is opened on it. This avoids accumulating background processes across repeated launches during a dev session.
|
|
710
|
+
|
|
711
|
+
The coordination mechanism is a state file at `~/.traceact/viewer.json` that records the host and port of the running viewer. On each launch, TraceAct probes that address with a health check before deciding whether to reuse or start fresh. A stale state file (crashed or stopped viewer) is ignored and a new server starts normally.
|
|
712
|
+
|
|
713
|
+
Pass `--new` to bypass this and force a second instance — useful when you want two viewers side-by-side with different sources.
|
|
714
|
+
|
|
715
|
+
### Adding sources in the viewer
|
|
716
|
+
|
|
717
|
+
The "Add source" modal supports three ways to load a source:
|
|
718
|
+
|
|
719
|
+
- **Choose file / Choose folder buttons** — opens a native macOS file or folder picker. The picker runs on the server side via AppleScript (`osascript`), so it returns the real filesystem path and the viewer can tail it live. (Falls back to a `tkinter` dialog on non-macOS platforms.)
|
|
720
|
+
- **Drag and drop** — drop a `.jsonl` file onto the drop zone. The browser reads the file contents and posts them to the server, which saves a copy to `~/.traceact/imports/` and adds it as a source. Because the server holds a copy (not the original), this is a **static snapshot** — new writes to the original file won't appear. The viewer labels imported sources accordingly.
|
|
721
|
+
- **Type a path** — a collapsible text input for pasting or typing an absolute path. This gives live tailing just like the command-line argument.
|
|
722
|
+
|
|
723
|
+
### macOS launcher (`launch.command`)
|
|
724
|
+
|
|
725
|
+
`launch.command` is a double-clickable shell script in the repo root. Opening it from Finder:
|
|
726
|
+
|
|
727
|
+
1. Probes port 8765 — if a viewer is already running, opens it immediately and exits.
|
|
728
|
+
2. Finds Python 3.9+ on the machine (checks pyenv shims, Homebrew, system Python).
|
|
729
|
+
3. Creates (or reuses) a `.venv/` virtual environment in the same directory.
|
|
730
|
+
4. Installs or upgrades `traceact` inside that venv.
|
|
731
|
+
5. Runs `traceact view`, waits for the server to be ready, then opens the browser.
|
|
732
|
+
|
|
733
|
+
The Terminal window stays open so Ctrl+C stops the viewer cleanly. To pass a source file at launch from a script: `open launch.command path/to/traces.jsonl`.
|
|
734
|
+
|
|
735
|
+
### What the viewer shows
|
|
736
|
+
|
|
737
|
+
- **Trace log** — a live, newest-first table of traces (time, action, status, duration, and touch/error/budget counts). A search box filters by action, kind, status, or touched target. The row count is capped (25 / 50 / 100 / 250, default 100) and paired with live tailing, so the newest traces are always in view.
|
|
738
|
+
- **Trace inspector** — selecting a trace shows its own ID, its parent and root trace IDs (when it is a child trace), kind, duration, and touch/error counts. "Copy JSON" copies the full record.
|
|
739
|
+
- **Trace map** — a visual of one trace: the action as origin, its events and resources as connected nodes, with per-node status and a red marker on failures. Plays as a sequential step-through replay, with a speed slider (1×–10×, live, persisted) and pause/play.
|
|
740
|
+
- **Settings** — accent colour, display density, default trace view, row count, and default replay speed — all persisted to `localStorage`.
|
|
741
|
+
|
|
742
|
+
### Viewer server API
|
|
743
|
+
|
|
744
|
+
These endpoints are available while a viewer is running. Apps and scripts can call them directly.
|
|
745
|
+
|
|
746
|
+
| Method | Path | Request | Response |
|
|
747
|
+
|---|---|---|---|
|
|
748
|
+
| `GET` | `/api/health` | — | `{"status":"ok","version":"0.2.0","sources":N}` |
|
|
749
|
+
| `GET` | `/api/sources` | — | `[{"name":"...","path":"..."}]` |
|
|
750
|
+
| `POST` | `/api/sources` | `{"path":"..."}` | `{"name":"...","path":"..."}` |
|
|
751
|
+
| `GET` | `/api/pick?type=file\|folder` | — | `{"path":"...","cancelled":bool}` |
|
|
752
|
+
| `POST` | `/api/import` | `{"name":"file.jsonl","content":"..."}` | `{"name":"...","path":"...","imported":true}` |
|
|
753
|
+
| `GET` | `/api/stream?source=NAME&limit=N` | — | SSE stream: `snapshot` then `append` events |
|
|
754
|
+
|
|
755
|
+
The SSE stream delivers one `snapshot` message (the last N traces as a JSON array) then `append` messages for each newly-written trace. A `": keepalive"` comment is sent every 0.5 s when nothing new arrives, to keep the connection alive through proxies.
|
|
756
|
+
|
|
757
|
+
### Notes
|
|
758
|
+
|
|
759
|
+
- The viewer is a **local, single-node development tool**. It reads files on the machine it runs on. Exposing one machine's traces to another over the network (`traceact serve`) is planned for a later version.
|
|
760
|
+
- The viewer server binds to localhost by default. Only pass `--host 0.0.0.0` if you understand that it exposes trace data (which may contain sensitive payloads) to your network.
|
|
761
|
+
|
|
762
|
+
---
|
|
763
|
+
|
|
764
|
+
## Integrating the viewer into your app
|
|
765
|
+
|
|
766
|
+
If your app has its own web UI, you can add a "traceact viewer" button that opens the viewer in a new tab — starting it automatically if it isn't already running.
|
|
767
|
+
|
|
768
|
+
### Backend route
|
|
769
|
+
|
|
770
|
+
Add a route that calls `launch_or_connect()`. It checks for a running viewer (via `~/.traceact/viewer.json` + health probe), adds your trace source to it, and returns the URL. If no viewer is running it spawns one as a background subprocess and waits up to 3 seconds for it to be ready.
|
|
771
|
+
|
|
772
|
+
**FastAPI** (`launch_or_connect` does blocking I/O, so run it in a thread pool):
|
|
773
|
+
```python
|
|
774
|
+
import asyncio
|
|
775
|
+
from traceact.viewer.instance import launch_or_connect
|
|
776
|
+
|
|
777
|
+
@router.get("/api/launch-viewer")
|
|
778
|
+
async def launch_viewer():
|
|
779
|
+
loop = asyncio.get_event_loop()
|
|
780
|
+
url = await loop.run_in_executor(None, launch_or_connect,
|
|
781
|
+
"data/traces/traces.jsonl")
|
|
782
|
+
return {"url": url}
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
**Flask:**
|
|
786
|
+
```python
|
|
787
|
+
from traceact.viewer.instance import launch_or_connect
|
|
788
|
+
from flask import jsonify
|
|
789
|
+
|
|
790
|
+
@app.get("/api/launch-viewer")
|
|
791
|
+
def launch_viewer():
|
|
792
|
+
url = launch_or_connect(source="data/traces/traces.jsonl")
|
|
793
|
+
return jsonify({"url": url})
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
`launch_or_connect` signature:
|
|
797
|
+
|
|
798
|
+
```python
|
|
799
|
+
launch_or_connect(
|
|
800
|
+
source=None, # path to .jsonl file or folder; added to running viewer if given
|
|
801
|
+
host="127.0.0.1", # host to bind if starting a new viewer
|
|
802
|
+
port=8765, # port to try if starting a new viewer
|
|
803
|
+
open_browser=False,
|
|
804
|
+
timeout=3.0, # seconds to wait for a freshly started server to be ready
|
|
805
|
+
) -> str # returns the viewer URL, e.g. "http://127.0.0.1:8765/"
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
### Frontend button
|
|
809
|
+
|
|
810
|
+
The button should be a `<button>` (not an `<a href>`), so you can disable it and show a loading state while the backend starts the viewer:
|
|
811
|
+
|
|
812
|
+
```javascript
|
|
813
|
+
document.getElementById("btn-traceact-viewer").addEventListener("click", async () => {
|
|
814
|
+
const btn = document.getElementById("btn-traceact-viewer");
|
|
815
|
+
btn.disabled = true;
|
|
816
|
+
btn.textContent = "opening…";
|
|
817
|
+
try {
|
|
818
|
+
const { url } = await fetch("/api/launch-viewer").then(r => r.json());
|
|
819
|
+
window.open(url, "_blank", "noopener");
|
|
820
|
+
} catch {
|
|
821
|
+
window.open("http://127.0.0.1:8765/", "_blank", "noopener");
|
|
822
|
+
} finally {
|
|
823
|
+
btn.disabled = false;
|
|
824
|
+
btn.textContent = "traceact viewer";
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
The fallback `window.open` in the `catch` block handles the case where the backend route doesn't exist yet (e.g. during development), so the button always does something useful.
|
|
830
|
+
|
|
831
|
+
### Direct API access (no backend route)
|
|
832
|
+
|
|
833
|
+
If adding a backend route isn't practical, the frontend can probe the viewer directly:
|
|
834
|
+
|
|
835
|
+
```javascript
|
|
836
|
+
async function openViewer() {
|
|
837
|
+
try {
|
|
838
|
+
await fetch("http://127.0.0.1:8765/api/health", { mode: "no-cors" });
|
|
839
|
+
} catch { /* not running — tell the user to start it */ }
|
|
840
|
+
window.open("http://127.0.0.1:8765/", "_blank", "noopener");
|
|
841
|
+
}
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
Note: cross-origin `fetch` to the viewer will always be blocked by CORS unless the viewer adds an `Access-Control-Allow-Origin` header (it doesn't, by design). Use `mode: "no-cors"` only to warm up the connection; don't try to read the response. The backend-route approach above is cleaner because it doesn't depend on the client being on the same machine as the viewer.
|
|
845
|
+
|
|
846
|
+
---
|
|
847
|
+
|
|
622
848
|
## Quick reference
|
|
623
849
|
|
|
624
850
|
```python
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "traceact"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "X-ray vision for your code. Lightweight action-level tracing for Python."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -41,9 +41,19 @@ dev = [
|
|
|
41
41
|
Homepage = "https://github.com/traceact/traceact"
|
|
42
42
|
Repository = "https://github.com/traceact/traceact"
|
|
43
43
|
Documentation = "https://github.com/traceact/traceact/blob/main/USAGE.md"
|
|
44
|
+
Changelog = "https://github.com/traceact/traceact/blob/main/CHANGELOG.md"
|
|
44
45
|
Issues = "https://github.com/traceact/traceact/issues"
|
|
45
46
|
Author = "https://mohammedshehu.com"
|
|
46
47
|
|
|
48
|
+
# The `traceact` command line. Both `traceact view` and `traceact show` are
|
|
49
|
+
# handled inside cli:main.
|
|
50
|
+
[project.scripts]
|
|
51
|
+
traceact = "traceact.viewer.cli:main"
|
|
52
|
+
|
|
47
53
|
[tool.setuptools.packages.find]
|
|
48
54
|
where = ["."]
|
|
49
55
|
include = ["traceact*"]
|
|
56
|
+
|
|
57
|
+
# Ship the viewer's static frontend (HTML/CSS/JS) inside the wheel.
|
|
58
|
+
[tool.setuptools.package-data]
|
|
59
|
+
"traceact.viewer" = ["static/*"]
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
#
|
|
26
26
|
# ConsoleSink — prints traces to stdout.
|
|
27
27
|
|
|
28
|
+
__version__ = "0.2.0"
|
|
29
|
+
|
|
28
30
|
from traceact.config import configure, reset_config, TraceConfig
|
|
29
31
|
from traceact.budget import TraceBudget
|
|
30
32
|
from traceact.trace import ActionTrace
|
|
@@ -32,6 +34,7 @@ from traceact.decorators import traced_action
|
|
|
32
34
|
from traceact.sinks import JsonlSink, ConsoleSink
|
|
33
35
|
|
|
34
36
|
__all__ = [
|
|
37
|
+
"__version__",
|
|
35
38
|
"ActionTrace",
|
|
36
39
|
"TraceConfig",
|
|
37
40
|
"TraceBudget",
|
|
@@ -84,6 +84,39 @@ class TraceBudget:
|
|
|
84
84
|
self.sample_rate = sample_rate
|
|
85
85
|
self.always_trace_errors = always_trace_errors
|
|
86
86
|
|
|
87
|
+
@classmethod
|
|
88
|
+
def production(cls) -> "TraceBudget":
|
|
89
|
+
"""
|
|
90
|
+
A preset budget tuned for high-volume production use.
|
|
91
|
+
|
|
92
|
+
The package default records every trace (sample_rate=1.0), which is the
|
|
93
|
+
right behaviour for development and first-run: you trace a function, run
|
|
94
|
+
it once, and the trace is there. That default is deliberately NOT changed
|
|
95
|
+
globally, because a developer who wired TraceAct in and then found nothing
|
|
96
|
+
recorded would be justifiably confused.
|
|
97
|
+
|
|
98
|
+
In production, though, an app might run hundreds or thousands of traced
|
|
99
|
+
actions per second. Recording all of them can strain the sink and the
|
|
100
|
+
viewer. This preset opts into a lighter footprint:
|
|
101
|
+
|
|
102
|
+
sample_rate=0.1 record roughly 10% of successful traces
|
|
103
|
+
always_trace_errors=True never drop a failed trace, even when sampled
|
|
104
|
+
|
|
105
|
+
So you keep a representative sample of healthy traffic while still
|
|
106
|
+
capturing every failure — which is the part you most need when debugging.
|
|
107
|
+
|
|
108
|
+
Only sample_rate and always_trace_errors are set here. Every other field
|
|
109
|
+
is left as None so it inherits from the package default or a parent trace.
|
|
110
|
+
|
|
111
|
+
Usage:
|
|
112
|
+
configure(budget=TraceBudget.production())
|
|
113
|
+
|
|
114
|
+
# or override a single field on top of the preset:
|
|
115
|
+
budget = TraceBudget.production()
|
|
116
|
+
budget.sample_rate = 0.25 # record 25% instead of 10%
|
|
117
|
+
"""
|
|
118
|
+
return cls(sample_rate=0.1, always_trace_errors=True)
|
|
119
|
+
|
|
87
120
|
|
|
88
121
|
# ---------------------------------------------------------------------------
|
|
89
122
|
# Package-level defaults
|