mermaid-trace 0.3.1__py3-none-any.whl → 0.4.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.
- mermaid_trace/__init__.py +79 -18
- mermaid_trace/cli.py +88 -20
- mermaid_trace/core/context.py +21 -20
- mermaid_trace/core/decorators.py +212 -84
- mermaid_trace/core/events.py +27 -25
- mermaid_trace/core/formatter.py +20 -11
- mermaid_trace/handlers/async_handler.py +52 -0
- mermaid_trace/handlers/mermaid_handler.py +33 -20
- mermaid_trace/integrations/fastapi.py +45 -22
- {mermaid_trace-0.3.1.dist-info → mermaid_trace-0.4.0.dist-info}/METADATA +4 -2
- mermaid_trace-0.4.0.dist-info/RECORD +16 -0
- mermaid_trace-0.3.1.dist-info/RECORD +0 -15
- {mermaid_trace-0.3.1.dist-info → mermaid_trace-0.4.0.dist-info}/WHEEL +0 -0
- {mermaid_trace-0.3.1.dist-info → mermaid_trace-0.4.0.dist-info}/entry_points.txt +0 -0
- {mermaid_trace-0.3.1.dist-info → mermaid_trace-0.4.0.dist-info}/licenses/LICENSE +0 -0
mermaid_trace/__init__.py
CHANGED
|
@@ -7,14 +7,28 @@ developers understand the flow of their applications, debug complex interactions
|
|
|
7
7
|
and document system behavior.
|
|
8
8
|
|
|
9
9
|
Key Components:
|
|
10
|
-
- `trace`: A decorator to instrument functions for tracing.
|
|
10
|
+
- `trace`: A decorator to instrument functions for tracing. It captures arguments,
|
|
11
|
+
return values, and errors, and logs them as interactions.
|
|
11
12
|
- `LogContext`: Manages execution context (like thread-local storage) to track
|
|
12
13
|
caller/callee relationships across async tasks and threads.
|
|
13
14
|
- `configure_flow`: Sets up the logging handler to write diagrams to a file.
|
|
15
|
+
It handles handler configuration, file modes, and async logging setup.
|
|
16
|
+
|
|
17
|
+
Usage Example:
|
|
18
|
+
from mermaid_trace import trace, configure_flow
|
|
19
|
+
|
|
20
|
+
configure_flow("my_flow.mmd")
|
|
21
|
+
|
|
22
|
+
@trace
|
|
23
|
+
def hello():
|
|
24
|
+
print("Hello")
|
|
25
|
+
|
|
26
|
+
hello()
|
|
14
27
|
"""
|
|
15
28
|
|
|
16
29
|
from .core.decorators import trace_interaction, trace
|
|
17
30
|
from .handlers.mermaid_handler import MermaidFileHandler
|
|
31
|
+
from .handlers.async_handler import AsyncMermaidHandler
|
|
18
32
|
from .core.events import FlowEvent
|
|
19
33
|
from .core.context import LogContext
|
|
20
34
|
from .core.formatter import MermaidFormatter
|
|
@@ -22,42 +36,80 @@ from .core.formatter import MermaidFormatter
|
|
|
22
36
|
# Integrations (like FastAPI) must be imported explicitly by the user if needed.
|
|
23
37
|
|
|
24
38
|
from importlib.metadata import PackageNotFoundError, version
|
|
39
|
+
from typing import List, Optional
|
|
25
40
|
|
|
26
41
|
import logging
|
|
27
42
|
|
|
28
|
-
|
|
43
|
+
|
|
44
|
+
def configure_flow(
|
|
45
|
+
output_file: str = "flow.mmd",
|
|
46
|
+
handlers: Optional[List[logging.Handler]] = None,
|
|
47
|
+
append: bool = False,
|
|
48
|
+
async_mode: bool = False,
|
|
49
|
+
) -> logging.Logger:
|
|
29
50
|
"""
|
|
30
51
|
Configures the flow logger to output to a Mermaid file.
|
|
31
|
-
|
|
52
|
+
|
|
32
53
|
This function sets up the logging infrastructure required to capture
|
|
33
54
|
trace events and write them to the specified output file. It should
|
|
34
|
-
be called once at the start of your application.
|
|
35
|
-
|
|
55
|
+
be called once at the start of your application to initialize the tracing system.
|
|
56
|
+
|
|
36
57
|
Args:
|
|
37
58
|
output_file (str): The absolute or relative path to the output .mmd file.
|
|
38
59
|
Defaults to "flow.mmd" in the current directory.
|
|
39
|
-
|
|
60
|
+
If the file does not exist, it will be created with the correct header.
|
|
61
|
+
handlers (List[logging.Handler], optional): A list of custom logging handlers.
|
|
62
|
+
If provided, 'output_file' is ignored unless
|
|
63
|
+
you explicitly include a MermaidFileHandler.
|
|
64
|
+
Useful if you want to stream logs to other destinations.
|
|
65
|
+
append (bool): If True, adds the new handler(s) without removing existing ones.
|
|
66
|
+
Defaults to False (clears existing handlers to prevent duplicate logging).
|
|
67
|
+
async_mode (bool): If True, uses a non-blocking background thread for logging (QueueHandler).
|
|
68
|
+
Recommended for high-performance production environments to avoid
|
|
69
|
+
blocking the main execution thread during file I/O.
|
|
70
|
+
Defaults to False.
|
|
71
|
+
|
|
40
72
|
Returns:
|
|
41
73
|
logging.Logger: The configured logger instance used for flow tracing.
|
|
42
74
|
"""
|
|
43
75
|
# Get the specific logger used by the tracing decorators
|
|
76
|
+
# This logger is isolated from the root logger to prevent pollution
|
|
44
77
|
logger = logging.getLogger("mermaid_trace.flow")
|
|
45
78
|
logger.setLevel(logging.INFO)
|
|
46
|
-
|
|
79
|
+
|
|
47
80
|
# Remove existing handlers to avoid duplicate logs if configured multiple times
|
|
48
|
-
|
|
81
|
+
# unless 'append' is requested. This ensures idempotency when calling configure_flow multiple times.
|
|
82
|
+
if not append and logger.hasHandlers():
|
|
49
83
|
logger.handlers.clear()
|
|
50
|
-
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
84
|
+
|
|
85
|
+
# Determine the target handlers
|
|
86
|
+
target_handlers = []
|
|
87
|
+
|
|
88
|
+
if handlers:
|
|
89
|
+
# Use user-provided handlers if available
|
|
90
|
+
target_handlers = handlers
|
|
91
|
+
else:
|
|
92
|
+
# Create default Mermaid handler
|
|
93
|
+
# This handler knows how to write the Mermaid header and format events
|
|
94
|
+
handler = MermaidFileHandler(output_file)
|
|
95
|
+
handler.setFormatter(MermaidFormatter())
|
|
96
|
+
target_handlers = [handler]
|
|
97
|
+
|
|
98
|
+
if async_mode:
|
|
99
|
+
# Wrap the target handlers in an AsyncMermaidHandler (QueueHandler)
|
|
100
|
+
# The QueueListener will pick up logs from the queue and dispatch to target_handlers
|
|
101
|
+
# This decouples the application execution from the logging I/O
|
|
102
|
+
async_handler = AsyncMermaidHandler(target_handlers)
|
|
103
|
+
logger.addHandler(async_handler)
|
|
104
|
+
else:
|
|
105
|
+
# Attach handlers directly to the logger for synchronous logging
|
|
106
|
+
# Simple and reliable for debugging or low-throughput applications
|
|
107
|
+
for h in target_handlers:
|
|
108
|
+
logger.addHandler(h)
|
|
109
|
+
|
|
59
110
|
return logger
|
|
60
111
|
|
|
112
|
+
|
|
61
113
|
try:
|
|
62
114
|
# Attempt to retrieve the installed package version
|
|
63
115
|
__version__ = version("mermaid-trace")
|
|
@@ -67,4 +119,13 @@ except PackageNotFoundError:
|
|
|
67
119
|
|
|
68
120
|
|
|
69
121
|
# Export public API for easy access
|
|
70
|
-
__all__ = [
|
|
122
|
+
__all__ = [
|
|
123
|
+
"trace_interaction",
|
|
124
|
+
"trace",
|
|
125
|
+
"configure_flow",
|
|
126
|
+
"MermaidFileHandler",
|
|
127
|
+
"AsyncMermaidHandler",
|
|
128
|
+
"LogContext",
|
|
129
|
+
"FlowEvent",
|
|
130
|
+
"MermaidFormatter",
|
|
131
|
+
]
|
mermaid_trace/cli.py
CHANGED
|
@@ -8,13 +8,22 @@ from pathlib import Path
|
|
|
8
8
|
from typing import Type, Any
|
|
9
9
|
|
|
10
10
|
try:
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
# Watchdog is an optional dependency that allows efficient file monitoring.
|
|
12
|
+
# If installed, we use it to detect file changes instantly.
|
|
13
|
+
from watchdog.observers import Observer
|
|
14
|
+
from watchdog.events import FileSystemEventHandler
|
|
15
|
+
|
|
13
16
|
HAS_WATCHDOG = True
|
|
14
17
|
except ImportError:
|
|
18
|
+
# Fallback for when watchdog is not installed (e.g., minimal install).
|
|
15
19
|
HAS_WATCHDOG = False
|
|
16
20
|
|
|
17
21
|
# HTML Template for the preview page
|
|
22
|
+
# This template provides a self-contained environment to render Mermaid diagrams.
|
|
23
|
+
# It includes:
|
|
24
|
+
# 1. Mermaid.js library from CDN.
|
|
25
|
+
# 2. CSS for basic styling and layout.
|
|
26
|
+
# 3. JavaScript logic for auto-refreshing when the source file changes.
|
|
18
27
|
HTML_TEMPLATE = """
|
|
19
28
|
<!DOCTYPE html>
|
|
20
29
|
<html lang="en">
|
|
@@ -59,12 +68,15 @@ HTML_TEMPLATE = """
|
|
|
59
68
|
mermaid.initialize({{ startOnLoad: true, theme: 'default' }});
|
|
60
69
|
|
|
61
70
|
// Live Reload Logic
|
|
71
|
+
// We track the file's modification time (mtime) sent by the server.
|
|
62
72
|
const currentMtime = "{mtime}";
|
|
63
73
|
|
|
64
74
|
function checkUpdate() {{
|
|
75
|
+
// Poll the /_status endpoint to check if the file has changed on disk
|
|
65
76
|
fetch('/_status')
|
|
66
77
|
.then(response => response.text())
|
|
67
78
|
.then(mtime => {{
|
|
79
|
+
// If the server reports a different mtime, reload the page
|
|
68
80
|
if (mtime && mtime !== currentMtime) {{
|
|
69
81
|
console.log("File changed, reloading...");
|
|
70
82
|
location.reload();
|
|
@@ -74,20 +86,40 @@ HTML_TEMPLATE = """
|
|
|
74
86
|
}}
|
|
75
87
|
|
|
76
88
|
// Poll every 1 second
|
|
89
|
+
// This is a simple alternative to WebSockets for local dev tools
|
|
77
90
|
setInterval(checkUpdate, 1000);
|
|
78
91
|
</script>
|
|
79
92
|
</body>
|
|
80
93
|
</html>
|
|
81
94
|
"""
|
|
82
95
|
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
|
|
97
|
+
def _create_handler(
|
|
98
|
+
filename: str, path: Path
|
|
99
|
+
) -> Type[http.server.SimpleHTTPRequestHandler]:
|
|
100
|
+
"""
|
|
101
|
+
Factory function to create a custom request handler class.
|
|
102
|
+
|
|
103
|
+
This uses a closure to inject `filename` and `path` into the handler's scope,
|
|
104
|
+
allowing the `do_GET` method to access them without global variables.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
filename (str): Display name of the file.
|
|
108
|
+
path (Path): Path object to the file on disk.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Type[SimpleHTTPRequestHandler]: A custom handler class.
|
|
112
|
+
"""
|
|
113
|
+
|
|
85
114
|
class Handler(http.server.SimpleHTTPRequestHandler):
|
|
86
115
|
"""
|
|
87
116
|
Custom Request Handler to serve the generated HTML dynamically.
|
|
117
|
+
It intercepts GET requests to serve the constructed HTML instead of static files.
|
|
88
118
|
"""
|
|
119
|
+
|
|
89
120
|
def log_message(self, format: str, *args: Any) -> None:
|
|
90
121
|
# Suppress default logging to keep console clean
|
|
122
|
+
# We only want to see application logs, not every HTTP request
|
|
91
123
|
pass
|
|
92
124
|
|
|
93
125
|
def do_GET(self) -> None:
|
|
@@ -99,22 +131,26 @@ def _create_handler(filename: str, path: Path) -> Type[http.server.SimpleHTTPReq
|
|
|
99
131
|
self.send_response(200)
|
|
100
132
|
self.send_header("Content-type", "text/html")
|
|
101
133
|
self.end_headers()
|
|
102
|
-
|
|
134
|
+
|
|
103
135
|
try:
|
|
104
136
|
# Read the current content of the mermaid file
|
|
105
137
|
content = path.read_text(encoding="utf-8")
|
|
106
138
|
mtime = str(path.stat().st_mtime)
|
|
107
139
|
except Exception as e:
|
|
108
140
|
# Fallback if reading fails (e.g., file locked)
|
|
141
|
+
# Show the error directly in the diagram area
|
|
109
142
|
content = f"sequenceDiagram\nNote right of Error: Failed to read file: {e}"
|
|
110
143
|
mtime = "0"
|
|
111
|
-
|
|
144
|
+
|
|
112
145
|
# Inject content into the HTML template
|
|
113
|
-
html = HTML_TEMPLATE.format(
|
|
146
|
+
html = HTML_TEMPLATE.format(
|
|
147
|
+
filename=filename, content=content, mtime=mtime
|
|
148
|
+
)
|
|
114
149
|
self.wfile.write(html.encode("utf-8"))
|
|
115
|
-
|
|
150
|
+
|
|
116
151
|
elif self.path == "/_status":
|
|
117
|
-
# API endpoint for client
|
|
152
|
+
# API endpoint for client-side polling.
|
|
153
|
+
# Returns the current modification time of the file.
|
|
118
154
|
self.send_response(200)
|
|
119
155
|
self.send_header("Content-type", "text/plain")
|
|
120
156
|
self.end_headers()
|
|
@@ -123,15 +159,29 @@ def _create_handler(filename: str, path: Path) -> Type[http.server.SimpleHTTPReq
|
|
|
123
159
|
except OSError:
|
|
124
160
|
mtime = "0"
|
|
125
161
|
self.wfile.write(mtime.encode("utf-8"))
|
|
126
|
-
|
|
162
|
+
|
|
127
163
|
else:
|
|
128
|
-
# Serve static files if needed, or return 404
|
|
164
|
+
# Serve other static files if needed, or return 404
|
|
129
165
|
super().do_GET()
|
|
166
|
+
|
|
130
167
|
return Handler
|
|
131
168
|
|
|
169
|
+
|
|
132
170
|
def serve(filename: str, port: int = 8000) -> None:
|
|
133
171
|
"""
|
|
134
172
|
Starts a local HTTP server to preview the Mermaid diagram.
|
|
173
|
+
|
|
174
|
+
This function blocks the main thread and runs a TCP server.
|
|
175
|
+
It automatically opens the default web browser to the preview URL.
|
|
176
|
+
|
|
177
|
+
Features:
|
|
178
|
+
- Serves the .mmd file wrapped in an HTML viewer.
|
|
179
|
+
- Uses Watchdog (if available) or client-side polling for live reloads.
|
|
180
|
+
- Gracefully handles shutdown on Ctrl+C.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
filename (str): Path to the .mmd file to serve.
|
|
184
|
+
port (int): Port to bind the server to. Default is 8000.
|
|
135
185
|
"""
|
|
136
186
|
path = Path(filename)
|
|
137
187
|
if not path.exists():
|
|
@@ -139,11 +189,18 @@ def serve(filename: str, port: int = 8000) -> None:
|
|
|
139
189
|
sys.exit(1)
|
|
140
190
|
|
|
141
191
|
# Setup Watchdog if available
|
|
192
|
+
# Watchdog allows us to print console messages when the file changes.
|
|
193
|
+
# The actual browser reload is triggered by the client polling the /_status endpoint,
|
|
194
|
+
# but Watchdog gives immediate feedback in the terminal.
|
|
142
195
|
observer = None
|
|
143
196
|
if HAS_WATCHDOG:
|
|
197
|
+
|
|
144
198
|
class FileChangeHandler(FileSystemEventHandler):
|
|
145
199
|
def on_modified(self, event: Any) -> None:
|
|
146
|
-
|
|
200
|
+
# Filter for the specific file we are watching
|
|
201
|
+
if not event.is_directory and os.path.abspath(event.src_path) == str(
|
|
202
|
+
path.resolve()
|
|
203
|
+
):
|
|
147
204
|
print(f"[Watchdog] File changed: {filename}")
|
|
148
205
|
|
|
149
206
|
print("Initializing file watcher...")
|
|
@@ -151,17 +208,21 @@ def serve(filename: str, port: int = 8000) -> None:
|
|
|
151
208
|
observer.schedule(FileChangeHandler(), path=str(path.parent), recursive=False)
|
|
152
209
|
observer.start()
|
|
153
210
|
else:
|
|
154
|
-
print(
|
|
211
|
+
print(
|
|
212
|
+
"Watchdog not installed. Falling back to polling mode (client-side only)."
|
|
213
|
+
)
|
|
155
214
|
|
|
156
215
|
HandlerClass = _create_handler(filename, path)
|
|
157
216
|
|
|
158
217
|
print(f"Serving {filename} at http://localhost:{port}")
|
|
159
218
|
print("Press Ctrl+C to stop.")
|
|
160
|
-
|
|
219
|
+
|
|
161
220
|
# Open browser automatically to the server URL
|
|
162
221
|
webbrowser.open(f"http://localhost:{port}")
|
|
163
|
-
|
|
222
|
+
|
|
164
223
|
# Start the TCP server
|
|
224
|
+
# ThreadingTCPServer is used to handle multiple requests concurrently if needed,
|
|
225
|
+
# ensuring the browser polling doesn't block the initial load.
|
|
165
226
|
with socketserver.ThreadingTCPServer(("", port), HandlerClass) as httpd:
|
|
166
227
|
try:
|
|
167
228
|
httpd.serve_forever()
|
|
@@ -172,22 +233,29 @@ def serve(filename: str, port: int = 8000) -> None:
|
|
|
172
233
|
observer.join()
|
|
173
234
|
httpd.server_close()
|
|
174
235
|
|
|
236
|
+
|
|
175
237
|
def main() -> None:
|
|
176
238
|
"""
|
|
177
239
|
Entry point for the CLI application.
|
|
240
|
+
Parses arguments and dispatches to the appropriate command handler.
|
|
178
241
|
"""
|
|
179
242
|
parser = argparse.ArgumentParser(description="MermaidTrace CLI")
|
|
180
243
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
181
|
-
|
|
244
|
+
|
|
182
245
|
# 'serve' command definition
|
|
183
|
-
serve_parser = subparsers.add_parser(
|
|
246
|
+
serve_parser = subparsers.add_parser(
|
|
247
|
+
"serve", help="Serve a Mermaid file in the browser"
|
|
248
|
+
)
|
|
184
249
|
serve_parser.add_argument("file", help="Path to the .mmd file")
|
|
185
|
-
serve_parser.add_argument(
|
|
186
|
-
|
|
250
|
+
serve_parser.add_argument(
|
|
251
|
+
"--port", type=int, default=8000, help="Port to bind to (default: 8000)"
|
|
252
|
+
)
|
|
253
|
+
|
|
187
254
|
args = parser.parse_args()
|
|
188
|
-
|
|
255
|
+
|
|
189
256
|
if args.command == "serve":
|
|
190
257
|
serve(args.file, args.port)
|
|
191
258
|
|
|
259
|
+
|
|
192
260
|
if __name__ == "__main__":
|
|
193
261
|
main()
|
mermaid_trace/core/context.py
CHANGED
|
@@ -3,17 +3,18 @@ from contextlib import asynccontextmanager, contextmanager
|
|
|
3
3
|
from typing import Any, AsyncIterator, Dict, Iterator
|
|
4
4
|
import uuid
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
class LogContext:
|
|
7
8
|
"""
|
|
8
9
|
Manages global context information for logging (e.g., request_id, user_id, current_participant).
|
|
9
|
-
|
|
10
|
-
This class utilizes `contextvars.ContextVar` to ensure thread-safety and
|
|
10
|
+
|
|
11
|
+
This class utilizes `contextvars.ContextVar` to ensure thread-safety and
|
|
11
12
|
correct context propagation in asynchronous (asyncio) environments.
|
|
12
|
-
Unlike `threading.local()`, `ContextVar` works natively with Python's async/await
|
|
13
|
-
event loop, ensuring that context is preserved across `await` points but isolated
|
|
13
|
+
Unlike `threading.local()`, `ContextVar` works natively with Python's async/await
|
|
14
|
+
event loop, ensuring that context is preserved across `await` points but isolated
|
|
14
15
|
between different concurrent tasks.
|
|
15
16
|
"""
|
|
16
|
-
|
|
17
|
+
|
|
17
18
|
# ContextVar is the key mechanism here.
|
|
18
19
|
# It stores a dictionary unique to the current execution context (Task/Thread).
|
|
19
20
|
# "log_context" is the name of the variable, useful for debugging.
|
|
@@ -24,9 +25,9 @@ class LogContext:
|
|
|
24
25
|
def _get_store(cls) -> Dict[str, Any]:
|
|
25
26
|
"""
|
|
26
27
|
Retrieves the current context dictionary.
|
|
27
|
-
|
|
28
|
-
If the context variable has not been set in the current context,
|
|
29
|
-
it returns a fresh empty dictionary. This prevents LookupError
|
|
28
|
+
|
|
29
|
+
If the context variable has not been set in the current context,
|
|
30
|
+
it returns a fresh empty dictionary. This prevents LookupError
|
|
30
31
|
and ensures there's always a valid dictionary to work with.
|
|
31
32
|
"""
|
|
32
33
|
try:
|
|
@@ -38,8 +39,8 @@ class LogContext:
|
|
|
38
39
|
def set(cls, key: str, value: Any) -> None:
|
|
39
40
|
"""
|
|
40
41
|
Sets a specific key-value pair in the current context.
|
|
41
|
-
|
|
42
|
-
Important: ContextVars are immutable collections. To modify the context,
|
|
42
|
+
|
|
43
|
+
Important: ContextVars are immutable collections. To modify the context,
|
|
43
44
|
we must:
|
|
44
45
|
1. Retrieve the current dictionary.
|
|
45
46
|
2. Create a shallow copy (to avoid affecting parent contexts if we were reusing the object).
|
|
@@ -54,8 +55,8 @@ class LogContext:
|
|
|
54
55
|
def update(cls, data: Dict[str, Any]) -> None:
|
|
55
56
|
"""
|
|
56
57
|
Updates multiple keys in the current context at once.
|
|
57
|
-
|
|
58
|
-
This follows the same Copy-Update-Set pattern as `set()` to maintain
|
|
58
|
+
|
|
59
|
+
This follows the same Copy-Update-Set pattern as `set()` to maintain
|
|
59
60
|
context isolation.
|
|
60
61
|
"""
|
|
61
62
|
if not data:
|
|
@@ -83,18 +84,18 @@ class LogContext:
|
|
|
83
84
|
def scope(cls, data: Dict[str, Any]) -> Iterator[None]:
|
|
84
85
|
"""
|
|
85
86
|
Synchronous context manager for temporary context updates.
|
|
86
|
-
|
|
87
|
+
|
|
87
88
|
Usage:
|
|
88
89
|
with LogContext.scope({"user_id": 123}):
|
|
89
90
|
# user_id is 123 here
|
|
90
91
|
some_function()
|
|
91
92
|
# user_id reverts to previous value (or disappears) here
|
|
92
|
-
|
|
93
|
+
|
|
93
94
|
Mechanism:
|
|
94
95
|
1. Copies current context and updates it with new data.
|
|
95
96
|
2. Sets the ContextVar to this new state, receiving a `Token`.
|
|
96
97
|
3. Yields control to the block.
|
|
97
|
-
4. Finally, uses the `Token` to reset the ContextVar to its exact state
|
|
98
|
+
4. Finally, uses the `Token` to reset the ContextVar to its exact state
|
|
98
99
|
before the block entered.
|
|
99
100
|
"""
|
|
100
101
|
current_ctx = cls._get_store().copy()
|
|
@@ -111,11 +112,11 @@ class LogContext:
|
|
|
111
112
|
async def ascope(cls, data: Dict[str, Any]) -> AsyncIterator[None]:
|
|
112
113
|
"""
|
|
113
114
|
Async context manager for temporary context updates in coroutines.
|
|
114
|
-
|
|
115
|
+
|
|
115
116
|
Usage:
|
|
116
117
|
async with LogContext.ascope({"request_id": "abc"}):
|
|
117
118
|
await some_async_function()
|
|
118
|
-
|
|
119
|
+
|
|
119
120
|
This is functionally identical to `scope` but designed for `async with` blocks.
|
|
120
121
|
It ensures that even if the code inside `yield` suspends execution (await),
|
|
121
122
|
the context remains valid for that task.
|
|
@@ -165,12 +166,12 @@ class LogContext:
|
|
|
165
166
|
def current_trace_id(cls) -> str:
|
|
166
167
|
"""
|
|
167
168
|
Retrieves the current trace ID for correlating events in a single flow.
|
|
168
|
-
|
|
169
|
+
|
|
169
170
|
Lazy Initialization Logic:
|
|
170
|
-
If no trace_id exists in the current context, it generates a new UUIDv4
|
|
171
|
+
If no trace_id exists in the current context, it generates a new UUIDv4
|
|
171
172
|
and sets it immediately. This ensures that:
|
|
172
173
|
1. A trace ID is always available when asked for.
|
|
173
|
-
2. Once generated, the same ID persists for the duration of the context
|
|
174
|
+
2. Once generated, the same ID persists for the duration of the context
|
|
174
175
|
(unless manually changed), linking all subsequent logs together.
|
|
175
176
|
"""
|
|
176
177
|
tid = cls.get("trace_id")
|