x-ipe 1.0.23__py3-none-any.whl → 1.0.25__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.
- x_ipe/app.py +32 -1
- x_ipe/handlers/terminal_handlers.py +6 -0
- x_ipe/handlers/voice_handlers.py +5 -0
- x_ipe/resources/copilot-instructions.md +19 -6
- x_ipe/resources/skills/lesson-learned/SKILL.md +208 -0
- x_ipe/resources/skills/lesson-learned/references/examples.md +238 -0
- x_ipe/resources/skills/project-quality-board-management/SKILL.md +135 -298
- x_ipe/resources/skills/project-quality-board-management/references/evaluation-principles.md +213 -0
- x_ipe/resources/skills/project-quality-board-management/references/evaluation-procedures.md +214 -0
- x_ipe/resources/skills/project-quality-board-management/templates/quality-report.md +70 -18
- x_ipe/resources/skills/task-execution-guideline/SKILL.md +2 -2
- x_ipe/resources/skills/task-execution-guideline/templates/task-record.yaml +1 -1
- x_ipe/resources/skills/task-type-code-implementation/SKILL.md +72 -270
- x_ipe/resources/skills/task-type-code-implementation/references/implementation-guidelines.md +432 -0
- x_ipe/resources/skills/task-type-code-refactor-v2/SKILL.md +127 -353
- x_ipe/resources/skills/task-type-code-refactor-v2/references/refactoring-techniques.md +373 -0
- x_ipe/resources/skills/task-type-feature-breakdown/SKILL.md +31 -243
- x_ipe/resources/skills/task-type-feature-breakdown/references/breakdown-guidelines.md +330 -0
- x_ipe/resources/skills/task-type-feature-refinement/SKILL.md +27 -180
- x_ipe/resources/skills/task-type-feature-refinement/references/specification-writing-guide.md +267 -0
- x_ipe/resources/skills/task-type-idea-mockup/SKILL.md +38 -276
- x_ipe/resources/skills/task-type-idea-mockup/references/mockup-guidelines.md +299 -0
- x_ipe/resources/skills/task-type-idea-to-architecture/SKILL.md +20 -218
- x_ipe/resources/skills/task-type-idea-to-architecture/references/architecture-patterns.md +342 -0
- x_ipe/resources/skills/task-type-ideation/SKILL.md +10 -266
- x_ipe/resources/skills/task-type-ideation/references/folder-naming-guide.md +55 -0
- x_ipe/resources/skills/task-type-ideation/references/tool-usage-guide.md +236 -0
- x_ipe/resources/skills/task-type-ideation-v2/SKILL.md +488 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/examples.md +377 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/folder-naming-guide.md +74 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/tool-usage-guide.md +145 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/visualization-guide.md +160 -0
- x_ipe/resources/skills/task-type-ideation-v2/templates/idea-summary.md +86 -0
- x_ipe/resources/skills/task-type-refactoring-analysis/SKILL.md +83 -145
- x_ipe/resources/skills/task-type-refactoring-analysis/references/output-schema.md +172 -0
- x_ipe/resources/skills/task-type-technical-design/SKILL.md +28 -214
- x_ipe/resources/skills/task-type-technical-design/references/design-templates.md +422 -0
- x_ipe/resources/skills/task-type-test-generation/SKILL.md +47 -332
- x_ipe/resources/skills/task-type-test-generation/references/test-patterns.md +368 -0
- x_ipe/resources/skills/tool-tracing-creator/SKILL.md +312 -0
- x_ipe/resources/skills/tool-tracing-creator/references/examples.md +324 -0
- x_ipe/resources/skills/tool-tracing-instrumentation/SKILL.md +373 -0
- x_ipe/resources/skills/tool-tracing-instrumentation/references/examples.md +264 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/SKILL.md +486 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/10. example-gate-conditions.md +73 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/11. reference-quality-standards.md +127 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/2. reference-section-order.md +127 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/3. example-step-based-code-review.md +84 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/4. example-step-based-feature-implementation.md +113 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/5. example-function-based-validation.md +73 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/6. example-function-based-analysis.md +94 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/7. example-task-io-code-implementation.md +36 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/8. example-structured-summary.md +43 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/9. example-dor-dod.md +77 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/examples.md +429 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/skill-general-guidelines-v2.md +611 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-meta.md +153 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-task-based.md +324 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-task-category.md +109 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-tool.md +205 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-meta.md +334 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-task-based.md +279 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-tool.md +175 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-workflow-orchestration.md +329 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/SKILL.md +487 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/examples.md +377 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/folder-naming-guide.md +74 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/tool-usage-guide.md +145 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/visualization-guide.md +160 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/templates/idea-summary.md +86 -0
- x_ipe/routes/__init__.py +2 -0
- x_ipe/routes/ideas_routes.py +289 -0
- x_ipe/routes/kb_routes.py +80 -0
- x_ipe/routes/main_routes.py +18 -0
- x_ipe/routes/project_routes.py +7 -0
- x_ipe/routes/proxy_routes.py +10 -2
- x_ipe/routes/quality_evaluation_routes.py +193 -0
- x_ipe/routes/settings_routes.py +6 -0
- x_ipe/routes/tools_routes.py +6 -0
- x_ipe/routes/tracing_routes.py +232 -0
- x_ipe/routes/uiux_feedback_routes.py +50 -0
- x_ipe/services/__init__.py +5 -0
- x_ipe/services/config_service.py +6 -0
- x_ipe/services/file_service.py +20 -0
- x_ipe/services/homepage_service.py +160 -0
- x_ipe/services/ideas_service.py +535 -2
- x_ipe/services/kb_service.py +378 -0
- x_ipe/services/proxy_service.py +37 -7
- x_ipe/services/settings_service.py +13 -0
- x_ipe/services/skills_service.py +4 -0
- x_ipe/services/terminal_service.py +24 -0
- x_ipe/services/themes_service.py +4 -0
- x_ipe/services/tools_config_service.py +4 -0
- x_ipe/services/tracing_service.py +333 -0
- x_ipe/services/uiux_feedback_service.py +148 -1
- x_ipe/services/voice_input_service_v2.py +11 -0
- x_ipe/static/css/base.css +7 -0
- x_ipe/static/css/homepage-infinity.css +330 -0
- x_ipe/static/css/kb-core.css +301 -0
- x_ipe/static/css/quality-evaluation.css +345 -0
- x_ipe/static/css/sidebar.css +14 -4
- x_ipe/static/css/terminal.css +23 -0
- x_ipe/static/css/tracing-dashboard.css +796 -0
- x_ipe/static/css/uiux-feedback.css +7 -1
- x_ipe/static/css/workplace.css +636 -0
- x_ipe/static/img/homepage-infinity-loop.png +0 -0
- x_ipe/static/js/features/confirm-dialog.js +169 -0
- x_ipe/static/js/features/folder-view.js +742 -0
- x_ipe/static/js/features/homepage-infinity.js +314 -0
- x_ipe/static/js/features/kb-core.js +371 -0
- x_ipe/static/js/features/quality-evaluation.js +387 -0
- x_ipe/static/js/features/sidebar.js +255 -12
- x_ipe/static/js/features/tracing-dashboard.js +855 -0
- x_ipe/static/js/features/tracing-graph.js +1031 -0
- x_ipe/static/js/features/tree-drag.js +227 -0
- x_ipe/static/js/features/tree-search.js +228 -0
- x_ipe/static/js/features/workplace.js +661 -33
- x_ipe/static/js/init.js +76 -0
- x_ipe/static/js/terminal-v2.js +45 -14
- x_ipe/static/js/terminal.js +50 -49
- x_ipe/static/js/uiux-feedback.js +75 -16
- x_ipe/templates/base.html +24 -0
- x_ipe/templates/index.html +10 -1
- x_ipe/templates/knowledge-base.html +110 -0
- x_ipe/templates/workplace.html +4 -0
- x_ipe/tracing/__init__.py +37 -0
- x_ipe/tracing/buffer.py +135 -0
- x_ipe/tracing/context.py +125 -0
- x_ipe/tracing/decorator.py +288 -0
- x_ipe/tracing/middleware.py +197 -0
- x_ipe/tracing/parser.py +235 -0
- x_ipe/tracing/redactor.py +111 -0
- x_ipe/tracing/writer.py +122 -0
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/METADATA +2 -2
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/RECORD +138 -65
- x_ipe/app.py.bak +0 -1333
- x_ipe/resources/skills/x-ipe-skill-creator/SKILL.md +0 -329
- x_ipe/resources/skills/x-ipe-skill-creator/references/output-patterns.md +0 -169
- x_ipe/resources/skills/x-ipe-skill-creator/references/skill-structure.md +0 -162
- x_ipe/resources/skills/x-ipe-skill-creator/references/workflows.md +0 -110
- x_ipe/resources/skills/x-ipe-skill-creator/templates/references/examples.md +0 -113
- x_ipe/resources/skills/x-ipe-skill-creator/templates/skill-category-skill.md +0 -296
- x_ipe/resources/skills/x-ipe-skill-creator/templates/task-type-skill.md +0 -269
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/WHEEL +0 -0
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/entry_points.txt +0 -0
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FEATURE-023: Application Action Tracing - Core
|
|
3
|
+
|
|
4
|
+
@x_ipe_tracing decorator for automatic function tracing.
|
|
5
|
+
|
|
6
|
+
Provides a decorator that automatically logs function entry, exit,
|
|
7
|
+
return values, and exceptions with execution timing.
|
|
8
|
+
"""
|
|
9
|
+
import functools
|
|
10
|
+
import time
|
|
11
|
+
import asyncio
|
|
12
|
+
import inspect
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from typing import Callable, List, Optional, Any, Dict
|
|
15
|
+
|
|
16
|
+
from .context import TraceContext
|
|
17
|
+
from .buffer import TraceEntry
|
|
18
|
+
from .redactor import Redactor
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def x_ipe_tracing(
|
|
22
|
+
level: str = "INFO",
|
|
23
|
+
redact: Optional[List[str]] = None
|
|
24
|
+
) -> Callable:
|
|
25
|
+
"""
|
|
26
|
+
Decorator for automatic function tracing.
|
|
27
|
+
|
|
28
|
+
Logs function entry with parameters, exit with return value,
|
|
29
|
+
and any exceptions that occur. Automatically redacts sensitive data.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
level: Log level - "INFO", "DEBUG", or "SKIP"
|
|
33
|
+
redact: List of parameter names to redact (in addition to built-in patterns)
|
|
34
|
+
|
|
35
|
+
Usage:
|
|
36
|
+
@x_ipe_tracing(level="INFO", redact=["password"])
|
|
37
|
+
def create_user(email: str, password: str) -> dict:
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Decorated function that traces execution
|
|
42
|
+
"""
|
|
43
|
+
if level == "SKIP":
|
|
44
|
+
return lambda fn: fn # No-op decorator
|
|
45
|
+
|
|
46
|
+
redactor = Redactor(custom_fields=redact)
|
|
47
|
+
|
|
48
|
+
def decorator(func: Callable) -> Callable:
|
|
49
|
+
@functools.wraps(func)
|
|
50
|
+
def sync_wrapper(*args, **kwargs):
|
|
51
|
+
ctx = TraceContext.get_current()
|
|
52
|
+
if not ctx:
|
|
53
|
+
return func(*args, **kwargs) # No active trace
|
|
54
|
+
|
|
55
|
+
return _trace_call(ctx, func, args, kwargs, level, redactor)
|
|
56
|
+
|
|
57
|
+
@functools.wraps(func)
|
|
58
|
+
async def async_wrapper(*args, **kwargs):
|
|
59
|
+
ctx = TraceContext.get_current()
|
|
60
|
+
if not ctx:
|
|
61
|
+
return await func(*args, **kwargs) # No active trace
|
|
62
|
+
|
|
63
|
+
return await _trace_call_async(ctx, func, args, kwargs, level, redactor)
|
|
64
|
+
|
|
65
|
+
if asyncio.iscoroutinefunction(func):
|
|
66
|
+
return async_wrapper
|
|
67
|
+
return sync_wrapper
|
|
68
|
+
|
|
69
|
+
return decorator
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _extract_params(func: Callable, args: tuple, kwargs: dict) -> Dict[str, Any]:
|
|
73
|
+
"""
|
|
74
|
+
Extract function parameters as a dictionary.
|
|
75
|
+
|
|
76
|
+
Uses function signature to map positional args to parameter names.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
func: The function being called
|
|
80
|
+
args: Positional arguments
|
|
81
|
+
kwargs: Keyword arguments
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Dictionary of parameter names to values
|
|
85
|
+
"""
|
|
86
|
+
params = {}
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
sig = inspect.signature(func)
|
|
90
|
+
param_names = list(sig.parameters.keys())
|
|
91
|
+
|
|
92
|
+
# Map positional args
|
|
93
|
+
for i, arg in enumerate(args):
|
|
94
|
+
if i < len(param_names):
|
|
95
|
+
# Skip 'self' and 'cls' parameters
|
|
96
|
+
param_name = param_names[i]
|
|
97
|
+
if param_name not in ('self', 'cls'):
|
|
98
|
+
params[param_name] = arg
|
|
99
|
+
else:
|
|
100
|
+
params[f"arg_{i}"] = arg
|
|
101
|
+
|
|
102
|
+
# Add keyword args
|
|
103
|
+
params.update(kwargs)
|
|
104
|
+
except (ValueError, TypeError):
|
|
105
|
+
# Fallback if signature introspection fails
|
|
106
|
+
for i, arg in enumerate(args):
|
|
107
|
+
params[f"arg_{i}"] = arg
|
|
108
|
+
params.update(kwargs)
|
|
109
|
+
|
|
110
|
+
return params
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _safe_serialize(value: Any) -> Any:
|
|
114
|
+
"""
|
|
115
|
+
Safely serialize a value for logging.
|
|
116
|
+
|
|
117
|
+
Handles circular references and non-serializable objects.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
value: Value to serialize
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Serializable representation of the value
|
|
124
|
+
"""
|
|
125
|
+
try:
|
|
126
|
+
# Try direct serialization
|
|
127
|
+
import json
|
|
128
|
+
json.dumps(value, default=str)
|
|
129
|
+
return value
|
|
130
|
+
except (TypeError, ValueError, RecursionError):
|
|
131
|
+
# Fallback for complex objects
|
|
132
|
+
return str(value)[:500]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _trace_call(
|
|
136
|
+
ctx: TraceContext,
|
|
137
|
+
func: Callable,
|
|
138
|
+
args: tuple,
|
|
139
|
+
kwargs: dict,
|
|
140
|
+
level: str,
|
|
141
|
+
redactor: Redactor
|
|
142
|
+
) -> Any:
|
|
143
|
+
"""
|
|
144
|
+
Trace a synchronous function call.
|
|
145
|
+
|
|
146
|
+
Logs entry, executes the function, logs exit or exception.
|
|
147
|
+
"""
|
|
148
|
+
func_name = func.__name__
|
|
149
|
+
depth = ctx.push_call(func_name)
|
|
150
|
+
|
|
151
|
+
# Extract and redact parameters
|
|
152
|
+
params = _extract_params(func, args, kwargs)
|
|
153
|
+
redacted_params = redactor.redact(params)
|
|
154
|
+
|
|
155
|
+
# Log entry
|
|
156
|
+
ctx.buffer.add(TraceEntry(
|
|
157
|
+
timestamp=datetime.now(timezone.utc),
|
|
158
|
+
trace_id=ctx.trace_id,
|
|
159
|
+
level=level,
|
|
160
|
+
direction="→",
|
|
161
|
+
event_type="start_function",
|
|
162
|
+
function_name=func_name,
|
|
163
|
+
data=_safe_serialize(redacted_params),
|
|
164
|
+
depth=depth
|
|
165
|
+
))
|
|
166
|
+
|
|
167
|
+
start = time.perf_counter()
|
|
168
|
+
try:
|
|
169
|
+
result = func(*args, **kwargs)
|
|
170
|
+
duration = (time.perf_counter() - start) * 1000
|
|
171
|
+
|
|
172
|
+
# Redact and serialize return value
|
|
173
|
+
redacted_result = redactor.redact({"return": _safe_serialize(result)})
|
|
174
|
+
|
|
175
|
+
# Log success
|
|
176
|
+
ctx.buffer.add(TraceEntry(
|
|
177
|
+
timestamp=datetime.now(timezone.utc),
|
|
178
|
+
trace_id=ctx.trace_id,
|
|
179
|
+
level=level,
|
|
180
|
+
direction="←",
|
|
181
|
+
event_type="return_function",
|
|
182
|
+
function_name=func_name,
|
|
183
|
+
data=redacted_result,
|
|
184
|
+
duration_ms=duration,
|
|
185
|
+
depth=depth
|
|
186
|
+
))
|
|
187
|
+
return result
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
duration = (time.perf_counter() - start) * 1000
|
|
191
|
+
|
|
192
|
+
# Log error
|
|
193
|
+
ctx.buffer.add(TraceEntry(
|
|
194
|
+
timestamp=datetime.now(timezone.utc),
|
|
195
|
+
trace_id=ctx.trace_id,
|
|
196
|
+
level="ERROR",
|
|
197
|
+
direction="←",
|
|
198
|
+
event_type="exception",
|
|
199
|
+
function_name=func_name,
|
|
200
|
+
data={
|
|
201
|
+
"error": type(e).__name__,
|
|
202
|
+
"message": str(e)
|
|
203
|
+
},
|
|
204
|
+
duration_ms=duration,
|
|
205
|
+
depth=depth
|
|
206
|
+
))
|
|
207
|
+
raise
|
|
208
|
+
|
|
209
|
+
finally:
|
|
210
|
+
ctx.pop_call()
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
async def _trace_call_async(
|
|
214
|
+
ctx: TraceContext,
|
|
215
|
+
func: Callable,
|
|
216
|
+
args: tuple,
|
|
217
|
+
kwargs: dict,
|
|
218
|
+
level: str,
|
|
219
|
+
redactor: Redactor
|
|
220
|
+
) -> Any:
|
|
221
|
+
"""
|
|
222
|
+
Trace an asynchronous function call.
|
|
223
|
+
|
|
224
|
+
Logs entry, awaits the function, logs exit or exception.
|
|
225
|
+
"""
|
|
226
|
+
func_name = func.__name__
|
|
227
|
+
depth = ctx.push_call(func_name)
|
|
228
|
+
|
|
229
|
+
# Extract and redact parameters
|
|
230
|
+
params = _extract_params(func, args, kwargs)
|
|
231
|
+
redacted_params = redactor.redact(params)
|
|
232
|
+
|
|
233
|
+
# Log entry
|
|
234
|
+
ctx.buffer.add(TraceEntry(
|
|
235
|
+
timestamp=datetime.now(timezone.utc),
|
|
236
|
+
trace_id=ctx.trace_id,
|
|
237
|
+
level=level,
|
|
238
|
+
direction="→",
|
|
239
|
+
event_type="start_function",
|
|
240
|
+
function_name=func_name,
|
|
241
|
+
data=_safe_serialize(redacted_params),
|
|
242
|
+
depth=depth
|
|
243
|
+
))
|
|
244
|
+
|
|
245
|
+
start = time.perf_counter()
|
|
246
|
+
try:
|
|
247
|
+
result = await func(*args, **kwargs)
|
|
248
|
+
duration = (time.perf_counter() - start) * 1000
|
|
249
|
+
|
|
250
|
+
# Redact and serialize return value
|
|
251
|
+
redacted_result = redactor.redact({"return": _safe_serialize(result)})
|
|
252
|
+
|
|
253
|
+
# Log success
|
|
254
|
+
ctx.buffer.add(TraceEntry(
|
|
255
|
+
timestamp=datetime.now(timezone.utc),
|
|
256
|
+
trace_id=ctx.trace_id,
|
|
257
|
+
level=level,
|
|
258
|
+
direction="←",
|
|
259
|
+
event_type="return_function",
|
|
260
|
+
function_name=func_name,
|
|
261
|
+
data=redacted_result,
|
|
262
|
+
duration_ms=duration,
|
|
263
|
+
depth=depth
|
|
264
|
+
))
|
|
265
|
+
return result
|
|
266
|
+
|
|
267
|
+
except Exception as e:
|
|
268
|
+
duration = (time.perf_counter() - start) * 1000
|
|
269
|
+
|
|
270
|
+
# Log error
|
|
271
|
+
ctx.buffer.add(TraceEntry(
|
|
272
|
+
timestamp=datetime.now(timezone.utc),
|
|
273
|
+
trace_id=ctx.trace_id,
|
|
274
|
+
level="ERROR",
|
|
275
|
+
direction="←",
|
|
276
|
+
event_type="exception",
|
|
277
|
+
function_name=func_name,
|
|
278
|
+
data={
|
|
279
|
+
"error": type(e).__name__,
|
|
280
|
+
"message": str(e)
|
|
281
|
+
},
|
|
282
|
+
duration_ms=duration,
|
|
283
|
+
depth=depth
|
|
284
|
+
))
|
|
285
|
+
raise
|
|
286
|
+
|
|
287
|
+
finally:
|
|
288
|
+
ctx.pop_call()
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FEATURE-023: Application Action Tracing - Middleware
|
|
3
|
+
|
|
4
|
+
Flask middleware for automatic request tracing.
|
|
5
|
+
|
|
6
|
+
Creates TraceContext for each request when tracing is active,
|
|
7
|
+
then writes the trace log file after the request completes.
|
|
8
|
+
"""
|
|
9
|
+
import time
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from flask import Flask, request, g
|
|
13
|
+
from typing import List
|
|
14
|
+
|
|
15
|
+
from .context import TraceContext
|
|
16
|
+
from .writer import TraceLogWriter
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# APIs to ignore (to avoid infinite loops and noise)
|
|
20
|
+
IGNORED_API_PREFIXES = (
|
|
21
|
+
'/api/tracing/', # Tracing APIs themselves
|
|
22
|
+
'/static/', # Static files
|
|
23
|
+
'/socket.io/', # WebSocket
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def init_tracing_middleware(app: Flask) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Initialize tracing middleware for a Flask app.
|
|
30
|
+
|
|
31
|
+
Registers before_request and after_request hooks that:
|
|
32
|
+
- Check if tracing is active
|
|
33
|
+
- Start TraceContext before each request
|
|
34
|
+
- End TraceContext and write log file after each request
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
app: Flask application instance
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
@app.before_request
|
|
41
|
+
def start_trace():
|
|
42
|
+
"""Start trace context if tracing is active."""
|
|
43
|
+
# Skip hardcoded ignored paths (system paths)
|
|
44
|
+
if request.path.startswith(IGNORED_API_PREFIXES):
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
# Check if tracing is active and get config
|
|
48
|
+
project_root = app.config.get('PROJECT_ROOT', '.')
|
|
49
|
+
active, ignored_apis = _get_tracing_config(project_root)
|
|
50
|
+
|
|
51
|
+
if not active:
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
# Skip user-configured ignored APIs
|
|
55
|
+
if _is_path_ignored(request.path, ignored_apis):
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# Start trace context
|
|
59
|
+
api_name = f"{request.method} {request.path}"
|
|
60
|
+
TraceContext.start_trace(api_name)
|
|
61
|
+
g.trace_start_time = time.perf_counter()
|
|
62
|
+
|
|
63
|
+
@app.after_request
|
|
64
|
+
def end_trace(response):
|
|
65
|
+
"""End trace context and write log file."""
|
|
66
|
+
# Check if we started a trace
|
|
67
|
+
if not hasattr(g, 'trace_start_time'):
|
|
68
|
+
return response
|
|
69
|
+
|
|
70
|
+
# End the trace
|
|
71
|
+
buffer = TraceContext.end_trace()
|
|
72
|
+
if buffer is None:
|
|
73
|
+
return response
|
|
74
|
+
|
|
75
|
+
# Determine status from response
|
|
76
|
+
status = "SUCCESS" if response.status_code < 400 else "ERROR"
|
|
77
|
+
|
|
78
|
+
# Get log path from config
|
|
79
|
+
project_root = app.config.get('PROJECT_ROOT', '.')
|
|
80
|
+
log_path = _get_trace_log_path(project_root)
|
|
81
|
+
|
|
82
|
+
# Write trace to file
|
|
83
|
+
writer = TraceLogWriter(log_path)
|
|
84
|
+
writer.write(buffer, status)
|
|
85
|
+
|
|
86
|
+
return response
|
|
87
|
+
|
|
88
|
+
@app.teardown_request
|
|
89
|
+
def cleanup_trace(exception=None):
|
|
90
|
+
"""Clean up trace context on error."""
|
|
91
|
+
if exception is not None:
|
|
92
|
+
buffer = TraceContext.end_trace()
|
|
93
|
+
if buffer is not None:
|
|
94
|
+
project_root = app.config.get('PROJECT_ROOT', '.')
|
|
95
|
+
log_path = _get_trace_log_path(project_root)
|
|
96
|
+
writer = TraceLogWriter(log_path)
|
|
97
|
+
writer.write(buffer, "ERROR")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _get_tracing_config(project_root: str) -> tuple:
|
|
101
|
+
"""
|
|
102
|
+
Get tracing configuration including active status and ignored APIs.
|
|
103
|
+
|
|
104
|
+
Reads from tools.json to check tracing_enabled, tracing_stop_at,
|
|
105
|
+
and tracing_ignored_apis.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
project_root: Path to project root
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Tuple of (is_active: bool, ignored_apis: List[str])
|
|
112
|
+
"""
|
|
113
|
+
import json
|
|
114
|
+
|
|
115
|
+
tools_path = Path(project_root) / "x-ipe-docs" / "config" / "tools.json"
|
|
116
|
+
if not tools_path.exists():
|
|
117
|
+
return False, []
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
with open(tools_path) as f:
|
|
121
|
+
config = json.load(f)
|
|
122
|
+
except (json.JSONDecodeError, IOError):
|
|
123
|
+
return False, []
|
|
124
|
+
|
|
125
|
+
ignored_apis = config.get("tracing_ignored_apis", [])
|
|
126
|
+
|
|
127
|
+
# Check if explicitly enabled
|
|
128
|
+
if config.get("tracing_enabled", False):
|
|
129
|
+
return True, ignored_apis
|
|
130
|
+
|
|
131
|
+
# Check if stop_at is in the future
|
|
132
|
+
stop_at = config.get("tracing_stop_at")
|
|
133
|
+
if stop_at:
|
|
134
|
+
try:
|
|
135
|
+
# Keep timezone-aware for proper comparison
|
|
136
|
+
stop_time = datetime.fromisoformat(
|
|
137
|
+
stop_at.replace("Z", "+00:00")
|
|
138
|
+
)
|
|
139
|
+
if datetime.now(timezone.utc) < stop_time:
|
|
140
|
+
return True, ignored_apis
|
|
141
|
+
except (ValueError, AttributeError):
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
return False, ignored_apis
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _is_path_ignored(path: str, ignored_apis: List[str]) -> bool:
|
|
148
|
+
"""
|
|
149
|
+
Check if a request path matches any user-configured ignored API patterns.
|
|
150
|
+
|
|
151
|
+
Supports exact matches and prefix matches (patterns ending with *).
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
path: Request path (e.g., "/api/project/structure")
|
|
155
|
+
ignored_apis: List of API patterns to ignore
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
True if path should be ignored
|
|
159
|
+
"""
|
|
160
|
+
for pattern in ignored_apis:
|
|
161
|
+
if pattern.endswith('*'):
|
|
162
|
+
# Prefix match
|
|
163
|
+
if path.startswith(pattern[:-1]):
|
|
164
|
+
return True
|
|
165
|
+
else:
|
|
166
|
+
# Exact match
|
|
167
|
+
if path == pattern:
|
|
168
|
+
return True
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _get_trace_log_path(project_root: str) -> str:
|
|
173
|
+
"""
|
|
174
|
+
Get the trace log directory path.
|
|
175
|
+
|
|
176
|
+
Reads from tools.json or uses default.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
project_root: Path to project root
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Absolute path to trace log directory
|
|
183
|
+
"""
|
|
184
|
+
import json
|
|
185
|
+
|
|
186
|
+
tools_path = Path(project_root) / "x-ipe-docs" / "config" / "tools.json"
|
|
187
|
+
log_path = "instance/traces/"
|
|
188
|
+
|
|
189
|
+
if tools_path.exists():
|
|
190
|
+
try:
|
|
191
|
+
with open(tools_path) as f:
|
|
192
|
+
config = json.load(f)
|
|
193
|
+
log_path = config.get("tracing_log_path", log_path)
|
|
194
|
+
except (json.JSONDecodeError, IOError):
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
return str(Path(project_root) / log_path)
|