traceact 0.1.0__py3-none-any.whl
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/__init__.py +43 -0
- traceact/budget.py +103 -0
- traceact/config.py +198 -0
- traceact/context.py +130 -0
- traceact/decorators.py +430 -0
- traceact/helpers.py +103 -0
- traceact/ids.py +62 -0
- traceact/sinks.py +177 -0
- traceact/trace.py +1176 -0
- traceact-0.1.0.dist-info/METADATA +94 -0
- traceact-0.1.0.dist-info/RECORD +14 -0
- traceact-0.1.0.dist-info/WHEEL +5 -0
- traceact-0.1.0.dist-info/licenses/LICENSE +21 -0
- traceact-0.1.0.dist-info/top_level.txt +1 -0
traceact/sinks.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# sinks.py
|
|
2
|
+
#
|
|
3
|
+
# Defines the sink system — where finished traces are written.
|
|
4
|
+
#
|
|
5
|
+
# A sink is any object that accepts a trace record (a plain Python dict) and
|
|
6
|
+
# stores or displays it. TraceAct ships two sinks for v1:
|
|
7
|
+
# JsonlSink — appends records to a .jsonl file, one JSON object per line.
|
|
8
|
+
# ConsoleSink — prints records to stdout, formatted for readability.
|
|
9
|
+
#
|
|
10
|
+
# How sink_mode is handled:
|
|
11
|
+
# The sink objects themselves are simple: they just implement write(record).
|
|
12
|
+
# The sink_mode setting (from TraceConfig) controls *when* write() is called:
|
|
13
|
+
# "blocking" — write() is called immediately when a trace finishes.
|
|
14
|
+
# "buffered" — the record is held in memory; write() is called on flush().
|
|
15
|
+
# "disabled" — write() is never called.
|
|
16
|
+
#
|
|
17
|
+
# The actual sink_mode logic lives in trace.py (_write_to_sinks). Sinks do not
|
|
18
|
+
# need to know which mode is active — they just write when asked.
|
|
19
|
+
#
|
|
20
|
+
# Future sinks (SqliteSink, HttpSink, OpenTelemetrySink) will follow the same
|
|
21
|
+
# interface: implement write(record: dict) and that is all TraceAct requires.
|
|
22
|
+
|
|
23
|
+
import atexit
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
from typing import Any, Dict, List
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# Global buffer for buffered sink mode
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
#
|
|
33
|
+
# When sink_mode="buffered", finished trace records are appended here instead
|
|
34
|
+
# of being written to sinks immediately. They are flushed either by an explicit
|
|
35
|
+
# flush_buffer() call or automatically when the Python interpreter exits.
|
|
36
|
+
#
|
|
37
|
+
# Why a module-level list?
|
|
38
|
+
# The buffer needs to be shared across all traces in the same process, regardless
|
|
39
|
+
# of which sink or configuration object is in scope at the time of the flush.
|
|
40
|
+
# A module-level list is the simplest way to achieve this.
|
|
41
|
+
|
|
42
|
+
_buffer: List[Dict[str, Any]] = []
|
|
43
|
+
_flush_registered: bool = False # True once we have registered the atexit handler
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _flush_on_exit() -> None:
|
|
47
|
+
"""
|
|
48
|
+
Called automatically by the Python interpreter on normal program exit.
|
|
49
|
+
Flushes any buffered traces to the configured sinks.
|
|
50
|
+
|
|
51
|
+
This ensures that short-lived scripts (which never call flush_buffer()
|
|
52
|
+
explicitly) still produce complete trace output.
|
|
53
|
+
"""
|
|
54
|
+
from traceact.config import get_package_sinks
|
|
55
|
+
flush_buffer(get_package_sinks())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _ensure_flush_registered() -> None:
|
|
59
|
+
"""
|
|
60
|
+
Register the atexit flush handler the first time a buffered trace is
|
|
61
|
+
recorded. We register lazily so the handler is only installed when
|
|
62
|
+
actually needed.
|
|
63
|
+
"""
|
|
64
|
+
global _flush_registered
|
|
65
|
+
if not _flush_registered:
|
|
66
|
+
atexit.register(_flush_on_exit)
|
|
67
|
+
_flush_registered = True
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def buffer_record(record: Dict[str, Any]) -> None:
|
|
71
|
+
"""
|
|
72
|
+
Add a finished trace record to the in-memory buffer.
|
|
73
|
+
Also ensures the atexit flush handler is registered.
|
|
74
|
+
"""
|
|
75
|
+
_ensure_flush_registered()
|
|
76
|
+
_buffer.append(record)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def flush_buffer(sinks: List[Any]) -> None:
|
|
80
|
+
"""
|
|
81
|
+
Write all buffered records to each sink, then clear the buffer.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
sinks: The list of sink objects to write to.
|
|
85
|
+
|
|
86
|
+
This is safe to call multiple times. After flushing, the buffer is empty.
|
|
87
|
+
Calling flush when the buffer is already empty does nothing.
|
|
88
|
+
"""
|
|
89
|
+
if not _buffer:
|
|
90
|
+
return
|
|
91
|
+
for record in _buffer:
|
|
92
|
+
for sink in sinks:
|
|
93
|
+
try:
|
|
94
|
+
sink.write(record)
|
|
95
|
+
except Exception:
|
|
96
|
+
# Sink failures never crash the application. Tracing is
|
|
97
|
+
# observability tooling; it must never become a point of
|
|
98
|
+
# failure for the code it is observing.
|
|
99
|
+
pass
|
|
100
|
+
_buffer.clear()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
# JsonlSink
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
class JsonlSink:
|
|
108
|
+
"""
|
|
109
|
+
Writes trace records to a newline-delimited JSON (JSONL) file.
|
|
110
|
+
|
|
111
|
+
Each finished trace is appended as a single line of JSON, followed by a
|
|
112
|
+
newline. This format is easy to append to, easy to read line by line, and
|
|
113
|
+
compatible with tools like jq, grep, and most log aggregators.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
path: Path to the output file. The file is created if it does not exist
|
|
117
|
+
and appended to if it does. Parent directories must exist.
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
sinks=[JsonlSink("data/traces/traces.jsonl")]
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __init__(self, path: str) -> None:
|
|
124
|
+
self.path = path
|
|
125
|
+
# Ensure the parent directory exists so writes don't fail silently.
|
|
126
|
+
parent = os.path.dirname(os.path.abspath(path))
|
|
127
|
+
os.makedirs(parent, exist_ok=True)
|
|
128
|
+
|
|
129
|
+
def write(self, record: Dict[str, Any]) -> None:
|
|
130
|
+
"""
|
|
131
|
+
Append one trace record to the JSONL file.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
record: A plain dict representing the finished trace. Must be
|
|
135
|
+
JSON-serialisable; non-serialisable values should have
|
|
136
|
+
been sanitised before reaching the sink.
|
|
137
|
+
"""
|
|
138
|
+
with open(self.path, "a", encoding="utf-8") as f:
|
|
139
|
+
f.write(json.dumps(record, default=str) + "\n")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
# ConsoleSink
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
class ConsoleSink:
|
|
147
|
+
"""
|
|
148
|
+
Prints trace records to stdout.
|
|
149
|
+
|
|
150
|
+
In pretty mode (the default), the record is printed as indented JSON so it
|
|
151
|
+
is readable in a terminal. In compact mode, it is printed as a single line
|
|
152
|
+
— useful when traces are mixed with other log output and you want each
|
|
153
|
+
record to occupy one line.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
pretty: When True (default), print with 2-space indentation. When
|
|
157
|
+
False, print as a compact single-line JSON string.
|
|
158
|
+
|
|
159
|
+
Example:
|
|
160
|
+
sinks=[ConsoleSink()] # pretty output
|
|
161
|
+
sinks=[ConsoleSink(pretty=False)] # compact output
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
def __init__(self, pretty: bool = True) -> None:
|
|
165
|
+
self.pretty = pretty
|
|
166
|
+
|
|
167
|
+
def write(self, record: Dict[str, Any]) -> None:
|
|
168
|
+
"""
|
|
169
|
+
Print one trace record to stdout.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
record: A plain dict representing the finished trace.
|
|
173
|
+
"""
|
|
174
|
+
if self.pretty:
|
|
175
|
+
print(json.dumps(record, indent=2, default=str))
|
|
176
|
+
else:
|
|
177
|
+
print(json.dumps(record, default=str))
|