Skip to content

Commit aa81052

Browse files
committed
WS8 - Docs
1 parent 305c18e commit aa81052

File tree

7 files changed

+174
-16
lines changed

7 files changed

+174
-16
lines changed

README.md

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,68 @@
22

33
This repository now hosts two related projects:
44

5-
- codetracer-pure-python-recorder — the existing pure-Python prototype that records [CodeTracer](https://github.com/metacraft-labs/CodeTracer) traces using sys.settrace.
6-
- codetracer-python-recorder — a new, Rust-backed Python extension module (PyO3) intended to provide a faster and more featureful recorder.
5+
- codetracer-pure-python-recorder — a pure-Python tracer that still mirrors the early prototype.
6+
- codetracer-python-recorder — a Rust-backed Python extension (PyO3 + maturin) with structured errors and tighter tooling.
77

8-
> [!WARNING]
9-
> Both projects are early-stage prototypes. Contributions and discussion are welcome!
8+
Both projects are still in motion. Expect breaking changes while we finish the error-handling rollout.
9+
10+
### Structured errors (Rust-backed recorder)
11+
12+
The Rust module wraps every failure in a `RecorderError` hierarchy that reaches Python with a stable `code`, a readable `kind`, and a `context` dict.
13+
14+
- `UsageError` → bad input or calling pattern. Codes like `ERR_ALREADY_TRACING`.
15+
- `EnvironmentError` → IO or OS problems. Codes like `ERR_IO`.
16+
- `TargetError` → the traced program raised or refused inspection. Codes like `ERR_TRACE_INCOMPLETE`.
17+
- `InternalError` → a recorder bug or panic. Codes default to `ERR_UNKNOWN` unless classified.
18+
19+
Quick catch example:
20+
21+
```python
22+
from codetracer_python_recorder import RecorderError, start, stop
23+
24+
try:
25+
session = start("/tmp/trace", format="json")
26+
except RecorderError as err:
27+
print(f"Recorder failed: {err.code}")
28+
for key, value in err.context.items():
29+
print(f" {key}: {value}")
30+
else:
31+
try:
32+
... # run work here
33+
finally:
34+
session.flush()
35+
stop()
36+
```
37+
38+
All subclasses carry the same attributes, so existing handlers can migrate by catching `RecorderError` once and branching on `err.code` if needed.
39+
40+
### CLI exit behaviour and JSON trailers
41+
42+
`python -m codetracer_python_recorder` returns:
43+
44+
- `0` when tracing and the target script succeed.
45+
- The script's own exit code when it calls `sys.exit()`.
46+
- `1` when a `RecorderError` bubbles out of startup or shutdown.
47+
- `2` when the CLI arguments are incomplete.
48+
49+
Pass `--codetracer-json-errors` (or set the policy via `configure_policy(json_errors=True)`) to stream a one-line JSON trailer on stderr. The payload includes `run_id`, `trace_id`, `error_code`, `error_kind`, `message`, and the `context` map so downstream tooling can log failures without scraping text.
50+
51+
### Migration checklist for downstream tools
52+
53+
- Catch `RecorderError` (or a subclass) instead of `RuntimeError`.
54+
- Switch any string matching over to `err.code` values like `ERR_TRACE_DIR_CONFLICT`.
55+
- Expect structured log lines (JSON) on stderr. Use the `error_code` field instead of parsing text.
56+
- Opt in to JSON trailers for machine parsing and keep human output short.
57+
- Update policy wiring to use `configure_policy` / `policy_snapshot()` rather than hand-rolled env parsing.
58+
- Read `docs/onboarding/error-handling.md` for detailed migration steps and assertion rules.
59+
60+
### Logging defaults
61+
62+
The recorder now installs a JSON logger on first import. Logs include `run_id`, optional `trace_id`, and an `error_code` field when set.
63+
64+
- Control the log filter with `RUST_LOG=target=level` (standard env syntax).
65+
- Override from Python with `configure_policy(log_level="info")` or `log_file=...` for file output.
66+
- Metrics counters record dropped events, detach reasons, and caught panics; plug your own sink via the Rust API when embedding.
1067

1168
### codetracer-pure-python-recorder
1269

@@ -64,7 +121,7 @@ RUST_LOG=codetracer_python_recorder=debug pytest \
64121
codetracer-python-recorder/tests/python/unit/test_backend_exceptions.py -q
65122
```
66123

67-
Any filter accepted by `env_logger` works, so you can switch to
124+
Any filter accepted by `env_logger` still works, so you can switch to
68125
`RUST_LOG=codetracer_python_recorder=info` or silence everything with
69126
`RUST_LOG=off`.
70127

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# codetracer-python-recorder — Change Log
2+
3+
## Unreleased
4+
- Documented the error-handling policy. README now lists the `RecorderError` hierarchy, policy hooks (`configure_policy`, JSON trailers), exit codes, and sample handlers so Python callers can consume structured failures.
5+
- Added an onboarding guide under `docs/onboarding/error-handling.md` with migration steps for downstream tools.
6+
- Recorded assertion guidance for contributors: prefer `bug!`/`ensure_internal!` over raw `panic!`/`.unwrap()` and keep `debug_assert!` paired with classified errors.

codetracer-python-recorder/codetracer_python_recorder/__init__.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
"""High-level tracing API built on a Rust backend.
1+
"""Public tracing surface with structured recorder errors.
22
3-
This module exposes a minimal interface for starting and stopping
4-
runtime traces. The heavy lifting is delegated to the
5-
`codetracer_python_recorder` Rust extension which will eventually hook
6-
into `runtime_tracing` and `sys.monitoring`. For now the Rust side only
7-
maintains placeholder state and performs no actual tracing.
3+
Importing this package installs policy defaults, wires the Rust backend,
4+
and exposes helpers to start and stop tracing. Every failure travels
5+
through :class:`RecorderError` or one of its subclasses. Each exception
6+
carries a stable ``code`` string (``ERR_*``), a ``kind`` label, and a
7+
``context`` dict for tooling.
88
"""
99

1010
from . import api as _api

codetracer-python-recorder/codetracer_python_recorder/api.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
"""High-level tracing API built on a Rust backend."""
1+
"""High-level tracing helpers with structured error propagation.
2+
3+
Expose the core session helpers (:func:`start`, :func:`stop`,
4+
:func:`trace`, etc.). These wrappers bubble up :class:`RecorderError`
5+
instances from the Rust layer so callers see stable ``ERR_*`` codes.
6+
"""
27
from __future__ import annotations
38

49
from typing import Iterable

codetracer-python-recorder/codetracer_python_recorder/session.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
"""Tracing session management helpers."""
1+
"""Tracing session management helpers with policy integration.
2+
3+
These wrappers load policy from env vars, call into the Rust backend,
4+
and surface structured :class:`RecorderError` instances on failure.
5+
"""
26
from __future__ import annotations
37

48
import contextlib
@@ -20,7 +24,11 @@
2024

2125

2226
class TraceSession:
23-
"""Handle representing a live tracing session."""
27+
"""Handle representing a live tracing session.
28+
29+
The object keeps the resolved trace path and format. Use
30+
:meth:`flush` and :meth:`stop` to interact with the global session.
31+
"""
2432

2533
path: Path
2634
format: str
@@ -72,6 +80,20 @@ def start(
7280
When ``True`` (default), refresh policy settings from environment
7381
variables via :func:`configure_policy_from_env` prior to applying
7482
explicit overrides.
83+
84+
Returns
85+
-------
86+
TraceSession
87+
Handle for the active recorder session.
88+
89+
Raises
90+
------
91+
RecorderError
92+
Raised by the Rust backend when configuration, IO, or the target
93+
script fails.
94+
RuntimeError
95+
Raised when ``start`` is called while another session is still
96+
active. The guard lives in Python so the error stays synchronous.
7597
"""
7698
global _active_session
7799
if _is_tracing_backend():

design-docs/error-handling-implementation-plan.status.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Error Handling Implementation Plan — Status
22

3-
_Last updated: 2025-10-04_
3+
_Last updated: 2025-10-05_
44

55
## WS1 – Foundations & Inventory
66
State: In progress
@@ -62,5 +62,15 @@ Highlights:
6262
- Implemented `just lint` orchestration running `cargo clippy -D clippy::panic` and a repository script that blocks unchecked `.unwrap(` usage outside the legacy allowlist.
6363
Next moves: Monitor unwrap allowlist shrinkage once WS1 follow-ups land; evaluate extending the lint to `.expect(` once monitoring refactor closes.
6464

65+
## WS8 – Documentation & Rollout
66+
State: Done (2025-10-05)
67+
Highlights:
68+
- README now covers the recorder error policy, JSON trailers, exit codes, and a short Python `RecorderError` catch example.
69+
- Added `docs/onboarding/error-handling.md` with migration steps, policy wiring tips, and assertion rules for contributors.
70+
- Started `codetracer-python-recorder/CHANGELOG.md` to brief downstream tools on consuming structured errors.
71+
Next moves:
72+
- Share the onboarding doc with downstream maintainers and collect gaps before promoting ADR 0004 to **Accepted**.
73+
- Fold feedback into the change log before the next release tag.
74+
6575
## Upcoming Workstreams
66-
WS8 – Documentation & Rollout: Not started. Pending guidance from Docs WG and ADR promotion once downstream consumers validate the new error interfaces.
76+
- None. Hold for ADR 0004 promotion once downstream validation wraps up.

docs/onboarding/error-handling.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Recorder Error Handling Onboarding
2+
3+
This note aligns new contributors and downstream consumers on the structured error work. Keep it close when you wire the recorder into tools or review patches that touch failure paths.
4+
5+
## Error classes at a glance
6+
- `RecorderError` is the base class. Subclasses are `UsageError`, `EnvironmentError`, `TargetError`, and `InternalError`.
7+
- Every instance exposes `code` (an `ERR_*` string), `kind` (matches the class), and a `context` dict with string keys.
8+
- Codes stay stable. Add new codes instead of recycling strings.
9+
- The Rust layer also attaches a source error when possible; Python reprs show it as `caused by ...`.
10+
11+
## Python API quick start
12+
```python
13+
from codetracer_python_recorder import RecorderError, TargetError, start, stop
14+
15+
try:
16+
session = start("/tmp/trace", format="json")
17+
except RecorderError as err:
18+
print(f"Recorder failed: {err.code}")
19+
for key, value in err.context.items():
20+
print(f" {key}: {value}")
21+
else:
22+
try:
23+
... # run traced work here
24+
finally:
25+
session.flush()
26+
stop()
27+
```
28+
- Catch `RecorderError` when you want a single guard. Catch subclasses when you care about `UsageError` vs `TargetError`.
29+
- Calling `start` twice raises `RuntimeError` from a thin Python guard. Everything after the guard uses `RecorderError`.
30+
31+
## CLI workflow and JSON trailers
32+
- Run `python -m codetracer_python_recorder --codetracer-format=json app.py` to trace a script.
33+
- Exit codes: `0` for success, script exit code when the script stops itself, `1` when a `RecorderError` escapes startup/shutdown, `2` on CLI misuse.
34+
- Pass `--codetracer-json-errors` (or `configure_policy(json_errors=True)`) to mirror each failure as a one-line JSON object on stderr.
35+
- JSON fields: `run_id`, optional `trace_id`, `error_code`, `error_kind`, `message`, `context`.
36+
37+
## Migration checklist for existing clients
38+
1. Replace `RuntimeError` / string matching with `RecorderError` + `err.code` checks.
39+
2. Forward policy options through `configure_policy` (or `policy_snapshot`) instead of reinventing env parsing.
40+
3. Expect structured log lines on stderr. Parse JSON and read the `error_code` field.
41+
4. Opt in to JSON trailers when you need machine-readable failure signals.
42+
5. Keep CLI wrappers short. Avoid reformatting the recorder message; attach extra context alongside it.
43+
44+
## Assertion rules for recorder code
45+
- Use `ensure_usage!`, `ensure_env!`, or `ensure_internal!` when translating invariants into classified failures.
46+
- Reach for `bug!` when you hit a state that should never happen in production.
47+
- Reserve `assert!` and `debug_assert!` for tests or temporary invariants. If you need a dev-only guard, combine `debug_assert!` with the matching `ensure_*` call so production still fails cleanly.
48+
- Never reintroduce `.unwrap()` inside the recorder crate without extending the allowlist. Use the macros instead.
49+
50+
## Tooling guardrails
51+
- Run `just lint` before sending a patch. It runs Clippy with `-D clippy::panic` and our unwrap scanner.
52+
- Run `just test` to exercise Rust (nextest) and Python suites. Failure injections cover permission errors, target crashes, and panic paths.
53+
- Enable the `integration-test` cargo feature when you add new Python surface tests so the Rust hooks are active.
54+
- When in doubt, add a regression test alongside the docs. The plan treats docs plus tests as the definition of done.
55+
56+
## Need help?
57+
- Check `design-docs/error-handling-implementation-plan.md` for context and open questions.
58+
- Ping the error-handling working thread if a new code or policy toggle seems missing. The goal is to keep `RecorderError` exhaustive, not to fork ad hoc enums in downstream tools.

0 commit comments

Comments
 (0)