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
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
3
|
|
|
4
|
+
|
|
4
5
|
class MermaidFileHandler(logging.FileHandler):
|
|
5
6
|
"""
|
|
6
7
|
A custom logging handler that writes `FlowEvent` objects to a Mermaid (.mmd) file.
|
|
7
|
-
|
|
8
|
+
|
|
8
9
|
Strategy & Optimization:
|
|
9
|
-
1. **Inheritance**: Inherits from `logging.FileHandler` to leverage robust,
|
|
10
|
+
1. **Inheritance**: Inherits from `logging.FileHandler` to leverage robust,
|
|
10
11
|
thread-safe file writing capabilities (locking, buffering) provided by the stdlib.
|
|
11
|
-
2. **Header Management**: Automatically handles the Mermaid file header
|
|
12
|
-
(`sequenceDiagram`, `title`, `autonumber`) to ensure the output file
|
|
13
|
-
is a valid Mermaid document. It smartly detects if the file is new or
|
|
12
|
+
2. **Header Management**: Automatically handles the Mermaid file header
|
|
13
|
+
(`sequenceDiagram`, `title`, `autonumber`) to ensure the output file
|
|
14
|
+
is a valid Mermaid document. It smartly detects if the file is new or
|
|
14
15
|
being appended to.
|
|
15
|
-
3. **Deferred Formatting**: The actual string conversion happens in the `emit`
|
|
16
|
+
3. **Deferred Formatting**: The actual string conversion happens in the `emit`
|
|
16
17
|
method (via the formatter), keeping the handler focused on I/O.
|
|
17
18
|
"""
|
|
18
|
-
|
|
19
|
-
def __init__(
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
filename: str,
|
|
23
|
+
title: str = "Log Flow",
|
|
24
|
+
mode: str = "a",
|
|
25
|
+
encoding: str = "utf-8",
|
|
26
|
+
delay: bool = False,
|
|
27
|
+
):
|
|
20
28
|
"""
|
|
21
29
|
Initialize the handler.
|
|
22
|
-
|
|
30
|
+
|
|
23
31
|
Args:
|
|
24
32
|
filename (str): The path to the output .mmd file.
|
|
25
33
|
title (str): The title of the Mermaid diagram (written in the header).
|
|
@@ -30,22 +38,23 @@ class MermaidFileHandler(logging.FileHandler):
|
|
|
30
38
|
"""
|
|
31
39
|
# Ensure directory exists to prevent FileNotFoundError on open
|
|
32
40
|
os.makedirs(os.path.dirname(os.path.abspath(filename)) or ".", exist_ok=True)
|
|
33
|
-
|
|
41
|
+
|
|
34
42
|
# Header Strategy:
|
|
35
43
|
# We need to write the "sequenceDiagram" preamble ONLY if:
|
|
36
44
|
# 1. We are overwriting the file (mode='w').
|
|
37
45
|
# 2. We are appending (mode='a'), but the file doesn't exist or is empty.
|
|
46
|
+
# This prevents invalid Mermaid files (e.g., multiple "sequenceDiagram" lines).
|
|
38
47
|
should_write_header = False
|
|
39
|
-
if mode ==
|
|
48
|
+
if mode == "w":
|
|
40
49
|
should_write_header = True
|
|
41
|
-
elif mode ==
|
|
50
|
+
elif mode == "a":
|
|
42
51
|
if not os.path.exists(filename) or os.path.getsize(filename) == 0:
|
|
43
52
|
should_write_header = True
|
|
44
|
-
|
|
53
|
+
|
|
45
54
|
# Initialize standard FileHandler (opens the file unless delay=True)
|
|
46
55
|
super().__init__(filename, mode, encoding, delay)
|
|
47
56
|
self.title = title
|
|
48
|
-
|
|
57
|
+
|
|
49
58
|
# Write header immediately if needed.
|
|
50
59
|
if should_write_header:
|
|
51
60
|
self._write_header()
|
|
@@ -53,14 +62,17 @@ class MermaidFileHandler(logging.FileHandler):
|
|
|
53
62
|
def _write_header(self) -> None:
|
|
54
63
|
"""
|
|
55
64
|
Writes the initial Mermaid syntax lines.
|
|
56
|
-
|
|
65
|
+
|
|
57
66
|
This setup is required for Mermaid JS or Live Editor to render the diagram.
|
|
67
|
+
It defines the diagram type (sequenceDiagram), title, and enables autonumbering.
|
|
58
68
|
"""
|
|
59
69
|
# We use the stream directly if available, or open momentarily if delayed
|
|
60
70
|
if self.stream:
|
|
61
71
|
self.stream.write("sequenceDiagram\n")
|
|
62
72
|
self.stream.write(f" title {self.title}\n")
|
|
63
73
|
self.stream.write(" autonumber\n")
|
|
74
|
+
# Flush ensures the header is written to disk immediately,
|
|
75
|
+
# so it appears even if the program crashes right after.
|
|
64
76
|
self.flush()
|
|
65
77
|
else:
|
|
66
78
|
# Handling 'delay=True' case:
|
|
@@ -74,13 +86,14 @@ class MermaidFileHandler(logging.FileHandler):
|
|
|
74
86
|
def emit(self, record: logging.LogRecord) -> None:
|
|
75
87
|
"""
|
|
76
88
|
Process a log record.
|
|
77
|
-
|
|
89
|
+
|
|
78
90
|
Optimization:
|
|
79
|
-
- Checks for `flow_event` attribute first. This allows this handler
|
|
91
|
+
- Checks for `flow_event` attribute first. This allows this handler
|
|
80
92
|
to be attached to the root logger without processing irrelevant system logs.
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
It acts as a high-performance filter before formatting.
|
|
94
|
+
- Delegates the actual writing to `super().emit()`, which handles
|
|
95
|
+
thread locking and stream flushing safely.
|
|
83
96
|
"""
|
|
84
97
|
# Only process records that contain our structured FlowEvent data
|
|
85
|
-
if hasattr(record,
|
|
98
|
+
if hasattr(record, "flow_event"):
|
|
86
99
|
super().emit(record)
|
|
@@ -12,7 +12,10 @@ if TYPE_CHECKING:
|
|
|
12
12
|
else:
|
|
13
13
|
try:
|
|
14
14
|
from fastapi import Request, Response
|
|
15
|
-
from starlette.middleware.base import
|
|
15
|
+
from starlette.middleware.base import (
|
|
16
|
+
BaseHTTPMiddleware,
|
|
17
|
+
RequestResponseEndpoint,
|
|
18
|
+
)
|
|
16
19
|
except ImportError:
|
|
17
20
|
# Handle the case where FastAPI/Starlette are not installed.
|
|
18
21
|
# We define dummy types to prevent NameErrors at import time,
|
|
@@ -22,37 +25,43 @@ else:
|
|
|
22
25
|
Response = Any # type: ignore[assignment]
|
|
23
26
|
RequestResponseEndpoint = Any # type: ignore[assignment]
|
|
24
27
|
|
|
28
|
+
|
|
25
29
|
class MermaidTraceMiddleware(BaseHTTPMiddleware):
|
|
26
30
|
"""
|
|
27
31
|
FastAPI Middleware to trace HTTP requests as interactions in the sequence diagram.
|
|
28
|
-
|
|
32
|
+
|
|
29
33
|
This middleware acts as the entry point for tracing a web request. It:
|
|
30
34
|
1. Identifies the client (Source).
|
|
31
35
|
2. Logs the incoming request.
|
|
32
36
|
3. Initializes the `LogContext` for the request lifecycle.
|
|
33
37
|
4. Logs the response or error.
|
|
34
38
|
"""
|
|
39
|
+
|
|
35
40
|
def __init__(self, app: Any, app_name: str = "FastAPI"):
|
|
36
41
|
"""
|
|
37
42
|
Initialize the middleware.
|
|
38
|
-
|
|
43
|
+
|
|
39
44
|
Args:
|
|
40
45
|
app: The FastAPI application instance.
|
|
41
46
|
app_name: The name of this service to appear in the diagram (e.g., "UserAPI").
|
|
42
47
|
"""
|
|
43
|
-
if BaseHTTPMiddleware is object:
|
|
44
|
-
|
|
48
|
+
if BaseHTTPMiddleware is object: # type: ignore[comparison-overlap]
|
|
49
|
+
raise ImportError(
|
|
50
|
+
"FastAPI/Starlette is required to use MermaidTraceMiddleware"
|
|
51
|
+
)
|
|
45
52
|
super().__init__(app)
|
|
46
53
|
self.app_name = app_name
|
|
47
54
|
|
|
48
|
-
async def dispatch(
|
|
55
|
+
async def dispatch(
|
|
56
|
+
self, request: Request, call_next: RequestResponseEndpoint
|
|
57
|
+
) -> Response:
|
|
49
58
|
"""
|
|
50
59
|
Intercepts the incoming request.
|
|
51
|
-
|
|
60
|
+
|
|
52
61
|
Args:
|
|
53
62
|
request (Request): The incoming HTTP request.
|
|
54
63
|
call_next (Callable): The function to call the next middleware or endpoint.
|
|
55
|
-
|
|
64
|
+
|
|
56
65
|
Returns:
|
|
57
66
|
Response: The HTTP response.
|
|
58
67
|
"""
|
|
@@ -60,17 +69,17 @@ class MermaidTraceMiddleware(BaseHTTPMiddleware):
|
|
|
60
69
|
# Try to get a specific ID from headers (useful for distributed tracing),
|
|
61
70
|
# otherwise fallback to "Client".
|
|
62
71
|
source = request.headers.get("X-Source", "Client")
|
|
63
|
-
|
|
72
|
+
|
|
64
73
|
# Determine Trace ID
|
|
65
74
|
# Check X-Trace-ID header or generate new UUID
|
|
66
75
|
trace_id = request.headers.get("X-Trace-ID") or str(uuid.uuid4())
|
|
67
|
-
|
|
76
|
+
|
|
68
77
|
# 2. Determine Action
|
|
69
78
|
# Format: "METHOD /path" (e.g., "GET /users")
|
|
70
79
|
action = f"{request.method} {request.url.path}"
|
|
71
|
-
|
|
80
|
+
|
|
72
81
|
logger = get_flow_logger()
|
|
73
|
-
|
|
82
|
+
|
|
74
83
|
# 3. Log Request (Source -> App)
|
|
75
84
|
req_event = FlowEvent(
|
|
76
85
|
source=source,
|
|
@@ -78,20 +87,27 @@ class MermaidTraceMiddleware(BaseHTTPMiddleware):
|
|
|
78
87
|
action=action,
|
|
79
88
|
message=action,
|
|
80
89
|
params=f"query={request.query_params}" if request.query_params else None,
|
|
81
|
-
trace_id=trace_id
|
|
90
|
+
trace_id=trace_id,
|
|
82
91
|
)
|
|
83
|
-
logger.info(
|
|
84
|
-
|
|
92
|
+
logger.info(
|
|
93
|
+
f"{source}->{self.app_name}: {action}", extra={"flow_event": req_event}
|
|
94
|
+
)
|
|
95
|
+
|
|
85
96
|
# 4. Set Context and Process Request
|
|
86
97
|
# We set the current participant to the app name.
|
|
87
98
|
# `ascope` ensures this context applies to all code running within `call_next`.
|
|
88
|
-
|
|
99
|
+
# This includes route handlers, dependencies, and other middlewares called after this one.
|
|
100
|
+
async with LogContext.ascope(
|
|
101
|
+
{"participant": self.app_name, "trace_id": trace_id}
|
|
102
|
+
):
|
|
89
103
|
start_time = time.time()
|
|
90
104
|
try:
|
|
91
105
|
# Pass control to the application
|
|
106
|
+
# This executes the actual route logic
|
|
92
107
|
response = await call_next(request)
|
|
93
|
-
|
|
108
|
+
|
|
94
109
|
# 5. Log Response (App -> Source)
|
|
110
|
+
# Calculate execution duration for the response label
|
|
95
111
|
duration = (time.time() - start_time) * 1000
|
|
96
112
|
resp_event = FlowEvent(
|
|
97
113
|
source=self.app_name,
|
|
@@ -100,14 +116,19 @@ class MermaidTraceMiddleware(BaseHTTPMiddleware):
|
|
|
100
116
|
message="Return",
|
|
101
117
|
is_return=True,
|
|
102
118
|
result=f"{response.status_code} ({duration:.1f}ms)",
|
|
103
|
-
trace_id=trace_id
|
|
119
|
+
trace_id=trace_id,
|
|
120
|
+
)
|
|
121
|
+
logger.info(
|
|
122
|
+
f"{self.app_name}->{source}: Return",
|
|
123
|
+
extra={"flow_event": resp_event},
|
|
104
124
|
)
|
|
105
|
-
logger.info(f"{self.app_name}->{source}: Return", extra={"flow_event": resp_event})
|
|
106
125
|
return response
|
|
107
|
-
|
|
126
|
+
|
|
108
127
|
except Exception as e:
|
|
109
128
|
# 6. Log Error (App --x Source)
|
|
110
129
|
# This captures unhandled exceptions that bubble up to the middleware
|
|
130
|
+
# Note: FastAPI's ExceptionHandlers might catch this before it reaches here.
|
|
131
|
+
# If so, you might see a successful return with 500 status instead.
|
|
111
132
|
err_event = FlowEvent(
|
|
112
133
|
source=self.app_name,
|
|
113
134
|
target=source,
|
|
@@ -116,7 +137,9 @@ class MermaidTraceMiddleware(BaseHTTPMiddleware):
|
|
|
116
137
|
is_return=True,
|
|
117
138
|
is_error=True,
|
|
118
139
|
error_message=str(e),
|
|
119
|
-
trace_id=trace_id
|
|
140
|
+
trace_id=trace_id,
|
|
141
|
+
)
|
|
142
|
+
logger.error(
|
|
143
|
+
f"{self.app_name}-x{source}: Error", extra={"flow_event": err_event}
|
|
120
144
|
)
|
|
121
|
-
logger.error(f"{self.app_name}-x{source}: Error", extra={"flow_event": err_event})
|
|
122
145
|
raise
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mermaid-trace
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Visualize your Python code execution flow as Mermaid Sequence Diagrams.
|
|
5
5
|
Project-URL: Documentation, https://github.com/xt765/mermaid-trace#readme
|
|
6
6
|
Project-URL: Changelog, https://github.com/xt765/mermaid-trace/blob/main/docs/en/CHANGELOG.md
|
|
@@ -38,6 +38,8 @@ Classifier: Programming Language :: Python
|
|
|
38
38
|
Classifier: Programming Language :: Python :: 3.10
|
|
39
39
|
Classifier: Programming Language :: Python :: 3.11
|
|
40
40
|
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
41
43
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
42
44
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
43
45
|
Classifier: Topic :: Software Development :: Debuggers
|
|
@@ -151,7 +153,7 @@ mermaid-trace serve my_flow.mmd
|
|
|
151
153
|
|
|
152
154
|
## 📂 Documentation
|
|
153
155
|
|
|
154
|
-
- [English Documentation](docs/en/
|
|
156
|
+
- [English Documentation](docs/en/USER_GUIDE.md)
|
|
155
157
|
- [中文文档](README_CN.md)
|
|
156
158
|
|
|
157
159
|
## 🤝 Contributing
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
mermaid_trace/__init__.py,sha256=yhc-shETKioVEQEfqmCGFyVelRR3_ypeClbMR_D2oBQ,5267
|
|
2
|
+
mermaid_trace/cli.py,sha256=F5-QfnKp_Et719IKWvU5IAWZTpq9ft01ow62DqNpHdA,9477
|
|
3
|
+
mermaid_trace/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
mermaid_trace/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
mermaid_trace/core/context.py,sha256=WyTKbC_I1ge802VUj9PdUiqa-VgL1r3DQUW_y8PlG8M,6618
|
|
6
|
+
mermaid_trace/core/decorators.py,sha256=13-n6qsmWnInpKgQ0Bgnnn5aQIWkRZ7raTD2xVtKVmU,12303
|
|
7
|
+
mermaid_trace/core/events.py,sha256=TVtarp7IwfTR_C404ZWoyqBZmTtScROz5hWL0uel3G4,2857
|
|
8
|
+
mermaid_trace/core/formatter.py,sha256=6k79eBU09TSCEUcDJN1mfn-_KMld0xXfKtQTKZb8Ogw,3178
|
|
9
|
+
mermaid_trace/handlers/async_handler.py,sha256=uGC27TCgfxyvQQEiJ_7Ir1EFJdsLHBUzHEsLktEaFtM,1893
|
|
10
|
+
mermaid_trace/handlers/mermaid_handler.py,sha256=JUq5gSQepNISreUYkyucS_rk27zGIiwSYFw_Lb8lL28,4314
|
|
11
|
+
mermaid_trace/integrations/fastapi.py,sha256=GJL2H0Ypi4HqiAR-kkV4kSUTitdOj4RkhPi26GGNORM,5515
|
|
12
|
+
mermaid_trace-0.4.0.dist-info/METADATA,sha256=4oszQifzCHhgCEI0Ly1nKAUuvcF-Bs-ejCReGUOsTbo,6287
|
|
13
|
+
mermaid_trace-0.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
14
|
+
mermaid_trace-0.4.0.dist-info/entry_points.txt,sha256=WS57KT_870v0A4B87QDjQUqJcddMQxbCQyYeczDAX34,57
|
|
15
|
+
mermaid_trace-0.4.0.dist-info/licenses/LICENSE,sha256=BrBog1Etiq9PdWy0SVQNVByIMD9ss4Edz-R0oXt49zA,1062
|
|
16
|
+
mermaid_trace-0.4.0.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
mermaid_trace/__init__.py,sha256=yEtdgB0Cl2qAuhGnU0zp9_YANzT3ZIrDsnQu-i3qUAc,2741
|
|
2
|
-
mermaid_trace/cli.py,sha256=_6Bm6oGIUQFbb-tr4yDoZIa9_5PQ-qjftCd7JNUm4F8,7033
|
|
3
|
-
mermaid_trace/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
mermaid_trace/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
mermaid_trace/core/context.py,sha256=H2aXivdarZgpvfAdATRWAAPpf7KHSNqhoJOepd1FK_E,6711
|
|
6
|
-
mermaid_trace/core/decorators.py,sha256=vjTnqxOCbIZXBahpgvXHBp3BFIjnIRk15O2jR9V8Ls4,9274
|
|
7
|
-
mermaid_trace/core/events.py,sha256=SG-S0hZYNa_gEpwB4Cuaahwj9WAMdzS7xXIpThoZzGQ,2999
|
|
8
|
-
mermaid_trace/core/formatter.py,sha256=WKXQeNj4l-LvbF2_Jz8FS3dyWmvtD3CHX2bQKj8p7D0,2765
|
|
9
|
-
mermaid_trace/handlers/mermaid_handler.py,sha256=dZ_T57qWUQlVNWWH5se52mB4ZioECOFwGJF52V-OOps,3931
|
|
10
|
-
mermaid_trace/integrations/fastapi.py,sha256=DeBodoPxXz2nLUIAiqmdVfFrwNVo2P0jhe7rcdF60u4,4991
|
|
11
|
-
mermaid_trace-0.3.1.dist-info/METADATA,sha256=rRBCzhTbof4unI0MPJkj-eHzKTazsodNpdfHpzDPDi0,6181
|
|
12
|
-
mermaid_trace-0.3.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
13
|
-
mermaid_trace-0.3.1.dist-info/entry_points.txt,sha256=WS57KT_870v0A4B87QDjQUqJcddMQxbCQyYeczDAX34,57
|
|
14
|
-
mermaid_trace-0.3.1.dist-info/licenses/LICENSE,sha256=BrBog1Etiq9PdWy0SVQNVByIMD9ss4Edz-R0oXt49zA,1062
|
|
15
|
-
mermaid_trace-0.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|