mermaid-trace 0.5.3.post0__py3-none-any.whl → 0.6.0.post0__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/cli.py +30 -2
- mermaid_trace/integrations/__init__.py +5 -0
- mermaid_trace/integrations/langchain.py +312 -0
- mermaid_trace/server.py +406 -0
- mermaid_trace-0.6.0.post0.dist-info/METADATA +272 -0
- {mermaid_trace-0.5.3.post0.dist-info → mermaid_trace-0.6.0.post0.dist-info}/RECORD +9 -7
- mermaid_trace-0.5.3.post0.dist-info/METADATA +0 -232
- {mermaid_trace-0.5.3.post0.dist-info → mermaid_trace-0.6.0.post0.dist-info}/WHEEL +0 -0
- {mermaid_trace-0.5.3.post0.dist-info → mermaid_trace-0.6.0.post0.dist-info}/entry_points.txt +0 -0
- {mermaid_trace-0.5.3.post0.dist-info → mermaid_trace-0.6.0.post0.dist-info}/licenses/LICENSE +0 -0
mermaid_trace/cli.py
CHANGED
|
@@ -232,7 +232,7 @@ def _create_handler(
|
|
|
232
232
|
return Handler
|
|
233
233
|
|
|
234
234
|
|
|
235
|
-
def serve(filename: str, port: int = 8000) -> None:
|
|
235
|
+
def serve(filename: str, port: int = 8000, master: bool = False) -> None:
|
|
236
236
|
"""
|
|
237
237
|
Starts the local HTTP server and file watcher to preview a Mermaid diagram.
|
|
238
238
|
|
|
@@ -249,7 +249,30 @@ def serve(filename: str, port: int = 8000) -> None:
|
|
|
249
249
|
Args:
|
|
250
250
|
filename (str): The path to the .mmd file to serve.
|
|
251
251
|
port (int): The port number to bind the server to (default: 8000).
|
|
252
|
+
master (bool): Whether to use the enhanced Master Preview Server (FastAPI + SSE).
|
|
252
253
|
"""
|
|
254
|
+
# 1. Enhanced Master Mode
|
|
255
|
+
if master:
|
|
256
|
+
try:
|
|
257
|
+
from .server import run_server, HAS_SERVER_DEPS
|
|
258
|
+
|
|
259
|
+
if HAS_SERVER_DEPS:
|
|
260
|
+
# For master mode, we watch the directory of the file
|
|
261
|
+
target_dir = os.path.dirname(os.path.abspath(filename))
|
|
262
|
+
if os.path.isdir(filename):
|
|
263
|
+
target_dir = os.path.abspath(filename)
|
|
264
|
+
|
|
265
|
+
# Open browser first
|
|
266
|
+
webbrowser.open(f"http://localhost:{port}")
|
|
267
|
+
run_server(target_dir, port)
|
|
268
|
+
return
|
|
269
|
+
else:
|
|
270
|
+
print(
|
|
271
|
+
"Warning: FastAPI/Uvicorn not found. Falling back to basic server."
|
|
272
|
+
)
|
|
273
|
+
except ImportError:
|
|
274
|
+
print("Warning: server module not found. Falling back to basic server.")
|
|
275
|
+
|
|
253
276
|
# Create a Path object for robust file path handling
|
|
254
277
|
path = Path(filename)
|
|
255
278
|
|
|
@@ -342,6 +365,11 @@ def main() -> None:
|
|
|
342
365
|
serve_parser.add_argument(
|
|
343
366
|
"--port", type=int, default=8000, help="Port to bind to (default: 8000)"
|
|
344
367
|
)
|
|
368
|
+
serve_parser.add_argument(
|
|
369
|
+
"--master",
|
|
370
|
+
action="store_true",
|
|
371
|
+
help="Use enhanced Master Preview (requires FastAPI)",
|
|
372
|
+
)
|
|
345
373
|
|
|
346
374
|
# Parse the arguments provided by the user
|
|
347
375
|
args = parser.parse_args()
|
|
@@ -349,7 +377,7 @@ def main() -> None:
|
|
|
349
377
|
# Dispatch logic
|
|
350
378
|
if args.command == "serve":
|
|
351
379
|
# Invoke the serve function with parsed arguments
|
|
352
|
-
serve(args.file, args.port)
|
|
380
|
+
serve(args.file, args.port, args.master)
|
|
353
381
|
|
|
354
382
|
|
|
355
383
|
if __name__ == "__main__":
|
|
@@ -2,3 +2,8 @@
|
|
|
2
2
|
MermaidTrace integrations package.
|
|
3
3
|
Contains middleware and adapters for third-party frameworks.
|
|
4
4
|
"""
|
|
5
|
+
|
|
6
|
+
from .fastapi import MermaidTraceMiddleware
|
|
7
|
+
from .langchain import MermaidTraceCallbackHandler
|
|
8
|
+
|
|
9
|
+
__all__ = ["MermaidTraceMiddleware", "MermaidTraceCallbackHandler"]
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LangChain Integration Module for MermaidTrace.
|
|
3
|
+
|
|
4
|
+
This module provides a LangChain Callback Handler that allows you to automatically
|
|
5
|
+
generate Mermaid sequence diagrams for your LangChain chains, LLM calls, and tool usage.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
9
|
+
import uuid
|
|
10
|
+
|
|
11
|
+
from ..core.events import FlowEvent
|
|
12
|
+
from ..core.context import LogContext
|
|
13
|
+
from ..core.decorators import get_flow_logger
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from langchain_core.callbacks import BaseCallbackHandler
|
|
17
|
+
from langchain_core.outputs import LLMResult
|
|
18
|
+
else:
|
|
19
|
+
try:
|
|
20
|
+
from langchain_core.callbacks import BaseCallbackHandler
|
|
21
|
+
from langchain_core.outputs import LLMResult
|
|
22
|
+
except ImportError:
|
|
23
|
+
BaseCallbackHandler = object
|
|
24
|
+
LLMResult = Any
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MermaidTraceCallbackHandler(BaseCallbackHandler):
|
|
28
|
+
"""
|
|
29
|
+
LangChain Callback Handler that records execution flow as Mermaid sequence diagrams.
|
|
30
|
+
|
|
31
|
+
This handler intercepts LangChain events (Chain, LLM, Tool) and logs them as
|
|
32
|
+
FlowEvents, which are then processed by MermaidTrace to generate diagrams.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, host_name: str = "LangChain"):
|
|
36
|
+
"""
|
|
37
|
+
Initialize the callback handler.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
host_name (str): The name of the host participant in the diagram.
|
|
41
|
+
Defaults to "LangChain".
|
|
42
|
+
"""
|
|
43
|
+
if BaseCallbackHandler is object:
|
|
44
|
+
raise ImportError(
|
|
45
|
+
"langchain-core is required to use MermaidTraceCallbackHandler. "
|
|
46
|
+
"Install it with `pip install langchain-core`."
|
|
47
|
+
)
|
|
48
|
+
self.host_name = host_name
|
|
49
|
+
self.logger = get_flow_logger()
|
|
50
|
+
self._participant_stack: List[str] = []
|
|
51
|
+
|
|
52
|
+
def _get_current_source(self) -> str:
|
|
53
|
+
if self._participant_stack:
|
|
54
|
+
return self._participant_stack[-1]
|
|
55
|
+
return str(LogContext.get("current_participant", self.host_name))
|
|
56
|
+
|
|
57
|
+
def on_chain_start(
|
|
58
|
+
self,
|
|
59
|
+
serialized: Optional[Dict[str, Any]],
|
|
60
|
+
inputs: Dict[str, Any],
|
|
61
|
+
**kwargs: Any,
|
|
62
|
+
) -> None:
|
|
63
|
+
"""Run when chain starts running."""
|
|
64
|
+
target = (
|
|
65
|
+
(serialized.get("name") if serialized else None)
|
|
66
|
+
or kwargs.get("name")
|
|
67
|
+
or "Chain"
|
|
68
|
+
)
|
|
69
|
+
source = self._get_current_source()
|
|
70
|
+
|
|
71
|
+
event = FlowEvent(
|
|
72
|
+
source=source,
|
|
73
|
+
target=target,
|
|
74
|
+
action="Run Chain",
|
|
75
|
+
message=f"Start Chain: {target}",
|
|
76
|
+
trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
|
|
77
|
+
params=str(inputs),
|
|
78
|
+
)
|
|
79
|
+
self.logger.info(
|
|
80
|
+
f"{source} -> {target}: {event.action}", extra={"flow_event": event}
|
|
81
|
+
)
|
|
82
|
+
self._participant_stack.append(target)
|
|
83
|
+
|
|
84
|
+
def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> None:
|
|
85
|
+
"""Run when chain ends running."""
|
|
86
|
+
if not self._participant_stack:
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
target = self._participant_stack.pop()
|
|
90
|
+
source = self._get_current_source()
|
|
91
|
+
|
|
92
|
+
event = FlowEvent(
|
|
93
|
+
source=target,
|
|
94
|
+
target=source,
|
|
95
|
+
action="Finish Chain",
|
|
96
|
+
message="Chain Complete",
|
|
97
|
+
trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
|
|
98
|
+
result=str(outputs),
|
|
99
|
+
is_return=True,
|
|
100
|
+
)
|
|
101
|
+
self.logger.info(
|
|
102
|
+
f"{target} -> {source}: {event.action}", extra={"flow_event": event}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def on_llm_start(
|
|
106
|
+
self, serialized: Optional[Dict[str, Any]], prompts: List[str], **kwargs: Any
|
|
107
|
+
) -> None:
|
|
108
|
+
"""Run when LLM starts running."""
|
|
109
|
+
target = (serialized.get("name") if serialized else None) or "LLM"
|
|
110
|
+
source = self._get_current_source()
|
|
111
|
+
|
|
112
|
+
event = FlowEvent(
|
|
113
|
+
source=source,
|
|
114
|
+
target=target,
|
|
115
|
+
action="Prompt",
|
|
116
|
+
message="LLM Request",
|
|
117
|
+
trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
|
|
118
|
+
params=str(prompts),
|
|
119
|
+
)
|
|
120
|
+
self.logger.info(
|
|
121
|
+
f"{source} -> {target}: {event.action}", extra={"flow_event": event}
|
|
122
|
+
)
|
|
123
|
+
self._participant_stack.append(target)
|
|
124
|
+
|
|
125
|
+
def on_chat_model_start(
|
|
126
|
+
self,
|
|
127
|
+
serialized: Optional[Dict[str, Any]],
|
|
128
|
+
messages: List[List[Any]],
|
|
129
|
+
**kwargs: Any,
|
|
130
|
+
) -> None:
|
|
131
|
+
"""Run when Chat Model starts running."""
|
|
132
|
+
target = (serialized.get("name") if serialized else None) or "ChatModel"
|
|
133
|
+
source = self._get_current_source()
|
|
134
|
+
|
|
135
|
+
event = FlowEvent(
|
|
136
|
+
source=source,
|
|
137
|
+
target=target,
|
|
138
|
+
action="Chat",
|
|
139
|
+
message="ChatModel Request",
|
|
140
|
+
trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
|
|
141
|
+
params=str(messages),
|
|
142
|
+
)
|
|
143
|
+
self.logger.info(
|
|
144
|
+
f"{source} -> {target}: {event.action}", extra={"flow_event": event}
|
|
145
|
+
)
|
|
146
|
+
self._participant_stack.append(target)
|
|
147
|
+
|
|
148
|
+
def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
|
|
149
|
+
"""Run when LLM ends running."""
|
|
150
|
+
if not self._participant_stack:
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
source = self._participant_stack.pop()
|
|
154
|
+
target = self._get_current_source()
|
|
155
|
+
|
|
156
|
+
event = FlowEvent(
|
|
157
|
+
source=source,
|
|
158
|
+
target=target,
|
|
159
|
+
action="Response",
|
|
160
|
+
message="LLM/Chat Completion",
|
|
161
|
+
trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
|
|
162
|
+
result=str(response.generations),
|
|
163
|
+
is_return=True,
|
|
164
|
+
)
|
|
165
|
+
self.logger.info(
|
|
166
|
+
f"{source} -> {target}: {event.action}", extra={"flow_event": event}
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def on_llm_error(self, error: BaseException, **kwargs: Any) -> None:
|
|
170
|
+
"""Run when LLM errors."""
|
|
171
|
+
if not self._participant_stack:
|
|
172
|
+
return
|
|
173
|
+
target = self._participant_stack.pop()
|
|
174
|
+
source = self._get_current_source()
|
|
175
|
+
event = FlowEvent(
|
|
176
|
+
source=target,
|
|
177
|
+
target=source,
|
|
178
|
+
action="Error",
|
|
179
|
+
message=f"LLM Error: {type(error).__name__}",
|
|
180
|
+
trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
|
|
181
|
+
is_error=True,
|
|
182
|
+
error_message=str(error),
|
|
183
|
+
is_return=True,
|
|
184
|
+
)
|
|
185
|
+
self.logger.info(
|
|
186
|
+
f"{target} -> {source}: {event.action}", extra={"flow_event": event}
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def on_retriever_start(
|
|
190
|
+
self,
|
|
191
|
+
serialized: Optional[Dict[str, Any]],
|
|
192
|
+
query: str,
|
|
193
|
+
**kwargs: Any,
|
|
194
|
+
) -> None:
|
|
195
|
+
"""Run when Retriever starts running."""
|
|
196
|
+
target = (serialized.get("name") if serialized else None) or "Retriever"
|
|
197
|
+
source = self._get_current_source()
|
|
198
|
+
|
|
199
|
+
event = FlowEvent(
|
|
200
|
+
source=source,
|
|
201
|
+
target=target,
|
|
202
|
+
action="Retrieve",
|
|
203
|
+
message=f"Query: {query[:50]}...",
|
|
204
|
+
trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
|
|
205
|
+
params=query,
|
|
206
|
+
)
|
|
207
|
+
self.logger.info(
|
|
208
|
+
f"{source} -> {target}: {event.action}", extra={"flow_event": event}
|
|
209
|
+
)
|
|
210
|
+
self._participant_stack.append(target)
|
|
211
|
+
|
|
212
|
+
def on_retriever_end(self, documents: List[Any], **kwargs: Any) -> Any: # type: ignore[override]
|
|
213
|
+
"""Run when Retriever ends running."""
|
|
214
|
+
if not self._participant_stack:
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
target = self._participant_stack.pop()
|
|
218
|
+
source = self._get_current_source()
|
|
219
|
+
|
|
220
|
+
event = FlowEvent(
|
|
221
|
+
source=target,
|
|
222
|
+
target=source,
|
|
223
|
+
action="Documents",
|
|
224
|
+
message=f"Retrieved {len(documents)} docs",
|
|
225
|
+
trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
|
|
226
|
+
result=f"Count: {len(documents)}",
|
|
227
|
+
is_return=True,
|
|
228
|
+
)
|
|
229
|
+
self.logger.info(
|
|
230
|
+
f"{target} -> {source}: {event.action}", extra={"flow_event": event}
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
def on_tool_start(
|
|
234
|
+
self, serialized: Optional[Dict[str, Any]], input_str: str, **kwargs: Any
|
|
235
|
+
) -> None:
|
|
236
|
+
"""Run when tool starts running."""
|
|
237
|
+
target = (serialized.get("name") if serialized else None) or "Tool"
|
|
238
|
+
source = self._get_current_source()
|
|
239
|
+
|
|
240
|
+
event = FlowEvent(
|
|
241
|
+
source=source,
|
|
242
|
+
target=target,
|
|
243
|
+
action="Call Tool",
|
|
244
|
+
message=f"Tool: {target}",
|
|
245
|
+
trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
|
|
246
|
+
params=input_str,
|
|
247
|
+
)
|
|
248
|
+
self.logger.info(
|
|
249
|
+
f"{source} -> {target}: {event.action}", extra={"flow_event": event}
|
|
250
|
+
)
|
|
251
|
+
self._participant_stack.append(target)
|
|
252
|
+
|
|
253
|
+
def on_tool_end(self, output: Any, **kwargs: Any) -> None:
|
|
254
|
+
"""Run when tool ends running."""
|
|
255
|
+
if not self._participant_stack:
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
target = self._participant_stack.pop()
|
|
259
|
+
source = self._get_current_source()
|
|
260
|
+
|
|
261
|
+
event = FlowEvent(
|
|
262
|
+
source=target,
|
|
263
|
+
target=source,
|
|
264
|
+
action="Finish Tool",
|
|
265
|
+
message="Tool Complete",
|
|
266
|
+
trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
|
|
267
|
+
result=str(output),
|
|
268
|
+
is_return=True,
|
|
269
|
+
)
|
|
270
|
+
self.logger.info(
|
|
271
|
+
f"{target} -> {source}: {event.action}", extra={"flow_event": event}
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def on_chain_error(self, error: BaseException, **kwargs: Any) -> None:
|
|
275
|
+
"""Run when chain errors."""
|
|
276
|
+
if not self._participant_stack:
|
|
277
|
+
return
|
|
278
|
+
target = self._participant_stack.pop()
|
|
279
|
+
source = self._get_current_source()
|
|
280
|
+
event = FlowEvent(
|
|
281
|
+
source=target,
|
|
282
|
+
target=source,
|
|
283
|
+
action="Error",
|
|
284
|
+
message=f"Chain Error: {type(error).__name__}",
|
|
285
|
+
trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
|
|
286
|
+
is_error=True,
|
|
287
|
+
error_message=str(error),
|
|
288
|
+
is_return=True,
|
|
289
|
+
)
|
|
290
|
+
self.logger.info(
|
|
291
|
+
f"{target} -> {source}: {event.action}", extra={"flow_event": event}
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
def on_tool_error(self, error: BaseException, **kwargs: Any) -> None:
|
|
295
|
+
"""Run when tool errors."""
|
|
296
|
+
if not self._participant_stack:
|
|
297
|
+
return
|
|
298
|
+
target = self._participant_stack.pop()
|
|
299
|
+
source = self._get_current_source()
|
|
300
|
+
event = FlowEvent(
|
|
301
|
+
source=target,
|
|
302
|
+
target=source,
|
|
303
|
+
action="Error",
|
|
304
|
+
message=f"Tool Error: {type(error).__name__}",
|
|
305
|
+
trace_id=LogContext.get("trace_id", str(uuid.uuid4())),
|
|
306
|
+
is_error=True,
|
|
307
|
+
error_message=str(error),
|
|
308
|
+
is_return=True,
|
|
309
|
+
)
|
|
310
|
+
self.logger.info(
|
|
311
|
+
f"{target} -> {source}: {event.action}", extra={"flow_event": event}
|
|
312
|
+
)
|
mermaid_trace/server.py
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced Web Server for MermaidTrace.
|
|
3
|
+
|
|
4
|
+
This module provides a robust, real-time preview server using FastAPI and Server-Sent Events (SSE).
|
|
5
|
+
It monitors .mmd files and pushes updates to the browser instantly.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import AsyncGenerator, Set, Any
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from fastapi import FastAPI, Request, HTTPException
|
|
16
|
+
from fastapi.responses import HTMLResponse, StreamingResponse
|
|
17
|
+
import uvicorn
|
|
18
|
+
|
|
19
|
+
HAS_SERVER_DEPS = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
HAS_SERVER_DEPS = False
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from watchdog.observers import Observer
|
|
25
|
+
from watchdog.events import FileSystemEventHandler
|
|
26
|
+
|
|
27
|
+
HAS_WATCHDOG = True
|
|
28
|
+
except ImportError:
|
|
29
|
+
HAS_WATCHDOG = False
|
|
30
|
+
|
|
31
|
+
app = FastAPI(title="MermaidTrace Preview Server")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Global state to manage connected clients for SSE
|
|
35
|
+
class ConnectionManager:
|
|
36
|
+
def __init__(self) -> None:
|
|
37
|
+
self.active_connections: Set[asyncio.Queue[dict[str, Any]]] = set()
|
|
38
|
+
|
|
39
|
+
async def subscribe(self) -> asyncio.Queue[dict[str, Any]]:
|
|
40
|
+
queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue()
|
|
41
|
+
self.active_connections.add(queue)
|
|
42
|
+
return queue
|
|
43
|
+
|
|
44
|
+
def unsubscribe(self, queue: asyncio.Queue[dict[str, Any]]) -> None:
|
|
45
|
+
self.active_connections.remove(queue)
|
|
46
|
+
|
|
47
|
+
async def broadcast(self, data: dict[str, Any]) -> None:
|
|
48
|
+
for queue in self.active_connections:
|
|
49
|
+
await queue.put(data)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
manager = ConnectionManager()
|
|
53
|
+
|
|
54
|
+
# HTML Template with enhanced UI
|
|
55
|
+
HTML_TEMPLATE = """
|
|
56
|
+
<!DOCTYPE html>
|
|
57
|
+
<html lang="en">
|
|
58
|
+
<head>
|
|
59
|
+
<meta charset="UTF-8">
|
|
60
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
61
|
+
<title>MermaidTrace Master Preview</title>
|
|
62
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
|
|
63
|
+
<script src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.1/dist/svg-pan-zoom.min.js"></script>
|
|
64
|
+
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
|
65
|
+
<style>
|
|
66
|
+
body { background-color: #f8fafc; }
|
|
67
|
+
.mermaid { background: white; }
|
|
68
|
+
#diagram-container {
|
|
69
|
+
height: calc(100vh - 10rem);
|
|
70
|
+
overflow: hidden;
|
|
71
|
+
position: relative;
|
|
72
|
+
background-image: radial-gradient(#e2e8f0 1px, transparent 1px);
|
|
73
|
+
background-size: 20px 20px;
|
|
74
|
+
display: flex;
|
|
75
|
+
flex-direction: column;
|
|
76
|
+
}
|
|
77
|
+
#svg-wrapper {
|
|
78
|
+
flex: 1;
|
|
79
|
+
width: 100%;
|
|
80
|
+
height: 100%;
|
|
81
|
+
cursor: grab;
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
justify-content: center;
|
|
85
|
+
}
|
|
86
|
+
#svg-wrapper:active { cursor: grabbing; }
|
|
87
|
+
.sidebar-item:hover { background-color: #e2e8f0; }
|
|
88
|
+
.sidebar-item.active { background-color: #3b82f6; color: white; }
|
|
89
|
+
/* Ensure mermaid SVG doesn't have max-width constraints */
|
|
90
|
+
#mermaid-graph {
|
|
91
|
+
width: 100%;
|
|
92
|
+
height: 100%;
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
}
|
|
97
|
+
#mermaid-graph svg {
|
|
98
|
+
max-width: none !important;
|
|
99
|
+
max-height: none !important;
|
|
100
|
+
}
|
|
101
|
+
</style>
|
|
102
|
+
</head>
|
|
103
|
+
<body class="flex flex-col h-screen">
|
|
104
|
+
<!-- Header -->
|
|
105
|
+
<header class="bg-white border-b border-gray-200 px-6 py-4 flex justify-between items-center shadow-sm">
|
|
106
|
+
<div class="flex items-center space-x-3">
|
|
107
|
+
<div class="bg-blue-600 text-white p-2 rounded-lg">
|
|
108
|
+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
109
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
|
110
|
+
</svg>
|
|
111
|
+
</div>
|
|
112
|
+
<h1 class="text-xl font-bold text-gray-800">MermaidTrace <span class="text-blue-600">Master</span></h1>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="flex items-center space-x-4">
|
|
115
|
+
<span id="status-badge" class="px-3 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800">Live Connected</span>
|
|
116
|
+
<button onclick="resetZoom()" class="text-gray-600 hover:text-blue-600 transition">
|
|
117
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg>
|
|
118
|
+
</button>
|
|
119
|
+
<button onclick="downloadSVG()" class="text-gray-600 hover:text-blue-600 transition">
|
|
120
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg>
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
</header>
|
|
124
|
+
|
|
125
|
+
<div class="flex flex-1 overflow-hidden">
|
|
126
|
+
<!-- Sidebar -->
|
|
127
|
+
<aside class="w-64 bg-white border-r border-gray-200 overflow-y-auto hidden md:block">
|
|
128
|
+
<div class="p-4 border-b border-gray-100">
|
|
129
|
+
<h2 class="text-xs font-semibold text-gray-500 uppercase tracking-wider">Trace Files</h2>
|
|
130
|
+
</div>
|
|
131
|
+
<nav id="file-list" class="p-2 space-y-1">
|
|
132
|
+
<!-- File items will be injected here -->
|
|
133
|
+
</nav>
|
|
134
|
+
</aside>
|
|
135
|
+
|
|
136
|
+
<!-- Main Content -->
|
|
137
|
+
<main class="flex-1 flex flex-col p-6 overflow-hidden">
|
|
138
|
+
<div class="mb-4 flex justify-between items-end">
|
|
139
|
+
<div>
|
|
140
|
+
<h2 id="current-filename" class="text-2xl font-bold text-gray-900">Select a file</h2>
|
|
141
|
+
<p id="last-updated" class="text-sm text-gray-500 mt-1">Ready to visualize</p>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div id="diagram-container" class="flex-1 bg-white rounded-xl shadow-inner border border-gray-200 overflow-hidden">
|
|
146
|
+
<div id="svg-wrapper">
|
|
147
|
+
<div class="mermaid" id="mermaid-graph">
|
|
148
|
+
sequenceDiagram
|
|
149
|
+
Note over Server, Client: Waiting for trace data...
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</main>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<script>
|
|
157
|
+
let panZoomInstance = null;
|
|
158
|
+
let currentFile = null;
|
|
159
|
+
|
|
160
|
+
function initMermaid() {
|
|
161
|
+
if (typeof mermaid === 'undefined') {
|
|
162
|
+
console.log("Waiting for mermaid...");
|
|
163
|
+
setTimeout(initMermaid, 100);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
mermaid.initialize({
|
|
168
|
+
startOnLoad: false,
|
|
169
|
+
theme: 'default',
|
|
170
|
+
securityLevel: 'loose',
|
|
171
|
+
useMaxWidth: false,
|
|
172
|
+
sequence: {
|
|
173
|
+
showSequenceNumbers: true,
|
|
174
|
+
useMaxWidth: false,
|
|
175
|
+
bottomMarginAdjustment: 1
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Initialize
|
|
180
|
+
updateFileList();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function loadFile(filename) {
|
|
184
|
+
if (!filename) return;
|
|
185
|
+
currentFile = filename;
|
|
186
|
+
|
|
187
|
+
// Update active state in sidebar
|
|
188
|
+
document.querySelectorAll('.sidebar-item').forEach(el => {
|
|
189
|
+
if (el.dataset.filename === filename) el.classList.add('active');
|
|
190
|
+
else el.classList.remove('active');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const response = await fetch(`/api/file?name=${encodeURIComponent(filename)}`);
|
|
195
|
+
const data = await response.json();
|
|
196
|
+
|
|
197
|
+
document.getElementById('current-filename').textContent = filename;
|
|
198
|
+
renderDiagram(data.content);
|
|
199
|
+
updateTimestamp();
|
|
200
|
+
} catch (err) {
|
|
201
|
+
console.error("Failed to load file:", err);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function renderDiagram(content) {
|
|
206
|
+
const graphDiv = document.getElementById('mermaid-graph');
|
|
207
|
+
graphDiv.removeAttribute('data-processed');
|
|
208
|
+
// Clean up previous SVG before rendering new one
|
|
209
|
+
graphDiv.innerHTML = content;
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
const { svg } = await mermaid.render('mermaid-svg-' + Date.now(), content);
|
|
213
|
+
graphDiv.innerHTML = svg;
|
|
214
|
+
setupPanZoom();
|
|
215
|
+
} catch (err) {
|
|
216
|
+
console.error("Mermaid render error:", err);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function setupPanZoom() {
|
|
221
|
+
if (panZoomInstance) {
|
|
222
|
+
panZoomInstance.destroy();
|
|
223
|
+
panZoomInstance = null;
|
|
224
|
+
}
|
|
225
|
+
const svg = document.querySelector('#mermaid-graph svg');
|
|
226
|
+
if (svg) {
|
|
227
|
+
// Ensure SVG takes up all available space for the pan-zoom container
|
|
228
|
+
svg.style.width = '100%';
|
|
229
|
+
svg.style.height = '100%';
|
|
230
|
+
svg.style.maxWidth = 'none';
|
|
231
|
+
svg.style.maxHeight = 'none';
|
|
232
|
+
|
|
233
|
+
// Clear explicit width/height attributes that might conflict with 'fit'
|
|
234
|
+
svg.removeAttribute('width');
|
|
235
|
+
svg.removeAttribute('height');
|
|
236
|
+
|
|
237
|
+
panZoomInstance = svgPanZoom(svg, {
|
|
238
|
+
zoomEnabled: true,
|
|
239
|
+
controlIconsEnabled: false,
|
|
240
|
+
fit: true,
|
|
241
|
+
center: true,
|
|
242
|
+
minZoom: 0.1,
|
|
243
|
+
maxZoom: 20,
|
|
244
|
+
zoomScaleSensitivity: 0.2
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Auto fit on window resize
|
|
248
|
+
window.addEventListener('resize', () => {
|
|
249
|
+
if (panZoomInstance) {
|
|
250
|
+
panZoomInstance.resize();
|
|
251
|
+
panZoomInstance.fit();
|
|
252
|
+
panZoomInstance.center();
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function resetZoom() {
|
|
259
|
+
if (panZoomInstance) {
|
|
260
|
+
panZoomInstance.fit();
|
|
261
|
+
panZoomInstance.center();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function updateTimestamp() {
|
|
266
|
+
const now = new Date();
|
|
267
|
+
document.getElementById('last-updated').textContent = `Last updated: ${now.toLocaleTimeString()}`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function updateFileList() {
|
|
271
|
+
const response = await fetch('/api/files');
|
|
272
|
+
const files = await response.json();
|
|
273
|
+
const list = document.getElementById('file-list');
|
|
274
|
+
list.innerHTML = '';
|
|
275
|
+
|
|
276
|
+
files.forEach(f => {
|
|
277
|
+
const item = document.createElement('a');
|
|
278
|
+
item.href = "#";
|
|
279
|
+
item.className = `sidebar-item block px-3 py-2 text-sm font-medium rounded-md transition ${f === currentFile ? 'active' : 'text-gray-700'}`;
|
|
280
|
+
item.dataset.filename = f;
|
|
281
|
+
item.textContent = f;
|
|
282
|
+
item.onclick = (e) => {
|
|
283
|
+
e.preventDefault();
|
|
284
|
+
loadFile(f);
|
|
285
|
+
};
|
|
286
|
+
list.appendChild(item);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
if (!currentFile && files.length > 0) {
|
|
290
|
+
loadFile(files[0]);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// SSE Connection for real-time updates
|
|
295
|
+
const eventSource = new EventSource("/events");
|
|
296
|
+
eventSource.onmessage = (event) => {
|
|
297
|
+
const data = JSON.parse(event.data);
|
|
298
|
+
if (data.type === "update" && data.filename === currentFile) {
|
|
299
|
+
console.log("File update received:", data.filename);
|
|
300
|
+
loadFile(data.filename);
|
|
301
|
+
} else if (data.type === "refresh_list") {
|
|
302
|
+
updateFileList();
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
eventSource.onerror = () => {
|
|
307
|
+
document.getElementById('status-badge').className = "px-3 py-1 rounded-full text-xs font-medium bg-red-100 text-red-800";
|
|
308
|
+
document.getElementById('status-badge').textContent = "Connection Lost";
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// Start Initialization
|
|
312
|
+
initMermaid();
|
|
313
|
+
|
|
314
|
+
function downloadSVG() {
|
|
315
|
+
const svg = document.querySelector('#diagram-container svg');
|
|
316
|
+
if (!svg) return;
|
|
317
|
+
const serializer = new XMLSerializer();
|
|
318
|
+
let source = serializer.serializeToString(svg);
|
|
319
|
+
if(!source.match(/^<svg[^>]+xmlns="http\\:\\/\\/www\\.w3\\.org\\/2000\\/svg"/)){
|
|
320
|
+
source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
|
|
321
|
+
}
|
|
322
|
+
if(!source.match(/^<svg[^>]+xmlns\\:xlink="http\\:\\/\\/www\\.w3\\.org\\/1999\\/xlink"/)){
|
|
323
|
+
source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
|
|
324
|
+
}
|
|
325
|
+
source = '<?xml version="1.0" standalone="no"?>\\r\\n' + source;
|
|
326
|
+
const url = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source);
|
|
327
|
+
const link = document.createElement("a");
|
|
328
|
+
link.href = url;
|
|
329
|
+
link.download = `${currentFile || 'diagram'}.svg`;
|
|
330
|
+
link.click();
|
|
331
|
+
}
|
|
332
|
+
</script>
|
|
333
|
+
</body>
|
|
334
|
+
</html>
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
# Global directory to watch
|
|
338
|
+
watch_dir: Path = Path(".")
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@app.get("/", response_class=HTMLResponse)
|
|
342
|
+
async def get_index() -> str:
|
|
343
|
+
return HTML_TEMPLATE
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@app.get("/api/files")
|
|
347
|
+
async def list_files() -> list[str]:
|
|
348
|
+
files = sorted([f.name for f in watch_dir.glob("*.mmd")])
|
|
349
|
+
return files
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@app.get("/api/file")
|
|
353
|
+
async def get_file_content(name: str) -> dict[str, str]:
|
|
354
|
+
file_path = watch_dir / name
|
|
355
|
+
if not file_path.exists() or not str(file_path).endswith(".mmd"):
|
|
356
|
+
raise HTTPException(status_code=404, detail="File not found")
|
|
357
|
+
return {"content": file_path.read_text(encoding="utf-8")}
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@app.get("/events")
|
|
361
|
+
async def sse_endpoint(request: Request) -> StreamingResponse:
|
|
362
|
+
async def event_generator() -> AsyncGenerator[str, None]:
|
|
363
|
+
queue = await manager.subscribe()
|
|
364
|
+
try:
|
|
365
|
+
while True:
|
|
366
|
+
if await request.is_disconnected():
|
|
367
|
+
break
|
|
368
|
+
data = await queue.get()
|
|
369
|
+
yield f"data: {json.dumps(data)}\\n\\n"
|
|
370
|
+
finally:
|
|
371
|
+
manager.unsubscribe(queue)
|
|
372
|
+
|
|
373
|
+
return StreamingResponse(event_generator(), media_type="text/event-stream")
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def run_server(directory: str, port: int = 8000) -> None:
|
|
377
|
+
global watch_dir
|
|
378
|
+
watch_dir = Path(directory).resolve()
|
|
379
|
+
|
|
380
|
+
if not HAS_SERVER_DEPS:
|
|
381
|
+
print("Error: FastAPI, Uvicorn are required for the enhanced server.")
|
|
382
|
+
print("Install them with: pip install fastapi uvicorn")
|
|
383
|
+
return
|
|
384
|
+
|
|
385
|
+
# Start Watchdog
|
|
386
|
+
if HAS_WATCHDOG:
|
|
387
|
+
|
|
388
|
+
class Handler(FileSystemEventHandler):
|
|
389
|
+
def on_modified(self, event: Any) -> None:
|
|
390
|
+
if not event.is_directory and event.src_path.endswith(".mmd"):
|
|
391
|
+
filename = os.path.basename(event.src_path)
|
|
392
|
+
asyncio.run(
|
|
393
|
+
manager.broadcast({"type": "update", "filename": filename})
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
def on_created(self, event: Any) -> None:
|
|
397
|
+
if not event.is_directory and event.src_path.endswith(".mmd"):
|
|
398
|
+
asyncio.run(manager.broadcast({"type": "refresh_list"}))
|
|
399
|
+
|
|
400
|
+
observer = Observer()
|
|
401
|
+
observer.schedule(Handler(), str(watch_dir), recursive=False)
|
|
402
|
+
observer.start()
|
|
403
|
+
print(f"[*] Watching directory: {watch_dir}")
|
|
404
|
+
|
|
405
|
+
print(f"[*] Starting Master Preview Server at http://localhost:{port}")
|
|
406
|
+
uvicorn.run(app, host="0.0.0.0", port=port, log_level="error")
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mermaid-trace
|
|
3
|
+
Version: 0.6.0.post0
|
|
4
|
+
Summary: Visualize your Python code execution flow as Mermaid Sequence Diagrams.
|
|
5
|
+
Project-URL: Documentation, https://github.com/xt765/mermaid-trace#readme
|
|
6
|
+
Project-URL: Changelog, https://github.com/xt765/mermaid-trace/blob/main/docs/en/CHANGELOG.md
|
|
7
|
+
Project-URL: Issues, https://github.com/xt765/mermaid-trace/issues
|
|
8
|
+
Project-URL: Source, https://github.com/xt765/mermaid-trace
|
|
9
|
+
Author-email: xt765 <xt765@foxmail.com>
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 xt765
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: asyncio,logging,mermaid,mermaid-trace,sequence-diagram,trace,visualization
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
43
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
44
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
45
|
+
Classifier: Topic :: System :: Logging
|
|
46
|
+
Requires-Python: >=3.10
|
|
47
|
+
Requires-Dist: typing-extensions>=4.0.0
|
|
48
|
+
Requires-Dist: watchdog>=2.0.0
|
|
49
|
+
Provides-Extra: all
|
|
50
|
+
Requires-Dist: fastapi>=0.100.0; extra == 'all'
|
|
51
|
+
Requires-Dist: langchain-core>=0.1.0; extra == 'all'
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: fastapi>=0.100.0; extra == 'dev'
|
|
54
|
+
Requires-Dist: httpx; extra == 'dev'
|
|
55
|
+
Requires-Dist: langchain-core>=0.1.0; extra == 'dev'
|
|
56
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
57
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
58
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
59
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
60
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
61
|
+
Provides-Extra: fastapi
|
|
62
|
+
Requires-Dist: fastapi>=0.100.0; extra == 'fastapi'
|
|
63
|
+
Provides-Extra: langchain
|
|
64
|
+
Requires-Dist: langchain-core>=0.1.0; extra == 'langchain'
|
|
65
|
+
Description-Content-Type: text/markdown
|
|
66
|
+
|
|
67
|
+
# MermaidTrace: Visualize Your Python Code Logic
|
|
68
|
+
|
|
69
|
+
**Stop drowning in cryptic logs. One line of code to transform complex execution logic into clear Mermaid sequence diagrams.**
|
|
70
|
+
|
|
71
|
+
🌐 **Language**: [English](README.md) | [中文](README_CN.md)
|
|
72
|
+
|
|
73
|
+
[](https://blog.csdn.net/Yunyi_Chi)
|
|
74
|
+
[](https://github.com/xt765/mermaid-trace)
|
|
75
|
+
[](https://gitee.com/xt765/mermaid-trace)
|
|
76
|
+
[](https://pypi.org/project/mermaid-trace/)
|
|
77
|
+
[](https://pypi.org/project/mermaid-trace/)
|
|
78
|
+
[](LICENSE)
|
|
79
|
+
[](https://github.com/xt765/mermaid-trace/actions/workflows/ci.yml)
|
|
80
|
+
[](https://codecov.io/gh/xt765/mermaid-trace)
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## ⚡️ Understand MermaidTrace in 5 Seconds
|
|
85
|
+
|
|
86
|
+
#### 1. Original Code (15+ lines)
|
|
87
|
+
```python
|
|
88
|
+
@trace(source="User", target="OrderSys")
|
|
89
|
+
def create_order(user_id, items):
|
|
90
|
+
# Complex business logic
|
|
91
|
+
if not check_inventory(items):
|
|
92
|
+
return "Out of Stock"
|
|
93
|
+
|
|
94
|
+
# Nested logic calls
|
|
95
|
+
price = calculate_price(items)
|
|
96
|
+
discount = get_discount(user_id)
|
|
97
|
+
final = price - discount
|
|
98
|
+
|
|
99
|
+
# External service interactions
|
|
100
|
+
res = pay_service.process(final)
|
|
101
|
+
if res.success:
|
|
102
|
+
update_stock(items)
|
|
103
|
+
send_notif(user_id)
|
|
104
|
+
return "Success"
|
|
105
|
+
return "Failed"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### 2. Auto-Generated Sequence Diagram
|
|
109
|
+
```mermaid
|
|
110
|
+
sequenceDiagram
|
|
111
|
+
autonumber
|
|
112
|
+
User->>OrderSys: create_order(user_id, items)
|
|
113
|
+
activate OrderSys
|
|
114
|
+
OrderSys->>Inventory: check_inventory(items)
|
|
115
|
+
Inventory-->>OrderSys: True
|
|
116
|
+
OrderSys->>Pricing: calculate_price(items)
|
|
117
|
+
Pricing-->>OrderSys: 100.0
|
|
118
|
+
OrderSys->>UserDB: get_discount(user_id)
|
|
119
|
+
UserDB-->>OrderSys: 5.0
|
|
120
|
+
OrderSys->>PayService: process(95.0)
|
|
121
|
+
activate PayService
|
|
122
|
+
PayService-->>OrderSys: success
|
|
123
|
+
deactivate PayService
|
|
124
|
+
OrderSys->>Inventory: update_stock(items)
|
|
125
|
+
OrderSys->>Notification: send_notif(user_id)
|
|
126
|
+
OrderSys-->>User: "Success"
|
|
127
|
+
deactivate OrderSys
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 🚀 Dynamic Demo & Online Tryout
|
|
133
|
+
|
|
134
|
+
### 🎬 Quick Demo
|
|
135
|
+
|
|
136
|
+

|
|
137
|
+
|
|
138
|
+
*(Master Preview: Multi-file browsing, live-reload, and interactive pan/zoom)*
|
|
139
|
+
|
|
140
|
+
```mermaid
|
|
141
|
+
sequenceDiagram
|
|
142
|
+
participant CLI as mermaid-trace CLI
|
|
143
|
+
participant App as Python App
|
|
144
|
+
participant Web as Live Preview
|
|
145
|
+
|
|
146
|
+
Note over CLI, Web: Enable Live Preview Mode
|
|
147
|
+
CLI->>Web: Start HTTP Server (localhost:8000)
|
|
148
|
+
App->>App: Run Logic (with @trace decorator)
|
|
149
|
+
App->>App: Auto-update flow.mmd
|
|
150
|
+
Web->>Web: File Change Detected (Hot Reload)
|
|
151
|
+
Web-->>CLI: Render Latest Diagram
|
|
152
|
+
```
|
|
153
|
+
*(From adding decorators to browser live preview in 10 seconds)*
|
|
154
|
+
|
|
155
|
+
### 🛠️ Try Online (Google Colab)
|
|
156
|
+
|
|
157
|
+
No local setup required. Experience core features in your browser:
|
|
158
|
+
|
|
159
|
+
[](https://colab.research.google.com/github/xt765/mermaid-trace/blob/main/examples/MermaidTrace_Demo.ipynb)
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 🎯 Why MermaidTrace? (Use Cases)
|
|
164
|
+
|
|
165
|
+
### 1. Master "Legacy" Codebases
|
|
166
|
+
**Pain**: Taking over a complex, undocumented legacy project with tangled function calls.
|
|
167
|
+
**Solution**: Add `@trace_class` or `@trace` to entry points and run the code once.
|
|
168
|
+
**Value**: Instantly generate a complete execution path map to understand the architecture.
|
|
169
|
+
|
|
170
|
+
### 2. Automated Technical Docs
|
|
171
|
+
**Pain**: Manual sequence diagrams are time-consuming and quickly become outdated.
|
|
172
|
+
**Solution**: Integrate MermaidTrace during development.
|
|
173
|
+
**Value**: Diagrams stay 100% in sync with your code logic automatically.
|
|
174
|
+
|
|
175
|
+
### 3. Debug Complex Recursion & Concurrency
|
|
176
|
+
**Pain**: Nested calls or async tasks produce interleaved logs that are impossible to read.
|
|
177
|
+
**Solution**: Use built-in async support and intelligent collapsing.
|
|
178
|
+
**Value**: Visualize recursion depth and concurrency flow to pinpoint logic bottlenecks.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 🚀 Quick Start in 3 Steps
|
|
183
|
+
|
|
184
|
+
### 1. Install
|
|
185
|
+
```bash
|
|
186
|
+
pip install mermaid-trace
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### 2. Add Decorators
|
|
190
|
+
```python
|
|
191
|
+
from mermaid_trace import trace, configure_flow
|
|
192
|
+
|
|
193
|
+
# Configure output file
|
|
194
|
+
configure_flow("my_flow.mmd")
|
|
195
|
+
|
|
196
|
+
@trace(source="User", target="AuthService")
|
|
197
|
+
def login(username):
|
|
198
|
+
return verify_db(username)
|
|
199
|
+
|
|
200
|
+
@trace(source="AuthService", target="DB")
|
|
201
|
+
def verify_db(username):
|
|
202
|
+
return True
|
|
203
|
+
|
|
204
|
+
login("admin")
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### 3. View Diagram
|
|
208
|
+
|
|
209
|
+
Run the built-in CLI tool to preview in real-time (with hot-reload):
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# Basic preview
|
|
213
|
+
mermaid-trace serve my_flow.mmd
|
|
214
|
+
|
|
215
|
+
# Master mode (Directory browsing, zoom, multi-file switching)
|
|
216
|
+
mermaid-trace serve . --master
|
|
217
|
+
# Or preview a specific file in Master mode
|
|
218
|
+
mermaid-trace serve .\mermaid_diagrams\examples\08-log-rotation.mmd --master
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### 🔗 LangChain Integration
|
|
222
|
+
Visualize LLM chains, agents, and RAG retrieval with a single handler:
|
|
223
|
+
```python
|
|
224
|
+
from mermaid_trace.integrations.langchain import MermaidTraceCallbackHandler
|
|
225
|
+
|
|
226
|
+
handler = MermaidTraceCallbackHandler(host_name="MyAIApp")
|
|
227
|
+
# Pass to any LangChain object
|
|
228
|
+
chain.invoke({"input": "..."}, config={"callbacks": [handler]})
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## ✨ Key Features
|
|
234
|
+
|
|
235
|
+
- **Decorator-Driven**: Simply add `@trace` or `@trace_interaction` to functions.
|
|
236
|
+
- **Auto-Instrumentation**: Use `@trace_class` to trace a whole class at once.
|
|
237
|
+
- **Third-Party Patching**: Use `patch_object` to trace calls inside external libraries.
|
|
238
|
+
- **Async Support**: Seamlessly works with `asyncio` coroutines and concurrency.
|
|
239
|
+
- **Enhanced Web UI**: Interactive preview server with file browsing, auto-reload, and pan/zoom support (use `--master`).
|
|
240
|
+
- **Intelligent Collapsing**: Automatically collapses repetitive calls and identifies loops.
|
|
241
|
+
- **FastAPI Integration**: Middleware for zero-config HTTP request tracing.
|
|
242
|
+
- **LangChain Integration**: Callback Handler for LLM chains and agent visualization.
|
|
243
|
+
- **Detailed Exceptions**: Captures full stack traces for errors, displayed in the diagram.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## 📚 Documentation
|
|
248
|
+
|
|
249
|
+
### Core Documentation
|
|
250
|
+
|
|
251
|
+
[User Guide](docs/en/USER_GUIDE.md) · [API Reference](docs/en/API.md) · [Contributing Guidelines](docs/en/CONTRIBUTING.md) · [Changelog](docs/en/CHANGELOG.md) · [License](LICENSE)
|
|
252
|
+
|
|
253
|
+
### Code Comment Documents
|
|
254
|
+
|
|
255
|
+
| Category | Links |
|
|
256
|
+
| :--- | :--- |
|
|
257
|
+
| **Core Modules** | [Context](docs/en/code_comments/src/mermaid_trace/core/context.md) · [Decorators](docs/en/code_comments/src/mermaid_trace/core/decorators.md) · [Events](docs/en/code_comments/src/mermaid_trace/core/events.md) · [Formatter](docs/en/code_comments/src/mermaid_trace/core/formatter.md) |
|
|
258
|
+
| **Handlers** | [Async Handler](docs/en/code_comments/src/mermaid_trace/handlers/async_handler.md) · [Mermaid Handler](docs/en/code_comments/src/mermaid_trace/handlers/mermaid_handler.md) |
|
|
259
|
+
| **Integrations** | [FastAPI](docs/en/code_comments/src/mermaid_trace/integrations/fastapi.md) |
|
|
260
|
+
| **Others** | [init](docs/en/code_comments/src/mermaid_trace/__init__.md) · [CLI](docs/en/code_comments/src/mermaid_trace/cli.md) |
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## 🤝 Contributing
|
|
265
|
+
|
|
266
|
+
We welcome contributions! Please see [CONTRIBUTING.md](docs/en/CONTRIBUTING.md) for details.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 📄 License
|
|
271
|
+
|
|
272
|
+
MIT
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
mermaid_trace/__init__.py,sha256=EnAsz6R6ag4dtabdHEg1wdB2k7V1uwmwpYTTC41GQX4,6721
|
|
2
|
-
mermaid_trace/cli.py,sha256=
|
|
2
|
+
mermaid_trace/cli.py,sha256=FCbm9kHf7B74bvkRv_k7stTeG7S7GqNSITmUQW1kJEA,16173
|
|
3
3
|
mermaid_trace/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
mermaid_trace/server.py,sha256=1nNIS6pNAn3_XCwuoUkKIOsq4GJK5o33QFzv-hh-IJY,15458
|
|
4
5
|
mermaid_trace/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
6
|
mermaid_trace/core/config.py,sha256=akTOc3m_bzcZ7e1Qtf1n5Ct9Dj8XPTTcWzATG3-qJHw,1890
|
|
6
7
|
mermaid_trace/core/context.py,sha256=wr-Ys3c6PsYCtbPUsQTrnaciSkP-fofxOewo4oZ27QM,8556
|
|
@@ -10,10 +11,11 @@ mermaid_trace/core/formatter.py,sha256=3KEq236E3GAj_j53MuBM2ndWm7Kx4KeywJWmoY5o5
|
|
|
10
11
|
mermaid_trace/core/utils.py,sha256=FagsMYLZcbmOgKmRo3v9tcRNVIpuc1YEKTAjcRk7keY,3410
|
|
11
12
|
mermaid_trace/handlers/async_handler.py,sha256=WmLcULXHasapt7-W0p_Eltjmwvlq6r6BS6pViuseZ4w,8252
|
|
12
13
|
mermaid_trace/handlers/mermaid_handler.py,sha256=Czar6JYSPTu5L1fQC0I8v1UpToD6aE_r96IkPpRSByw,6142
|
|
13
|
-
mermaid_trace/integrations/__init__.py,sha256=
|
|
14
|
+
mermaid_trace/integrations/__init__.py,sha256=stWAHIL1zv21foaXFXZ_3SLxJ8icN0YFGWOvvHERCDI,269
|
|
14
15
|
mermaid_trace/integrations/fastapi.py,sha256=6LRs4Z508l38ymfj5SP39vOV6OLfYLRkpxyL_SxvudM,9483
|
|
15
|
-
mermaid_trace
|
|
16
|
-
mermaid_trace-0.
|
|
17
|
-
mermaid_trace-0.
|
|
18
|
-
mermaid_trace-0.
|
|
19
|
-
mermaid_trace-0.
|
|
16
|
+
mermaid_trace/integrations/langchain.py,sha256=n1M2-OYNMsKiMpfGj04cs_dLyn2t5T9APmgAZsqqm5Q,10598
|
|
17
|
+
mermaid_trace-0.6.0.post0.dist-info/METADATA,sha256=kIvprKuj4jiOxpTaqumiqclh3euhw_7BkeZWg3i-YyE,10954
|
|
18
|
+
mermaid_trace-0.6.0.post0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
19
|
+
mermaid_trace-0.6.0.post0.dist-info/entry_points.txt,sha256=WS57KT_870v0A4B87QDjQUqJcddMQxbCQyYeczDAX34,57
|
|
20
|
+
mermaid_trace-0.6.0.post0.dist-info/licenses/LICENSE,sha256=BrBog1Etiq9PdWy0SVQNVByIMD9ss4Edz-R0oXt49zA,1062
|
|
21
|
+
mermaid_trace-0.6.0.post0.dist-info/RECORD,,
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: mermaid-trace
|
|
3
|
-
Version: 0.5.3.post0
|
|
4
|
-
Summary: Visualize your Python code execution flow as Mermaid Sequence Diagrams.
|
|
5
|
-
Project-URL: Documentation, https://github.com/xt765/mermaid-trace#readme
|
|
6
|
-
Project-URL: Changelog, https://github.com/xt765/mermaid-trace/blob/main/docs/en/CHANGELOG.md
|
|
7
|
-
Project-URL: Issues, https://github.com/xt765/mermaid-trace/issues
|
|
8
|
-
Project-URL: Source, https://github.com/xt765/mermaid-trace
|
|
9
|
-
Author-email: xt765 <xt765@foxmail.com>
|
|
10
|
-
License: MIT License
|
|
11
|
-
|
|
12
|
-
Copyright (c) 2026 xt765
|
|
13
|
-
|
|
14
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
-
in the Software without restriction, including without limitation the rights
|
|
17
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
-
furnished to do so, subject to the following conditions:
|
|
20
|
-
|
|
21
|
-
The above copyright notice and this permission notice shall be included in all
|
|
22
|
-
copies or substantial portions of the Software.
|
|
23
|
-
|
|
24
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
-
SOFTWARE.
|
|
31
|
-
License-File: LICENSE
|
|
32
|
-
Keywords: asyncio,logging,mermaid,mermaid-trace,sequence-diagram,trace,visualization
|
|
33
|
-
Classifier: Development Status :: 4 - Beta
|
|
34
|
-
Classifier: Intended Audience :: Developers
|
|
35
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
-
Classifier: Operating System :: OS Independent
|
|
37
|
-
Classifier: Programming Language :: Python
|
|
38
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
-
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
43
|
-
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
44
|
-
Classifier: Topic :: Software Development :: Debuggers
|
|
45
|
-
Classifier: Topic :: System :: Logging
|
|
46
|
-
Requires-Python: >=3.10
|
|
47
|
-
Requires-Dist: typing-extensions>=4.0.0
|
|
48
|
-
Requires-Dist: watchdog>=2.0.0
|
|
49
|
-
Provides-Extra: all
|
|
50
|
-
Requires-Dist: fastapi>=0.100.0; extra == 'all'
|
|
51
|
-
Provides-Extra: dev
|
|
52
|
-
Requires-Dist: fastapi>=0.100.0; extra == 'dev'
|
|
53
|
-
Requires-Dist: httpx; extra == 'dev'
|
|
54
|
-
Requires-Dist: mypy; extra == 'dev'
|
|
55
|
-
Requires-Dist: pytest; extra == 'dev'
|
|
56
|
-
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
57
|
-
Requires-Dist: pytest-cov; extra == 'dev'
|
|
58
|
-
Requires-Dist: ruff; extra == 'dev'
|
|
59
|
-
Provides-Extra: fastapi
|
|
60
|
-
Requires-Dist: fastapi>=0.100.0; extra == 'fastapi'
|
|
61
|
-
Description-Content-Type: text/markdown
|
|
62
|
-
|
|
63
|
-
# MermaidTrace: The Python Logger That Draws Diagrams
|
|
64
|
-
|
|
65
|
-
🌐 **Language**: [English](README.md) | [中文](README_CN.md)
|
|
66
|
-
|
|
67
|
-
[](https://pypi.org/project/mermaid-trace/)
|
|
68
|
-
[](https://pypi.org/project/mermaid-trace/)
|
|
69
|
-
[](LICENSE)
|
|
70
|
-
[](https://github.com/xt765/mermaid-trace/actions/workflows/ci.yml)
|
|
71
|
-
[](https://codecov.io/gh/xt765/mermaid-trace)
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
## 📋 Overview
|
|
76
|
-
|
|
77
|
-
**Stop reading logs. Start watching them.**
|
|
78
|
-
|
|
79
|
-
MermaidTrace is a specialized logging tool that automatically generates [Mermaid JS](https://mermaid.js.org/) sequence diagrams from your code execution. It's perfect for visualizing complex business logic, microservice interactions, or asynchronous flows.
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## 📚 Documentation
|
|
84
|
-
|
|
85
|
-
### Core Documentation
|
|
86
|
-
|
|
87
|
-
[User Guide](docs/en/USER_GUIDE.md) · [API Reference](docs/en/API.md) · [Contributing Guidelines](docs/en/CONTRIBUTING.md) · [Changelog](docs/en/CHANGELOG.md) · [License](docs/en/LICENSE)
|
|
88
|
-
|
|
89
|
-
### Code Comment Documents (Chinese)
|
|
90
|
-
|
|
91
|
-
| Category | Links |
|
|
92
|
-
| :--- | :--- |
|
|
93
|
-
| **Core Modules** | [Context](docs/zh/code_comments/src/mermaid_trace/core/context.md) · [Decorators](docs/zh/code_comments/src/mermaid_trace/core/decorators.md) · [Events](docs/zh/code_comments/src/mermaid_trace/core/events.md) · [Formatter](docs/zh/code_comments/src/mermaid_trace/core/formatter.md) |
|
|
94
|
-
| **Handlers** | [Async Handler](docs/zh/code_comments/src/mermaid_trace/handlers/async_handler.md) · [Mermaid Handler](docs/zh/code_comments/src/mermaid_trace/handlers/mermaid_handler.md) |
|
|
95
|
-
| **Integrations** | [FastAPI](docs/zh/code_comments/src/mermaid_trace/integrations/fastapi.md) |
|
|
96
|
-
| **Others** | [init](docs/zh/code_comments/src/mermaid_trace/__init__.md) · [CLI](docs/zh/code_comments/src/mermaid_trace/cli.md) |
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## ✨ Key Features
|
|
101
|
-
|
|
102
|
-
- **Decorator-Driven**: Just add `@trace` or `@trace_interaction` to your functions.
|
|
103
|
-
- **Auto-Instrumentation**: Use `@trace_class` to trace a whole class at once.
|
|
104
|
-
- **Third-Party Patching**: Use `patch_object` to trace calls inside external libraries.
|
|
105
|
-
- **Auto-Diagramming**: Generates `.mmd` files that can be viewed in VS Code, GitHub, or Mermaid Live Editor.
|
|
106
|
-
- **Async Support**: Works seamlessly with `asyncio` coroutines.
|
|
107
|
-
- **Context Inference**: Automatically tracks nested calls and infers `source` participants using `contextvars`.
|
|
108
|
-
- **Intelligent Collapsing**: Prevents diagram explosion by collapsing repetitive high-frequency calls and identifying recurring patterns (e.g., loops).
|
|
109
|
-
- **Detailed Exceptions**: Captures full stack traces for errors, displayed in interactive notes.
|
|
110
|
-
- **Simplified Objects**: Automatically cleans up memory addresses (e.g., `<__main__.Obj at 0x...>` -> `<Obj>`) and **groups consecutive identical items** in lists/tuples (e.g., `[<Obj> x 5]`) for cleaner diagrams.
|
|
111
|
-
- **Log Rotation**: Supports `RotatingMermaidFileHandler` for handling long-running systems by splitting logs based on size or time.
|
|
112
|
-
- **FastAPI Integration**: Includes middleware for zero-config HTTP request tracing, supporting distributed tracing via `X-Trace-ID` and `X-Source` headers.
|
|
113
|
-
- **CLI Tool**: Built-in viewer with live-reload to preview diagrams in your browser.
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## 🚀 Quick Start
|
|
118
|
-
|
|
119
|
-
### Installation
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
pip install mermaid-trace
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### Basic Usage
|
|
126
|
-
|
|
127
|
-
```python
|
|
128
|
-
from mermaid_trace import trace, configure_flow
|
|
129
|
-
import time
|
|
130
|
-
|
|
131
|
-
# 1. Configure output
|
|
132
|
-
# Recommendation: Store diagrams in a dedicated directory (e.g., mermaid_diagrams/)
|
|
133
|
-
configure_flow("mermaid_diagrams/my_flow.mmd", async_mode=True)
|
|
134
|
-
|
|
135
|
-
# 2. Add decorators
|
|
136
|
-
@trace(source="Client", target="PaymentService", action="Process Payment")
|
|
137
|
-
def process_payment(amount):
|
|
138
|
-
if check_balance(amount):
|
|
139
|
-
return "Success"
|
|
140
|
-
return "Failed"
|
|
141
|
-
|
|
142
|
-
@trace(source="PaymentService", target="Database", action="Check Balance")
|
|
143
|
-
def check_balance(amount):
|
|
144
|
-
return True
|
|
145
|
-
|
|
146
|
-
# 3. Run your code
|
|
147
|
-
process_payment(100)
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### Configuration
|
|
151
|
-
|
|
152
|
-
You can configure global settings via `configure_flow` or environment variables to control performance and behavior.
|
|
153
|
-
|
|
154
|
-
```python
|
|
155
|
-
configure_flow(
|
|
156
|
-
"flow.mmd",
|
|
157
|
-
overwrite=True, # Overwrite the file on each restart (default: True)
|
|
158
|
-
level=logging.DEBUG,
|
|
159
|
-
queue_size=5000, # Increase buffer for high-throughput
|
|
160
|
-
config_overrides={
|
|
161
|
-
"capture_args": False, # Disable arg capturing for max performance
|
|
162
|
-
"max_string_length": 100 # Increase string truncation limit
|
|
163
|
-
}
|
|
164
|
-
)
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
**Environment Variables:**
|
|
168
|
-
- `MERMAID_TRACE_CAPTURE_ARGS` (true/false)
|
|
169
|
-
- `MERMAID_TRACE_MAX_STRING_LENGTH` (int)
|
|
170
|
-
- `MERMAID_TRACE_MAX_ARG_DEPTH` (int)
|
|
171
|
-
- `MERMAID_TRACE_QUEUE_SIZE` (int)
|
|
172
|
-
|
|
173
|
-
### Nested Calls (Context Inference)
|
|
174
|
-
|
|
175
|
-
You don't need to specify `source` every time. MermaidTrace infers it from the current context.
|
|
176
|
-
|
|
177
|
-
```python
|
|
178
|
-
@trace(source="Client", target="API")
|
|
179
|
-
def main():
|
|
180
|
-
# Inside here, current participant is "API"
|
|
181
|
-
service_call()
|
|
182
|
-
|
|
183
|
-
@trace(target="Service") # source inferred as "API"
|
|
184
|
-
def service_call():
|
|
185
|
-
pass
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### FastAPI Integration
|
|
189
|
-
|
|
190
|
-
```python
|
|
191
|
-
from fastapi import FastAPI
|
|
192
|
-
from mermaid_trace.integrations.fastapi import MermaidTraceMiddleware
|
|
193
|
-
|
|
194
|
-
app = FastAPI()
|
|
195
|
-
app.add_middleware(MermaidTraceMiddleware, app_name="MyAPI")
|
|
196
|
-
|
|
197
|
-
@app.get("/")
|
|
198
|
-
async def root():
|
|
199
|
-
return {"message": "Hello World"}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
### CLI Viewer
|
|
203
|
-
|
|
204
|
-
Visualize your generated `.mmd` files instantly:
|
|
205
|
-
|
|
206
|
-
```bash
|
|
207
|
-
mermaid-trace serve my_flow.mmd
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### Examples
|
|
211
|
-
|
|
212
|
-
Check out the [examples/](examples/) directory for a complete set of demos covering all features:
|
|
213
|
-
- **[Basic Usage](examples/01_basic_usage.py)**: Decorators and class methods.
|
|
214
|
-
- **[Advanced Instrumentation](examples/02_advanced_instrumentation.py)**: `@trace_class` and `patch_object` for third-party libraries.
|
|
215
|
-
- **[Async & Concurrency](examples/03_async_concurrency.py)**: Tracing `asyncio` and concurrent tasks.
|
|
216
|
-
- **[Error Handling](examples/04_error_handling.py)**: Stack trace capture and error rendering.
|
|
217
|
-
- **[Intelligent Collapsing](examples/05_intelligent_collapsing.py)**: Keeping diagrams clean in loops.
|
|
218
|
-
- **[FastAPI Integration](examples/06_fastapi_integration.py)**: Middleware for web apps.
|
|
219
|
-
- **[Full Stack App](examples/07_full_stack_app.py)**: Comprehensive example with FastAPI, SQLAlchemy, and Pydantic.
|
|
220
|
-
- **[Log Rotation](examples/08-log-rotation.py)**: Handling long-running processes with file rotation.
|
|
221
|
-
|
|
222
|
-
---
|
|
223
|
-
|
|
224
|
-
## 🤝 Contributing
|
|
225
|
-
|
|
226
|
-
We welcome contributions! Please see [CONTRIBUTING.md](docs/en/CONTRIBUTING.md) for details.
|
|
227
|
-
|
|
228
|
-
---
|
|
229
|
-
|
|
230
|
-
## 📄 License
|
|
231
|
-
|
|
232
|
-
MIT
|
|
File without changes
|
{mermaid_trace-0.5.3.post0.dist-info → mermaid_trace-0.6.0.post0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{mermaid_trace-0.5.3.post0.dist-info → mermaid_trace-0.6.0.post0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|