python-traceprint 0.1.2__tar.gz → 0.1.3__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.
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.gitignore +0 -1
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/implementation/deliverables.yaml +5 -5
- python_traceprint-0.1.3/.macrostates/implementation/release.yaml +4 -0
- python_traceprint-0.1.3/.macrostates/specs/900_traceprint/001_project.md +65 -0
- python_traceprint-0.1.3/.macrostates/specs/900_traceprint/002_api.md +180 -0
- python_traceprint-0.1.3/.macrostates/specs/900_traceprint/003_formatting.md +109 -0
- python_traceprint-0.1.3/.macrostates/specs/900_traceprint/004_interception.md +115 -0
- python_traceprint-0.1.3/.macrostates/specs/900_traceprint/005_contexts.md +126 -0
- python_traceprint-0.1.3/.macrostates/specs/900_traceprint/006_logging.md +115 -0
- python_traceprint-0.1.3/.macrostates/specs/900_traceprint/007_release.md +184 -0
- python_traceprint-0.1.3/.macrostates/specs/900_traceprint/008_acceptance.md +224 -0
- python_traceprint-0.1.3/.macrostates/specs/900_traceprint/README.md +73 -0
- python_traceprint-0.1.3/.macrostates/specs/900_traceprint/annex_observed-compatibility.md +114 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/900_traceprint/package.yaml +2 -2
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/composition.yaml +2 -2
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/main.md +8 -5
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/CHANGELOG.md +11 -1
- python_traceprint-0.1.3/CONTRIBUTING.md +84 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/PKG-INFO +8 -3
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/README.md +7 -2
- python_traceprint-0.1.3/docs/releasing.rst +69 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/docs/usage.rst +4 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/pyproject.toml +2 -0
- python_traceprint-0.1.2/.macrostates/implementation/release.yaml +0 -4
- python_traceprint-0.1.2/.macrostates/specs/900_traceprint/README.md +0 -32
- python_traceprint-0.1.2/.macrostates/specs/900_traceprint/api.md +0 -29
- python_traceprint-0.1.2/.macrostates/specs/900_traceprint/interception.md +0 -24
- python_traceprint-0.1.2/.macrostates/specs/900_traceprint/project.md +0 -24
- python_traceprint-0.1.2/.macrostates/specs/900_traceprint/release.md +0 -76
- python_traceprint-0.1.2/CONTRIBUTING.md +0 -50
- python_traceprint-0.1.2/docs/releasing.rst +0 -56
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/001_concepts.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/002_project-composition.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/003_progressive-disclosure.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/LICENSE +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/README.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/annex_cli.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/annex_official-packages.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/annex_package-lock.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/annex_project-kick-off.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/package.yaml +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/000_meta/resources/project-kickoff.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/001_concepts.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/002_artifacts.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/003_execution.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/LICENSE +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/README.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/annex_conflicts.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/annex_exceptions.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/annex_project-state.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/annex_versioning.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/001_process/package.yaml +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/010_repository-1/001_conventions.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/010_repository-1/002_contents.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/010_repository-1/003_documentation.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/010_repository-1/004_technology.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/010_repository-1/LICENSE +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/010_repository-1/README.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/010_repository-1/package.yaml +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/020_python-1/001_python.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/020_python-1/LICENSE +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/020_python-1/README.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/020_python-1/package.yaml +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/001_project-layout.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/002_dependency-management.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/003_library-api.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/004_formatting.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/005_linting.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/006_type-checking.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/007_testing.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/008_packaging.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/009_documentation.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/010_examples.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/LICENSE +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/README.md +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/031_python-library-1/package.yaml +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/specs/composition.lock.yaml +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/LICENSE +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/docs/api.rst +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/docs/conf.py +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/docs/index.rst +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/examples/main_loop.py +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/examples/sklearn_style_transformer.py +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/hatch_build.py +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/src/traceprint/__init__.py +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/src/traceprint/colors.py +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/src/traceprint/py.typed +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/src/traceprint/tracer.py +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/tests/integration/test_logging.py +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/tests/integration/test_release.py +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/tests/packaging/check_release.py +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/tests/packaging/check_wheel.py +0 -0
- {python_traceprint-0.1.2 → python_traceprint-0.1.3}/tests/unit/test_tracer.py +0 -0
{python_traceprint-0.1.2 → python_traceprint-0.1.3}/.macrostates/implementation/deliverables.yaml
RENAMED
|
@@ -3,10 +3,10 @@ deliverables:
|
|
|
3
3
|
traceprint:
|
|
4
4
|
strategy: independent
|
|
5
5
|
version_scheme: pep440
|
|
6
|
-
version: 0.1.
|
|
6
|
+
version: 0.1.3
|
|
7
7
|
status: released
|
|
8
|
-
specification: specs-1.
|
|
9
|
-
implementation: imp-1.
|
|
10
|
-
release_date: '2026-10-
|
|
8
|
+
specification: specs-1.1.0
|
|
9
|
+
implementation: imp-1.1.0
|
|
10
|
+
release_date: '2026-10-25'
|
|
11
11
|
source:
|
|
12
|
-
tag: v0.1.
|
|
12
|
+
tag: v0.1.3
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Project
|
|
2
|
+
|
|
3
|
+
Traceprint exists to make ad hoc development output easier to follow without
|
|
4
|
+
requiring a full observability stack.
|
|
5
|
+
|
|
6
|
+
## Responsibilities
|
|
7
|
+
|
|
8
|
+
Traceprint should provide:
|
|
9
|
+
|
|
10
|
+
- formatted text output with timestamp, elapsed time, cumulative time, labels,
|
|
11
|
+
and nesting depth;
|
|
12
|
+
- optional ANSI colors for terminal use;
|
|
13
|
+
- manual spans for nested operations;
|
|
14
|
+
- a decorator for function-level tracing;
|
|
15
|
+
- Python logging handlers that route logging records through tracer formatting.
|
|
16
|
+
|
|
17
|
+
## Non-goals
|
|
18
|
+
|
|
19
|
+
Traceprint is not a replacement for structured logging, distributed tracing,
|
|
20
|
+
metrics, OpenTelemetry, or production observability infrastructure.
|
|
21
|
+
|
|
22
|
+
Traceprint should not include unrelated project, pipeline, placeholder, or
|
|
23
|
+
filesystem utility functions.
|
|
24
|
+
|
|
25
|
+
## Identity and supported use
|
|
26
|
+
|
|
27
|
+
- Distribution: `python-traceprint`; import package: `traceprint`; license: MIT.
|
|
28
|
+
- Python 3.12 or later; no runtime dependencies outside the Python standard library.
|
|
29
|
+
- Public API maturity: alpha. Repository specification versions and library
|
|
30
|
+
versions are independent; a specification 1.x does not declare the library stable.
|
|
31
|
+
- Supported use is ordinary text streams, development scripts, short-lived CLIs
|
|
32
|
+
and Python logging. Callers coordinate shared state and own stream/file cleanup.
|
|
33
|
+
|
|
34
|
+
The library is a synchronous formatter and a partial text-stream adapter. It is
|
|
35
|
+
not a general `TextIOBase` implementation: only `write`, `flush` and the explicitly
|
|
36
|
+
specified tracing operations are required. Do not infer `encoding`, `fileno`,
|
|
37
|
+
`isatty`, binary writes or a `close` operation on `Tracer` from stream interception.
|
|
38
|
+
TTY detection is delegated to the underlying sink during construction/configuration.
|
|
39
|
+
|
|
40
|
+
## Product structure
|
|
41
|
+
|
|
42
|
+
The importable package lives under `src/traceprint/`. Its root exports the API
|
|
43
|
+
in [API](002_api.md), `colors.py` supplies color constants/conversion, `tracer.py`
|
|
44
|
+
supplies tracer/context/logging behavior, and an empty `py.typed` marks typing
|
|
45
|
+
support. Private implementation organization may vary while these supported
|
|
46
|
+
interfaces and outputs remain equivalent.
|
|
47
|
+
|
|
48
|
+
The repository also provides tests, two runnable examples, `hatch_build.py`,
|
|
49
|
+
`pyproject.toml`, MIT licensing, README/contribution/changelog material and Sphinx
|
|
50
|
+
API/usage/release documentation. The latter is a product maintained according to
|
|
51
|
+
this package and the Python-library documentation rules. Build and release
|
|
52
|
+
requirements are owned by [Library versions and releases](007_release.md).
|
|
53
|
+
|
|
54
|
+
## Shared-state boundaries
|
|
55
|
+
|
|
56
|
+
A tracer owns its label stack, depth, timing, enabled/locked flags, sink,
|
|
57
|
+
interception captures, attached mirrors and current line state. None of these
|
|
58
|
+
is thread-local, task-local or isolated by `contextvars`. Intercepted stdout and
|
|
59
|
+
stderr share one sink and one context/line state.
|
|
60
|
+
|
|
61
|
+
Do not promise concurrent-context isolation, asynchronous coroutine/generator
|
|
62
|
+
execution tracing, overlapping tracer installation management or transactional
|
|
63
|
+
configuration. Keep enable/lock settings stable while a context is active and
|
|
64
|
+
balance manual push/pop operations. Unsupported mutation observations belong to
|
|
65
|
+
the [compatibility annex](annex_observed-compatibility.md).
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# API
|
|
2
|
+
|
|
3
|
+
Traceprint's public API is small and explicit. Signatures below specify argument
|
|
4
|
+
positions, keyword-only parameters and defaults; ordinary Python exceptions are
|
|
5
|
+
used rather than a separate error type. Values must satisfy the annotated text,
|
|
6
|
+
stream and logging contracts; constructors do not perform general type validation.
|
|
7
|
+
|
|
8
|
+
## Public imports
|
|
9
|
+
|
|
10
|
+
The root exports exactly these names through `__all__`, in this order:
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
DARK_GREY, DEFAULT_COLORS, RESET, LoggerMixin, Tracer,
|
|
14
|
+
TracerFileHandler, TracerHandler, trace, tracer
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The color values are owned by [Formatting](003_formatting.md#color-values).
|
|
18
|
+
`Span` and the logger proxy are returned objects, not package-root exports.
|
|
19
|
+
There is no required `traceprint.__version__`; distribution version lookup uses
|
|
20
|
+
`importlib.metadata.version("python-traceprint")`.
|
|
21
|
+
|
|
22
|
+
## Tracer construction
|
|
23
|
+
|
|
24
|
+
All constructor arguments are keyword-only:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
Tracer(
|
|
28
|
+
*,
|
|
29
|
+
use_colors: bool | None = None,
|
|
30
|
+
use_tabulation: bool = False,
|
|
31
|
+
color_scheme: list[str] | None = None,
|
|
32
|
+
metacolor: str | None = DARK_GREY,
|
|
33
|
+
print_transitions: bool = False,
|
|
34
|
+
filter_levels: range | Iterable[int] | Callable[[int], bool] | None = None,
|
|
35
|
+
start_enabled: bool = True,
|
|
36
|
+
output_format: str = DEFAULT_OUTPUT_FORMAT,
|
|
37
|
+
timestamp_format: str = "%Y-%m-%d %H:%M:%S",
|
|
38
|
+
stream: TextIO | None = None,
|
|
39
|
+
error_stream: TextIO | None = None,
|
|
40
|
+
intercept_stdout: bool = False,
|
|
41
|
+
intercept_stderr: bool = False,
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`DEFAULT_OUTPUT_FORMAT` denotes the exact literal specified in
|
|
46
|
+
[Formatting](003_formatting.md#format-fields), not a new root export.
|
|
47
|
+
|
|
48
|
+
| Option | Meaning |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `use_colors` | `None` detects the sink's TTY status; explicit booleans select color output. |
|
|
51
|
+
| `use_tabulation` | Enable four spaces per depth, with placement independent of color output. |
|
|
52
|
+
| `color_scheme` | Ordered ANSI foreground strings; `None` or an empty list selects `DEFAULT_COLORS`. |
|
|
53
|
+
| `metacolor` | Color metadata separately; a falsey value selects whole-line depth coloring. |
|
|
54
|
+
| `print_transitions` | Emit the context entry/exit messages specified in [Contexts](005_contexts.md). |
|
|
55
|
+
| `filter_levels` | Allowed depths or a predicate returning truthy for allowed depths. |
|
|
56
|
+
| `start_enabled` | Initial formatting/depth-tracking state. |
|
|
57
|
+
| `output_format` | Python format string containing the supported fields. |
|
|
58
|
+
| `timestamp_format` | UTC `strftime` format. |
|
|
59
|
+
| `stream` | Output sink, defaulting to current stdout; preserve whether it was explicitly supplied. |
|
|
60
|
+
| `error_stream` | Accepted compatibility parameter; it does not create a separate stderr output route. |
|
|
61
|
+
| `intercept_stdout`, `intercept_stderr` | Explicit construction-time installation requests, both false by default. |
|
|
62
|
+
|
|
63
|
+
Construction initializes `depth = 0`, `labels = []`, an unlocked state, no active
|
|
64
|
+
interception captures, no attached file mirrors and a fresh line state. Read the
|
|
65
|
+
performance clock once and use that value as both the start and last-format time.
|
|
66
|
+
Resolve colors before optional installation; installation does not redetect TTY
|
|
67
|
+
status or reset timing, labels or line state.
|
|
68
|
+
|
|
69
|
+
Use the supplied stream when present in ordinary use, otherwise current stdout.
|
|
70
|
+
The alpha's falsey-stream selection nuance is documented in the
|
|
71
|
+
[compatibility annex](annex_observed-compatibility.md#stream-and-installation-edges).
|
|
72
|
+
A supplied nonempty palette and the default palette are shared by reference,
|
|
73
|
+
not copied. Keep a palette nonempty after selection.
|
|
74
|
+
|
|
75
|
+
## Tracer state and operations
|
|
76
|
+
|
|
77
|
+
Readable/mutable public configuration attributes are `use_colors`,
|
|
78
|
+
`use_tabulation`, `color_scheme`, `metacolor`, `print_transitions`, `filter_levels`,
|
|
79
|
+
`output_format` and `timestamp_format`. `depth` and `labels` expose current context;
|
|
80
|
+
prefer balanced context operations to direct mutation. `enabled` is read-only.
|
|
81
|
+
Depth and labels are separate mutable values; assigning one does not automatically
|
|
82
|
+
reconcile the other. Configuration changes do not reset clocks or line state.
|
|
83
|
+
|
|
84
|
+
| Signature | Result and owner |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `enabled -> bool` | Current enabled flag. |
|
|
87
|
+
| `install(*, stdout: bool = True, stderr: bool = False) -> None` | Explicit stream capture/replacement; [Interception](004_interception.md#installation). |
|
|
88
|
+
| `uninstall() -> None` | Restore captured streams and clear installation flags. |
|
|
89
|
+
| `enable() -> None`, `disable() -> None` | Set formatting/depth-tracking state without resetting context or clocks. |
|
|
90
|
+
| `lock() -> None`, `unlock() -> None` | Set depth-mutation lock without changing existing context. |
|
|
91
|
+
| `configure(**kwargs) -> None` | Ordered configuration updates described below. |
|
|
92
|
+
| `format(message: str) -> str` | Format without output; advances timing; [Formatting](003_formatting.md). |
|
|
93
|
+
| `write(data: str) -> int` | Immediate fragment output; returns input length; [Interception](004_interception.md#writing-fragments). |
|
|
94
|
+
| `flush() -> None` | Flush the sink and each mirror without ending a line. |
|
|
95
|
+
| `push(label: str | None = None) -> None` | Manual depth entry; [Contexts](005_contexts.md#manual-push-and-pop). |
|
|
96
|
+
| `pop(label: str | None = None) -> None` | Manual depth exit, safe at depth zero. |
|
|
97
|
+
| `span(label: str) -> Span` | Return a synchronous context manager; [Contexts](005_contexts.md#spans). |
|
|
98
|
+
| `add_file_handler(handler: TracerFileHandler) -> None` | Append a mirror in attachment order. |
|
|
99
|
+
| `remove_file_handler(handler: TracerFileHandler) -> None` | Remove its first matching attachment; missing attachment is a no-op. |
|
|
100
|
+
|
|
101
|
+
There is no return-value aggregation from sink/mirror writes and no implicit
|
|
102
|
+
closing of a sink or attached handlers. See [Logging](006_logging.md) for handler
|
|
103
|
+
ownership and failure behavior.
|
|
104
|
+
|
|
105
|
+
## Configuration order
|
|
106
|
+
|
|
107
|
+
Apply one `configure` call in this order:
|
|
108
|
+
|
|
109
|
+
1. If either interception key is supplied, derive both desired flags. Missing
|
|
110
|
+
flags retain their current installed state; supplied values use `bool(value)`.
|
|
111
|
+
Uninstall the current captures, then install the derived combination.
|
|
112
|
+
2. If `use_colors` is supplied, `None` redetects the current sink's TTY status;
|
|
113
|
+
any other value uses `bool(value)`.
|
|
114
|
+
3. If `color_scheme` is supplied, select the provided sequence or default palette
|
|
115
|
+
using its truthiness.
|
|
116
|
+
4. Assign remaining keys in caller order when the tracer already has that
|
|
117
|
+
attribute. Otherwise raise `AttributeError("Tracer has no attribute NAME")`.
|
|
118
|
+
|
|
119
|
+
This is not a constructor replay. Names such as `stream`, `error_stream` and
|
|
120
|
+
`start_enabled` are not public configuration attributes; use lifecycle methods
|
|
121
|
+
for enabled state and construction for a sink. A read-only attribute assignment
|
|
122
|
+
raises normal `AttributeError`. Earlier updates are retained if a later one
|
|
123
|
+
fails. The permissive alpha handling of private/method attributes is an
|
|
124
|
+
[observation](annex_observed-compatibility.md#configuration-and-mutable-state),
|
|
125
|
+
not an additional supported configuration surface.
|
|
126
|
+
|
|
127
|
+
## Global tracer
|
|
128
|
+
|
|
129
|
+
Create one global convenience `tracer` when the runtime module first imports,
|
|
130
|
+
using ordinary constructor defaults. Root imports refer to this same object.
|
|
131
|
+
Import alone must not replace stdout/stderr, open logging files, attach root
|
|
132
|
+
logging handlers or configure the root logger. Environment labels are the only
|
|
133
|
+
specified import-time configuration beyond constructor defaults.
|
|
134
|
+
|
|
135
|
+
## Environment variables
|
|
136
|
+
|
|
137
|
+
Read `TRACEPRINT_INITIAL_LABELS` once at import time, defaulting to the empty
|
|
138
|
+
string. Strip the complete value; split it on `/`; strip each component and
|
|
139
|
+
discard empty components. Push each remaining component in order on the global
|
|
140
|
+
tracer. This sets both labels and depth; it does not install the tracer.
|
|
141
|
+
|
|
142
|
+
For example, `" / job // load / "` produces labels `["job", "load"]` and depth 2.
|
|
143
|
+
Explicit new tracer instances do not read this variable. Later environment edits
|
|
144
|
+
do not mutate the existing global instance. There are no other Traceprint-owned
|
|
145
|
+
environment variables.
|
|
146
|
+
|
|
147
|
+
## Context and logging interfaces
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
trace(_func: Callable[..., Any] | None = None, *,
|
|
151
|
+
label: str | None = None, force: bool = False,
|
|
152
|
+
tracer_instance: Tracer | None = None) -> Callable[..., Any]
|
|
153
|
+
|
|
154
|
+
TracerHandler(level: int = logging.NOTSET, *,
|
|
155
|
+
tracer_instance: Tracer | None = None)
|
|
156
|
+
|
|
157
|
+
TracerFileHandler(filename: str | os.PathLike[str], mode: str = "a",
|
|
158
|
+
level: int = logging.NOTSET, encoding: str = "utf-8", *,
|
|
159
|
+
tracer_instance: Tracer | None = None)
|
|
160
|
+
|
|
161
|
+
LoggerMixin(*, verbose: bool = True, logging_level: int = logging.INFO,
|
|
162
|
+
logging_passthrough: bool = False, lazy_init: bool = False)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`trace` supports bare and parameterized decorator forms. Its target selection,
|
|
166
|
+
transitions, result/exception behavior and `force` boundary are owned by
|
|
167
|
+
[Contexts](005_contexts.md#function-decorator).
|
|
168
|
+
|
|
169
|
+
Both handlers subclass `logging.Handler` and support standard levels, formatters,
|
|
170
|
+
filters and `handle` behavior. They implement `emit(record: logging.LogRecord)
|
|
171
|
+
returning `None`. File handlers additionally expose `write(data: str) -> None`,
|
|
172
|
+
`flush() -> None` and `close() -> None`, and the filename/mode/encoding/tracer/stream
|
|
173
|
+
attributes described in [Logging](006_logging.md#file-handler).
|
|
174
|
+
|
|
175
|
+
`LoggerMixin.logger` returns a standard logger; `LoggerMixin.log` returns the same
|
|
176
|
+
instance's verbosity-gated proxy. The proxy supports `log(level, msg, *args,
|
|
177
|
+
**kwargs)`, `debug`, `info`, `warning`, `error` and `critical`, all returning `None`.
|
|
178
|
+
No proxy `exception` or `warn` method is required. Use `.logger` for the full
|
|
179
|
+
standard logging interface. Initialization and shared-logger effects are owned
|
|
180
|
+
by [Logging](006_logging.md#logger-mixin).
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Formatting and colors
|
|
2
|
+
|
|
3
|
+
## Format fields
|
|
4
|
+
|
|
5
|
+
The exact default output format is:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
[{timestamp}][{cumulative}][{elapsed}][{label_full}] {original_message}
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Use Python string formatting with these named values, all strings:
|
|
12
|
+
|
|
13
|
+
| Field | Value |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `timestamp` | Current aware UTC datetime formatted with `timestamp_format`. |
|
|
16
|
+
| `cumulative` | Performance-clock seconds since construction, `f"{seconds:.3f}s"`. |
|
|
17
|
+
| `elapsed` | Seconds since the previous `format` call, `f"+{seconds:.3f}s"`. |
|
|
18
|
+
| `label` | The final label, or empty; append `/>` for regular labeled messages. |
|
|
19
|
+
| `label_full` | The slash-joined label stack, or empty; same regular-message suffix. |
|
|
20
|
+
| `original_message` | The input string without further stripping or interpolation. |
|
|
21
|
+
|
|
22
|
+
At each `format` call, read the performance clock, compute elapsed from the last
|
|
23
|
+
format time and cumulative from the start time, then set last-format time to the
|
|
24
|
+
new value. Obtain the UTC timestamp separately. Formatting advances timing even
|
|
25
|
+
when called directly, when the tracer is disabled, or when a later formatting
|
|
26
|
+
operation raises. Continuation fragments, blank writes, filtered writes and
|
|
27
|
+
flushes do not call `format` and do not advance timing.
|
|
28
|
+
|
|
29
|
+
Clocks measure tracer age and gaps between formatted messages, not exclusive
|
|
30
|
+
span duration. Timing text has three fractional digits and an `s` suffix, with
|
|
31
|
+
an explicit `+` before elapsed text. The default timestamp uses
|
|
32
|
+
`%Y-%m-%d %H:%M:%S`; `%z` yields `+0000`.
|
|
33
|
+
|
|
34
|
+
Compute the raw full label by joining labels with `/` and the short label from
|
|
35
|
+
the final entry. If the full label is nonempty and the message does not start
|
|
36
|
+
with literal `>` or `<`, append `/>` to both values. Otherwise keep them raw.
|
|
37
|
+
This rule examines the first character without stripping leading whitespace;
|
|
38
|
+
it is not a special transition object. Label strings are not escaped.
|
|
39
|
+
|
|
40
|
+
Custom formats may select, omit, repeat or reorder fields. Preserve message text
|
|
41
|
+
when the format includes `original_message`; do not force it into a format that
|
|
42
|
+
omits it. Unknown fields, incompatible format syntax and invalid timestamp formats
|
|
43
|
+
use the standard formatting/datetime exception behavior; there is no silent fallback.
|
|
44
|
+
For incremental writes, metadata/custom suffixes are applied to the first
|
|
45
|
+
nonblank fragment only; see [Writes](004_interception.md#writing-fragments).
|
|
46
|
+
|
|
47
|
+
## Color values
|
|
48
|
+
|
|
49
|
+
`RESET` is `"\x1b[0m"`; `DARK_GREY` is `"\x1b[90m"`. `DEFAULT_COLORS` is an
|
|
50
|
+
ordered mutable list of truecolor foreground sequences generated from:
|
|
51
|
+
|
|
52
|
+
| Depth modulo 8 | RGB hex | Red, green, blue |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| 0 | `#52C97F` | 82, 201, 127 |
|
|
55
|
+
| 1 | `#68BCCD` | 104, 188, 205 |
|
|
56
|
+
| 2 | `#7D7FD2` | 125, 127, 210 |
|
|
57
|
+
| 3 | `#C891D8` | 200, 145, 216 |
|
|
58
|
+
| 4 | `#DDA5BE` | 221, 165, 190 |
|
|
59
|
+
| 5 | `#E4C9B9` | 228, 201, 185 |
|
|
60
|
+
| 6 | `#E5EACC` | 229, 234, 204 |
|
|
61
|
+
| 7 | `#E0F2DF` | 224, 242, 223 |
|
|
62
|
+
|
|
63
|
+
The sequence for channels `r`, `g`, `b` is exactly
|
|
64
|
+
`"\x1b[38;2;{r};{g};{b}m"`, with decimal channel values. Select the current
|
|
65
|
+
line color at index `depth % len(color_scheme)`; custom palettes cycle by their
|
|
66
|
+
own length. No terminal capability negotiation beyond TTY color detection is
|
|
67
|
+
required. A caller can explicitly request ANSI output to a non-TTY sink.
|
|
68
|
+
|
|
69
|
+
The supporting module `traceprint.colors` also exposes
|
|
70
|
+
`hex_to_ansi(hexstr: str) -> str`, although it is not a root export. For normal
|
|
71
|
+
hex input, remove a single leading `#`, require six remaining characters, parse
|
|
72
|
+
each pair using base-16 integer conversion and generate the sequence above.
|
|
73
|
+
Bad length or pair-conversion failure raises
|
|
74
|
+
`ValueError(f"Expected #RRGGBB color, got {hexstr!r}")`; conversion failure chains
|
|
75
|
+
the original `ValueError`. No additional clamping is performed. Malformed inputs
|
|
76
|
+
accepted by Python's integer parser are discussed in the
|
|
77
|
+
[compatibility annex](annex_observed-compatibility.md#format-and-color-edges).
|
|
78
|
+
|
|
79
|
+
## Color and indentation composition
|
|
80
|
+
|
|
81
|
+
After formatting fields into `text`, compute `indent` as four literal spaces
|
|
82
|
+
times depth when tabulation is enabled, otherwise the empty string. Indentation
|
|
83
|
+
must have the same placement whether colors are enabled or disabled.
|
|
84
|
+
|
|
85
|
+
1. If `metacolor` is falsey, prepend `indent` to the entire `text`. When colors
|
|
86
|
+
are enabled, surround that complete result with the selected line color and
|
|
87
|
+
`RESET`; otherwise return it directly.
|
|
88
|
+
2. If `metacolor` is truthy, find the first occurrence of the input message in
|
|
89
|
+
the formatted `text`. Split into `meta_part` before that occurrence and
|
|
90
|
+
`message_part` from that occurrence through the end. With colors disabled,
|
|
91
|
+
return `meta_part + indent + message_part`. With colors enabled, return:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
metacolor + meta_part + RESET + line_color + indent + message_part + RESET
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
3. If the message does not occur in `text`, prepend `indent` to the whole text.
|
|
98
|
+
With colors enabled use `metacolor + indent + text + RESET`; with colors
|
|
99
|
+
disabled use `indent + text`.
|
|
100
|
+
|
|
101
|
+
This describes string concatenation, not literal names in emitted output. Do not
|
|
102
|
+
strip caller-supplied ANSI from console output. An empty `meta_part` still has
|
|
103
|
+
its metadata color/reset framing when colors are enabled. `format` adds no
|
|
104
|
+
newline of its own and does not update the fragment line state.
|
|
105
|
+
|
|
106
|
+
The first-substring rule is a compatibility detail of the existing formatter;
|
|
107
|
+
it is not a promise of placeholder-aware parsing. See the
|
|
108
|
+
[compatibility annex](annex_observed-compatibility.md#format-and-color-edges)
|
|
109
|
+
when a custom format repeats a message or includes it in metadata.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Interception
|
|
2
|
+
|
|
3
|
+
Traceprint may replace process streams only when the caller explicitly requests
|
|
4
|
+
that behavior. The same object also works as `print(..., file=target)` or through
|
|
5
|
+
direct `write` calls without installation. Writes have immediate effects; no
|
|
6
|
+
complete-line buffering is permitted merely to join `print` arguments.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
`install(*, stdout=True, stderr=False)` handles stdout first, then stderr:
|
|
11
|
+
|
|
12
|
+
- For a requested stream not already intercepted by this tracer, capture its
|
|
13
|
+
current process object and replace it with the tracer. Mark that stream installed.
|
|
14
|
+
- If stdout is newly installed and no explicit sink was supplied at construction,
|
|
15
|
+
choose the captured stdout as the sink.
|
|
16
|
+
- If stderr is newly installed, this call's `stdout` argument is false and no
|
|
17
|
+
explicit sink was supplied, choose the captured stderr as the sink.
|
|
18
|
+
- Otherwise retain the sink. An explicit constructor sink wins in every case.
|
|
19
|
+
|
|
20
|
+
This makes repeated requests for an already-installed stream idempotent without
|
|
21
|
+
recapturing the tracer itself. A false argument does not uninstall a previously
|
|
22
|
+
installed stream; use `uninstall` or `configure` to remove interception. With
|
|
23
|
+
both streams requested in the same call, both feed the selected stdout sink.
|
|
24
|
+
There is no separate routing through `error_stream`.
|
|
25
|
+
|
|
26
|
+
`uninstall()` restores each captured stdout/stderr object for which this tracer
|
|
27
|
+
has an active installation flag, then clears both flags and captures. Repeating
|
|
28
|
+
uninstall is a no-op. It does not close/reset the sink, clocks, labels, line state
|
|
29
|
+
or attached file handlers. Install/uninstall do not enable a disabled tracer.
|
|
30
|
+
|
|
31
|
+
Callers should use `try/finally` to restore streams. Normal single-tracer cleanup
|
|
32
|
+
must restore object identity, not just an equivalent stream. Overlapping tracer
|
|
33
|
+
ownership and external stream replacements are not automatically coordinated;
|
|
34
|
+
see [compatibility](annex_observed-compatibility.md#stream-and-installation-edges).
|
|
35
|
+
|
|
36
|
+
## Depth filtering
|
|
37
|
+
|
|
38
|
+
At the point a write chunk or transition is considered, determine whether the
|
|
39
|
+
current depth is allowed:
|
|
40
|
+
|
|
41
|
+
- `None`: allow every depth.
|
|
42
|
+
- `range`: allow exactly its members.
|
|
43
|
+
- Callable: call it with the current depth and allow a truthy result.
|
|
44
|
+
- Other iterable: allow when membership contains the current depth.
|
|
45
|
+
|
|
46
|
+
Suppressing output does not suppress context mutation. A disallowed chunk must
|
|
47
|
+
not reach either the sink or mirrors, call `format`, advance timing or alter
|
|
48
|
+
visible line state. Evaluate the write filter once per chunk, including whitespace
|
|
49
|
+
chunks. Context transition operations may also perform their own precheck.
|
|
50
|
+
Exceptions from predicates/membership propagate during direct tracer operations.
|
|
51
|
+
Iterable membership has ordinary Python semantics, including consumption of
|
|
52
|
+
one-shot iterators; prefer a reusable collection for stable membership.
|
|
53
|
+
|
|
54
|
+
## Writing fragments
|
|
55
|
+
|
|
56
|
+
`write(data: str)` returns `len(data)` on a successful call, even when all output
|
|
57
|
+
is filtered. It does not return the length of metadata or the underlying sink's
|
|
58
|
+
write result. Preserve message whitespace and original CR/LF endings.
|
|
59
|
+
|
|
60
|
+
When disabled, write `data` directly to the sink, bypassing formatting, filtering
|
|
61
|
+
and mirrors. For a nonempty input, set line state according to whether it ends
|
|
62
|
+
with CR or LF: an ending resets it; otherwise mark an existing line. An empty
|
|
63
|
+
disabled write still calls the sink but leaves line state unchanged.
|
|
64
|
+
|
|
65
|
+
When enabled:
|
|
66
|
+
|
|
67
|
+
1. Split `data` using `str.splitlines(keepends=True)`. An empty input creates no
|
|
68
|
+
chunks and causes no sink write.
|
|
69
|
+
2. For each allowed chunk, remove only trailing CR/LF characters into an `ending`
|
|
70
|
+
suffix, leaving `message`. Do not strip other characters from the emitted text.
|
|
71
|
+
3. If `message.strip()` is nonempty and no line has started, call `format(message)`.
|
|
72
|
+
If a line has started, emit the original message unchanged, or surround it
|
|
73
|
+
with the current depth color and `RESET` when colors are enabled. Do not
|
|
74
|
+
repeat metadata or indentation on a continuation.
|
|
75
|
+
4. Write that result plus its ending, then mark the line started. For a whitespace-
|
|
76
|
+
only chunk, write the original chunk without formatting or starting a new line.
|
|
77
|
+
5. Any nonempty CR/LF ending resets line state after that write.
|
|
78
|
+
|
|
79
|
+
Every enabled write reaches the sink first, followed by all mirrors in attachment
|
|
80
|
+
order with the same already-formatted text. Do not catch sink/mirror exceptions
|
|
81
|
+
in the tracer, automatically retry, or roll back effects already written. Line
|
|
82
|
+
state changes after the corresponding successful write operation.
|
|
83
|
+
|
|
84
|
+
Whitespace fragments are immediate too. Leading whitespace supplied separately
|
|
85
|
+
can precede the first metadata prefix; whitespace within a nonblank fragment
|
|
86
|
+
remains part of that formatted message. Blank lines have no metadata. Unicode
|
|
87
|
+
line separators recognized by `splitlines` but not CR/LF remain in their message
|
|
88
|
+
or whitespace chunk and do not reset this CR/LF line state. These unusual cases
|
|
89
|
+
are further illustrated in the [compatibility annex](annex_observed-compatibility.md).
|
|
90
|
+
|
|
91
|
+
Labels, timing and indentation belong to the first nonblank fragment's metadata.
|
|
92
|
+
Later context/configuration changes do not cause a second prefix before a newline.
|
|
93
|
+
Continuation coloring uses the current palette/depth at that write. A custom
|
|
94
|
+
format suffix follows the first fragment, not the eventual complete line.
|
|
95
|
+
|
|
96
|
+
## Flush and mirroring lifecycle
|
|
97
|
+
|
|
98
|
+
`flush()` flushes the sink, then every attached mirror in attachment order,
|
|
99
|
+
regardless of enabled state. It does not output pending text, add a newline,
|
|
100
|
+
reset line state or advance clocks. Exceptions propagate; later mirrors are not
|
|
101
|
+
flushed after an earlier failure.
|
|
102
|
+
|
|
103
|
+
Attaching a handler appends it without deduplication or opening/closing it.
|
|
104
|
+
Removing a handler removes the first equal attachment if present and otherwise
|
|
105
|
+
does nothing. Attaching the same handler twice yields two mirror writes/flushes;
|
|
106
|
+
ordinary use should attach once. The caller removes and closes owned handlers.
|
|
107
|
+
Handler behavior and ANSI stripping are owned by [Logging](006_logging.md).
|
|
108
|
+
|
|
109
|
+
## Formatting
|
|
110
|
+
|
|
111
|
+
Formatted output preserves message text when the configured format contains its
|
|
112
|
+
placeholder. Colors are optional and auto-disabled for non-TTY sinks. Exact
|
|
113
|
+
format fields, colors and indentation rules are in [Formatting](003_formatting.md).
|
|
114
|
+
File output strips terminal escape sequences as specified in [Logging](006_logging.md);
|
|
115
|
+
there is no current option to preserve ANSI in files.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Contexts and decorators
|
|
2
|
+
|
|
3
|
+
## Common context rules
|
|
4
|
+
|
|
5
|
+
Depth starts at zero and labels form a stack in outer-to-inner order. A normal
|
|
6
|
+
entry adds one depth and one label; a normal exit removes one of each. Filtering
|
|
7
|
+
affects output, not this tracking. Locked tracers retain their existing depth and
|
|
8
|
+
labels while still being able to print allowed transitions/messages.
|
|
9
|
+
|
|
10
|
+
Enable/disable and lock/unlock are setters, not counters or context managers.
|
|
11
|
+
They do not reset labels, clocks, mirrors or line state. Disabled manual operations
|
|
12
|
+
and spans do not track depth. Keep these flags stable inside active spans/decorated
|
|
13
|
+
calls and balance manual operations. Behavior outside those conditions is recorded
|
|
14
|
+
in [compatibility observations](annex_observed-compatibility.md#context-mutation-edges).
|
|
15
|
+
|
|
16
|
+
Transition strings below are supplied to `write` with a trailing newline and
|
|
17
|
+
are therefore subject to filtering, current line state and ordinary formatting.
|
|
18
|
+
Transition output is optional; no transitions are printed by default.
|
|
19
|
+
|
|
20
|
+
## Manual push and pop
|
|
21
|
+
|
|
22
|
+
`push(label=None)`:
|
|
23
|
+
|
|
24
|
+
1. If disabled, return without any effect.
|
|
25
|
+
2. If unlocked, increment depth and append the supplied truthy label, otherwise
|
|
26
|
+
append `"level" + str(new_depth)`. Thus `None` and `""` generate `levelN` labels.
|
|
27
|
+
3. If the supplied label itself is truthy, transitions are enabled and the new
|
|
28
|
+
current depth is allowed, write `"> LABEL\n"`.
|
|
29
|
+
|
|
30
|
+
Generated labels do not trigger a transition when no truthy label was supplied.
|
|
31
|
+
When locked, an explicitly labeled transition can still be written at the existing
|
|
32
|
+
depth; the label stack is unchanged.
|
|
33
|
+
|
|
34
|
+
`pop(label=None)`:
|
|
35
|
+
|
|
36
|
+
1. If disabled, return without any effect.
|
|
37
|
+
2. If unlocked and depth is above zero, decrement depth and remove the final
|
|
38
|
+
stored label. At depth zero, do not decrement or remove anything.
|
|
39
|
+
3. Select the supplied truthy label, otherwise the removed label. If that selected
|
|
40
|
+
value is truthy, transitions are enabled and the resulting depth is allowed,
|
|
41
|
+
write `"< LABEL\n"`.
|
|
42
|
+
|
|
43
|
+
An explicit pop label names the transition, not a stack entry to search/remove.
|
|
44
|
+
At depth zero or while locked, `pop("name")` may still print an exit transition.
|
|
45
|
+
With no selected label, print nothing. Manual push transitions use the entered
|
|
46
|
+
context; manual pop transitions use the parent context after removal.
|
|
47
|
+
|
|
48
|
+
## Spans
|
|
49
|
+
|
|
50
|
+
`span(label)` constructs a synchronous context manager without changing state.
|
|
51
|
+
The returned object exposes its target as `.tracer` and exact label as `.label`.
|
|
52
|
+
Entering returns that context object, so `with target.span("load") as context`
|
|
53
|
+
binds the span, not the tracer.
|
|
54
|
+
|
|
55
|
+
On entry when enabled:
|
|
56
|
+
|
|
57
|
+
1. If transitions are enabled and the parent depth is allowed, write `"> LABEL\n"`.
|
|
58
|
+
2. If unlocked, increment depth and append the exact supplied label, including an
|
|
59
|
+
empty string. Unlike manual `push`, spans do not generate a replacement label.
|
|
60
|
+
|
|
61
|
+
On exit when enabled:
|
|
62
|
+
|
|
63
|
+
1. If unlocked, decrement depth and remove the final label.
|
|
64
|
+
2. If transitions are enabled and the resulting parent depth is allowed, write
|
|
65
|
+
`"< LABEL\n"` on ordinary exit, or
|
|
66
|
+
`"< LABEL [failed: EXCEPTION_CLASS_NAME]\n"` when exiting with an exception.
|
|
67
|
+
|
|
68
|
+
Return `None` from exit; do not suppress the body's exception. On normal supported
|
|
69
|
+
use, cleanup restores the prior depth/labels even when the body raises. Disabled
|
|
70
|
+
entry/exit performs no context/transition work; locked entry/exit performs no
|
|
71
|
+
depth mutation. Spans do not install/uninstall streams or flush/close sinks.
|
|
72
|
+
|
|
73
|
+
## Function decorator
|
|
74
|
+
|
|
75
|
+
Support all these forms, preserving the function's metadata with `functools.wraps`:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
@trace
|
|
79
|
+
def work(): ...
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@trace(label="load")
|
|
83
|
+
def load(): ...
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@trace(tracer_instance=target)
|
|
87
|
+
def run(): ...
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Select the supplied tracer or global `tracer` when the decorator is created;
|
|
91
|
+
do not select a new tracer on each call. The optional first positional function
|
|
92
|
+
supports the bare form; when it is `None`, return a decorator awaiting a function.
|
|
93
|
+
A truthy explicit label wins; otherwise use `function.__name__`.
|
|
94
|
+
|
|
95
|
+
On each wrapped call:
|
|
96
|
+
|
|
97
|
+
1. If the target is disabled, directly call the function with the original
|
|
98
|
+
arguments and return its result. No transitions or context mutation occur.
|
|
99
|
+
2. Capture whether the entry depth is filtered. If transitions are enabled and
|
|
100
|
+
either entry depth is allowed or `force=True`, attempt
|
|
101
|
+
`"> LABEL()\n"` at the parent depth.
|
|
102
|
+
3. If unlocked, increment depth and append the selected label.
|
|
103
|
+
4. Call the function with unchanged positional/keyword arguments. Return its
|
|
104
|
+
result unchanged; let its exceptions propagate.
|
|
105
|
+
5. In `finally`, if unlocked, decrement depth and remove the last label. If
|
|
106
|
+
transitions are enabled and either the captured entry depth was allowed or
|
|
107
|
+
`force=True`, attempt `"< LABEL()\n"` at the resulting parent depth.
|
|
108
|
+
|
|
109
|
+
The decorator's exit has no span-style `[failed: ...]` suffix. Capture the entry
|
|
110
|
+
filter decision once for both transition attempts; each actual `write` still
|
|
111
|
+
applies the current depth filter. `force` is not a bypass of write-level filtering.
|
|
112
|
+
That alpha compatibility boundary is discussed in the
|
|
113
|
+
[annex](annex_observed-compatibility.md#transition-filtering).
|
|
114
|
+
|
|
115
|
+
The wrapper traces synchronous function invocation only. For an async function
|
|
116
|
+
or generator, it surrounds creation of the coroutine/generator object, not later
|
|
117
|
+
execution, awaits or iteration. Do not add special async/generator tracing while
|
|
118
|
+
replicating this library.
|
|
119
|
+
|
|
120
|
+
## Failure boundaries
|
|
121
|
+
|
|
122
|
+
Context tracking is not transactional with output. A transition write can fail
|
|
123
|
+
before body execution or during exit, using the direct stream failure behavior.
|
|
124
|
+
Entry/exit may therefore fail independently of the body. Normal exception cleanup
|
|
125
|
+
guarantees assume a writable sink, balanced context and stable enabled/locked state;
|
|
126
|
+
no recovery for externally corrupted stacks or flag changes is required.
|