mantisdk 0.1.1__py3-none-any.whl → 0.1.2__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.
- mantisdk/__init__.py +1 -1
- mantisdk/tracing/__init__.py +57 -0
- mantisdk/tracing/api.py +546 -0
- mantisdk/tracing/attributes.py +191 -0
- mantisdk/tracing/exporters/__init__.py +10 -0
- mantisdk/tracing/exporters/insight.py +202 -0
- mantisdk/tracing/init.py +371 -0
- mantisdk/tracing/instrumentors/__init__.py +15 -0
- mantisdk/tracing/instrumentors/claude_agent_sdk.py +591 -0
- mantisdk/tracing/instrumentors/instrumentation_principles.md +289 -0
- mantisdk/tracing/instrumentors/registry.py +313 -0
- {mantisdk-0.1.1.dist-info → mantisdk-0.1.2.dist-info}/METADATA +1 -1
- {mantisdk-0.1.1.dist-info → mantisdk-0.1.2.dist-info}/RECORD +16 -6
- {mantisdk-0.1.1.dist-info → mantisdk-0.1.2.dist-info}/WHEEL +0 -0
- {mantisdk-0.1.1.dist-info → mantisdk-0.1.2.dist-info}/entry_points.txt +0 -0
- {mantisdk-0.1.1.dist-info → mantisdk-0.1.2.dist-info}/licenses/LICENSE +0 -0
mantisdk/__init__.py
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Copyright (c) Metis. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Standalone tracing module for MantisDK.
|
|
4
|
+
|
|
5
|
+
This module provides a "one-liner" entry point for instrumenting applications
|
|
6
|
+
with OpenTelemetry, compatible with OpenInference and AgentOps ecosystems,
|
|
7
|
+
while natively supporting export to Mantis Insight.
|
|
8
|
+
|
|
9
|
+
Example usage::
|
|
10
|
+
|
|
11
|
+
import mantisdk.tracing_claude as tracing
|
|
12
|
+
|
|
13
|
+
# Auto-detect Insight from environment variables
|
|
14
|
+
tracing.init()
|
|
15
|
+
|
|
16
|
+
# Use context managers for manual spans
|
|
17
|
+
with tracing.trace("my-workflow"):
|
|
18
|
+
with tracing.span("step-1"):
|
|
19
|
+
do_work()
|
|
20
|
+
|
|
21
|
+
# Use decorators
|
|
22
|
+
@tracing.trace
|
|
23
|
+
def my_function():
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
# Ensure spans are flushed on shutdown
|
|
27
|
+
tracing.shutdown()
|
|
28
|
+
|
|
29
|
+
Environment variables for Insight auto-detect:
|
|
30
|
+
- INSIGHT_HOST: The Insight server URL (e.g., "https://insight.withmetis.ai")
|
|
31
|
+
- INSIGHT_PUBLIC_KEY: The public key for authentication (pk-lf-...)
|
|
32
|
+
- INSIGHT_SECRET_KEY: The secret key for authentication (sk-lf-...)
|
|
33
|
+
- INSIGHT_OTLP_ENDPOINT: Optional override for the OTLP endpoint
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from .init import init, instrument, shutdown, flush
|
|
37
|
+
from .api import trace, span, tool, atrace, aspan
|
|
38
|
+
|
|
39
|
+
# Re-export exporter factory for explicit configuration
|
|
40
|
+
from .exporters.insight import insight as insight_exporter
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Initialization
|
|
44
|
+
"init",
|
|
45
|
+
"instrument",
|
|
46
|
+
"shutdown",
|
|
47
|
+
"flush",
|
|
48
|
+
# Context managers (sync)
|
|
49
|
+
"trace",
|
|
50
|
+
"span",
|
|
51
|
+
"tool",
|
|
52
|
+
# Context managers (async)
|
|
53
|
+
"atrace",
|
|
54
|
+
"aspan",
|
|
55
|
+
# Exporter factory
|
|
56
|
+
"insight_exporter",
|
|
57
|
+
]
|
mantisdk/tracing/api.py
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
# Copyright (c) Metis. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Context managers and decorators for MantisDK tracing.
|
|
4
|
+
|
|
5
|
+
This module provides the user-facing API for creating spans:
|
|
6
|
+
- trace(): Create a root span (trace)
|
|
7
|
+
- span(): Create a child span
|
|
8
|
+
- tool(): Create a tool execution span
|
|
9
|
+
|
|
10
|
+
Both sync and async variants are provided.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import functools
|
|
17
|
+
import logging
|
|
18
|
+
from contextlib import asynccontextmanager, contextmanager
|
|
19
|
+
from typing import Any, AsyncGenerator, Callable, Generator, Optional, TypeVar, Union
|
|
20
|
+
|
|
21
|
+
from opentelemetry import trace
|
|
22
|
+
from opentelemetry.trace import Span, SpanKind, Status, StatusCode
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
# Type variable for decorators
|
|
27
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
28
|
+
|
|
29
|
+
# Tracer name for MantisDK spans
|
|
30
|
+
TRACER_NAME = "mantisdk.tracing"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _get_tracer() -> trace.Tracer:
|
|
34
|
+
"""Get the MantisDK tracer instance."""
|
|
35
|
+
return trace.get_tracer(TRACER_NAME)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@contextmanager
|
|
39
|
+
def trace(
|
|
40
|
+
name: str,
|
|
41
|
+
*,
|
|
42
|
+
kind: SpanKind = SpanKind.INTERNAL,
|
|
43
|
+
attributes: Optional[dict[str, Any]] = None,
|
|
44
|
+
record_exception: bool = True,
|
|
45
|
+
set_status_on_exception: bool = True,
|
|
46
|
+
**extra_attributes: Any,
|
|
47
|
+
) -> Generator[Span, None, None]:
|
|
48
|
+
"""Context manager to create a trace (root span).
|
|
49
|
+
|
|
50
|
+
This creates a new span that will be the root of a trace tree.
|
|
51
|
+
Child spans created within this context will be linked to this root.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
name: The name of the trace/span.
|
|
55
|
+
kind: The span kind (default: INTERNAL).
|
|
56
|
+
attributes: Dictionary of attributes to set on the span.
|
|
57
|
+
record_exception: If True, exceptions are recorded on the span.
|
|
58
|
+
set_status_on_exception: If True, span status is set to ERROR on exception.
|
|
59
|
+
**extra_attributes: Additional attributes as keyword arguments.
|
|
60
|
+
|
|
61
|
+
Yields:
|
|
62
|
+
The OpenTelemetry Span object.
|
|
63
|
+
|
|
64
|
+
Example::
|
|
65
|
+
|
|
66
|
+
import mantisdk.tracing_claude as tracing
|
|
67
|
+
|
|
68
|
+
tracing.init()
|
|
69
|
+
|
|
70
|
+
with tracing.trace("my-workflow", user_id="123") as span:
|
|
71
|
+
span.set_attribute("step", "preprocessing")
|
|
72
|
+
result = do_work()
|
|
73
|
+
span.set_attribute("result_count", len(result))
|
|
74
|
+
"""
|
|
75
|
+
tracer = _get_tracer()
|
|
76
|
+
all_attributes = {**(attributes or {}), **extra_attributes}
|
|
77
|
+
|
|
78
|
+
with tracer.start_as_current_span(
|
|
79
|
+
name=name,
|
|
80
|
+
kind=kind,
|
|
81
|
+
attributes=all_attributes if all_attributes else None,
|
|
82
|
+
record_exception=record_exception,
|
|
83
|
+
set_status_on_exception=set_status_on_exception,
|
|
84
|
+
) as otel_span:
|
|
85
|
+
try:
|
|
86
|
+
yield otel_span
|
|
87
|
+
except Exception as e:
|
|
88
|
+
# Span context manager handles recording; we just re-raise
|
|
89
|
+
raise
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@contextmanager
|
|
93
|
+
def span(
|
|
94
|
+
name: str,
|
|
95
|
+
*,
|
|
96
|
+
kind: SpanKind = SpanKind.INTERNAL,
|
|
97
|
+
attributes: Optional[dict[str, Any]] = None,
|
|
98
|
+
record_exception: bool = True,
|
|
99
|
+
set_status_on_exception: bool = True,
|
|
100
|
+
**extra_attributes: Any,
|
|
101
|
+
) -> Generator[Span, None, None]:
|
|
102
|
+
"""Context manager to create a child span.
|
|
103
|
+
|
|
104
|
+
This creates a span that will be a child of the current active span.
|
|
105
|
+
If no active span exists, it becomes a root span.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
name: The name of the span.
|
|
109
|
+
kind: The span kind (default: INTERNAL).
|
|
110
|
+
attributes: Dictionary of attributes to set on the span.
|
|
111
|
+
record_exception: If True, exceptions are recorded on the span.
|
|
112
|
+
set_status_on_exception: If True, span status is set to ERROR on exception.
|
|
113
|
+
**extra_attributes: Additional attributes as keyword arguments.
|
|
114
|
+
|
|
115
|
+
Yields:
|
|
116
|
+
The OpenTelemetry Span object.
|
|
117
|
+
|
|
118
|
+
Example::
|
|
119
|
+
|
|
120
|
+
import mantisdk.tracing_claude as tracing
|
|
121
|
+
|
|
122
|
+
with tracing.trace("my-workflow"):
|
|
123
|
+
with tracing.span("step.load_data", dataset="training") as s:
|
|
124
|
+
data = load_data()
|
|
125
|
+
s.set_attribute("rows", len(data))
|
|
126
|
+
|
|
127
|
+
with tracing.span("step.process"):
|
|
128
|
+
process(data)
|
|
129
|
+
"""
|
|
130
|
+
tracer = _get_tracer()
|
|
131
|
+
all_attributes = {**(attributes or {}), **extra_attributes}
|
|
132
|
+
|
|
133
|
+
with tracer.start_as_current_span(
|
|
134
|
+
name=name,
|
|
135
|
+
kind=kind,
|
|
136
|
+
attributes=all_attributes if all_attributes else None,
|
|
137
|
+
record_exception=record_exception,
|
|
138
|
+
set_status_on_exception=set_status_on_exception,
|
|
139
|
+
) as otel_span:
|
|
140
|
+
try:
|
|
141
|
+
yield otel_span
|
|
142
|
+
except Exception:
|
|
143
|
+
raise
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@contextmanager
|
|
147
|
+
def tool(
|
|
148
|
+
name: str,
|
|
149
|
+
*,
|
|
150
|
+
tool_name: Optional[str] = None,
|
|
151
|
+
attributes: Optional[dict[str, Any]] = None,
|
|
152
|
+
record_exception: bool = True,
|
|
153
|
+
set_status_on_exception: bool = True,
|
|
154
|
+
**extra_attributes: Any,
|
|
155
|
+
) -> Generator[Span, None, None]:
|
|
156
|
+
"""Context manager to trace a tool execution.
|
|
157
|
+
|
|
158
|
+
This creates a span with TOOL kind and semantic attributes for tool calls.
|
|
159
|
+
Useful for tracing function calls, API calls, or other discrete operations.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
name: The span name (often the function/tool name).
|
|
163
|
+
tool_name: Explicit tool name attribute (defaults to name).
|
|
164
|
+
attributes: Dictionary of attributes to set on the span.
|
|
165
|
+
record_exception: If True, exceptions are recorded on the span.
|
|
166
|
+
set_status_on_exception: If True, span status is set to ERROR on exception.
|
|
167
|
+
**extra_attributes: Additional attributes as keyword arguments.
|
|
168
|
+
|
|
169
|
+
Yields:
|
|
170
|
+
The OpenTelemetry Span object.
|
|
171
|
+
|
|
172
|
+
Example::
|
|
173
|
+
|
|
174
|
+
import mantisdk.tracing_claude as tracing
|
|
175
|
+
|
|
176
|
+
with tracing.tool("search_database", query="SELECT *") as s:
|
|
177
|
+
results = db.execute(query)
|
|
178
|
+
s.set_attribute("result_count", len(results))
|
|
179
|
+
"""
|
|
180
|
+
tracer = _get_tracer()
|
|
181
|
+
all_attributes = {
|
|
182
|
+
"tool.name": tool_name or name,
|
|
183
|
+
**(attributes or {}),
|
|
184
|
+
**extra_attributes,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# Use CLIENT kind for tool calls (external service calls)
|
|
188
|
+
with tracer.start_as_current_span(
|
|
189
|
+
name=name,
|
|
190
|
+
kind=SpanKind.CLIENT,
|
|
191
|
+
attributes=all_attributes,
|
|
192
|
+
record_exception=record_exception,
|
|
193
|
+
set_status_on_exception=set_status_on_exception,
|
|
194
|
+
) as otel_span:
|
|
195
|
+
try:
|
|
196
|
+
yield otel_span
|
|
197
|
+
except Exception:
|
|
198
|
+
raise
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@asynccontextmanager
|
|
202
|
+
async def atrace(
|
|
203
|
+
name: str,
|
|
204
|
+
*,
|
|
205
|
+
kind: SpanKind = SpanKind.INTERNAL,
|
|
206
|
+
attributes: Optional[dict[str, Any]] = None,
|
|
207
|
+
record_exception: bool = True,
|
|
208
|
+
set_status_on_exception: bool = True,
|
|
209
|
+
**extra_attributes: Any,
|
|
210
|
+
) -> AsyncGenerator[Span, None]:
|
|
211
|
+
"""Async context manager to create a trace (root span).
|
|
212
|
+
|
|
213
|
+
Async variant of trace() for use in async code.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
name: The name of the trace/span.
|
|
217
|
+
kind: The span kind (default: INTERNAL).
|
|
218
|
+
attributes: Dictionary of attributes to set on the span.
|
|
219
|
+
record_exception: If True, exceptions are recorded on the span.
|
|
220
|
+
set_status_on_exception: If True, span status is set to ERROR on exception.
|
|
221
|
+
**extra_attributes: Additional attributes as keyword arguments.
|
|
222
|
+
|
|
223
|
+
Yields:
|
|
224
|
+
The OpenTelemetry Span object.
|
|
225
|
+
|
|
226
|
+
Example::
|
|
227
|
+
|
|
228
|
+
import mantisdk.tracing_claude as tracing
|
|
229
|
+
|
|
230
|
+
async with tracing.atrace("my-async-workflow") as span:
|
|
231
|
+
result = await async_operation()
|
|
232
|
+
"""
|
|
233
|
+
tracer = _get_tracer()
|
|
234
|
+
all_attributes = {**(attributes or {}), **extra_attributes}
|
|
235
|
+
|
|
236
|
+
with tracer.start_as_current_span(
|
|
237
|
+
name=name,
|
|
238
|
+
kind=kind,
|
|
239
|
+
attributes=all_attributes if all_attributes else None,
|
|
240
|
+
record_exception=record_exception,
|
|
241
|
+
set_status_on_exception=set_status_on_exception,
|
|
242
|
+
) as otel_span:
|
|
243
|
+
try:
|
|
244
|
+
yield otel_span
|
|
245
|
+
except Exception:
|
|
246
|
+
raise
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@asynccontextmanager
|
|
250
|
+
async def aspan(
|
|
251
|
+
name: str,
|
|
252
|
+
*,
|
|
253
|
+
kind: SpanKind = SpanKind.INTERNAL,
|
|
254
|
+
attributes: Optional[dict[str, Any]] = None,
|
|
255
|
+
record_exception: bool = True,
|
|
256
|
+
set_status_on_exception: bool = True,
|
|
257
|
+
**extra_attributes: Any,
|
|
258
|
+
) -> AsyncGenerator[Span, None]:
|
|
259
|
+
"""Async context manager to create a child span.
|
|
260
|
+
|
|
261
|
+
Async variant of span() for use in async code.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
name: The name of the span.
|
|
265
|
+
kind: The span kind (default: INTERNAL).
|
|
266
|
+
attributes: Dictionary of attributes to set on the span.
|
|
267
|
+
record_exception: If True, exceptions are recorded on the span.
|
|
268
|
+
set_status_on_exception: If True, span status is set to ERROR on exception.
|
|
269
|
+
**extra_attributes: Additional attributes as keyword arguments.
|
|
270
|
+
|
|
271
|
+
Yields:
|
|
272
|
+
The OpenTelemetry Span object.
|
|
273
|
+
|
|
274
|
+
Example::
|
|
275
|
+
|
|
276
|
+
import mantisdk.tracing_claude as tracing
|
|
277
|
+
|
|
278
|
+
async with tracing.atrace("workflow"):
|
|
279
|
+
async with tracing.aspan("fetch_data") as s:
|
|
280
|
+
data = await fetch()
|
|
281
|
+
s.set_attribute("size", len(data))
|
|
282
|
+
"""
|
|
283
|
+
tracer = _get_tracer()
|
|
284
|
+
all_attributes = {**(attributes or {}), **extra_attributes}
|
|
285
|
+
|
|
286
|
+
with tracer.start_as_current_span(
|
|
287
|
+
name=name,
|
|
288
|
+
kind=kind,
|
|
289
|
+
attributes=all_attributes if all_attributes else None,
|
|
290
|
+
record_exception=record_exception,
|
|
291
|
+
set_status_on_exception=set_status_on_exception,
|
|
292
|
+
) as otel_span:
|
|
293
|
+
try:
|
|
294
|
+
yield otel_span
|
|
295
|
+
except Exception:
|
|
296
|
+
raise
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def trace_decorator(
|
|
300
|
+
func: Optional[F] = None,
|
|
301
|
+
*,
|
|
302
|
+
name: Optional[str] = None,
|
|
303
|
+
kind: SpanKind = SpanKind.INTERNAL,
|
|
304
|
+
attributes: Optional[dict[str, Any]] = None,
|
|
305
|
+
record_exception: bool = True,
|
|
306
|
+
set_status_on_exception: bool = True,
|
|
307
|
+
) -> Union[F, Callable[[F], F]]:
|
|
308
|
+
"""Decorator to trace a function execution.
|
|
309
|
+
|
|
310
|
+
Can be used with or without parentheses:
|
|
311
|
+
@trace_decorator
|
|
312
|
+
def my_func(): ...
|
|
313
|
+
|
|
314
|
+
@trace_decorator(name="custom-name")
|
|
315
|
+
def my_func(): ...
|
|
316
|
+
|
|
317
|
+
Works with both sync and async functions.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
func: The function to decorate (when used without parentheses).
|
|
321
|
+
name: Custom span name. Defaults to the function name.
|
|
322
|
+
kind: The span kind (default: INTERNAL).
|
|
323
|
+
attributes: Dictionary of attributes to set on the span.
|
|
324
|
+
record_exception: If True, exceptions are recorded on the span.
|
|
325
|
+
set_status_on_exception: If True, span status is set to ERROR on exception.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Decorated function.
|
|
329
|
+
|
|
330
|
+
Example::
|
|
331
|
+
|
|
332
|
+
import mantisdk.tracing_claude as tracing
|
|
333
|
+
|
|
334
|
+
@tracing.trace
|
|
335
|
+
def process_data(items):
|
|
336
|
+
return [process(item) for item in items]
|
|
337
|
+
|
|
338
|
+
@tracing.trace(name="custom-operation", attributes={"version": "1.0"})
|
|
339
|
+
async def async_operation():
|
|
340
|
+
return await do_something()
|
|
341
|
+
"""
|
|
342
|
+
def decorator(fn: F) -> F:
|
|
343
|
+
span_name = name or fn.__name__
|
|
344
|
+
span_attributes = attributes or {}
|
|
345
|
+
|
|
346
|
+
if asyncio.iscoroutinefunction(fn):
|
|
347
|
+
@functools.wraps(fn)
|
|
348
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
349
|
+
tracer = _get_tracer()
|
|
350
|
+
with tracer.start_as_current_span(
|
|
351
|
+
name=span_name,
|
|
352
|
+
kind=kind,
|
|
353
|
+
attributes=span_attributes if span_attributes else None,
|
|
354
|
+
record_exception=record_exception,
|
|
355
|
+
set_status_on_exception=set_status_on_exception,
|
|
356
|
+
):
|
|
357
|
+
return await fn(*args, **kwargs)
|
|
358
|
+
return async_wrapper # type: ignore
|
|
359
|
+
else:
|
|
360
|
+
@functools.wraps(fn)
|
|
361
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
362
|
+
tracer = _get_tracer()
|
|
363
|
+
with tracer.start_as_current_span(
|
|
364
|
+
name=span_name,
|
|
365
|
+
kind=kind,
|
|
366
|
+
attributes=span_attributes if span_attributes else None,
|
|
367
|
+
record_exception=record_exception,
|
|
368
|
+
set_status_on_exception=set_status_on_exception,
|
|
369
|
+
):
|
|
370
|
+
return fn(*args, **kwargs)
|
|
371
|
+
return sync_wrapper # type: ignore
|
|
372
|
+
|
|
373
|
+
if func is None:
|
|
374
|
+
# Called with parentheses: @trace_decorator(...)
|
|
375
|
+
return decorator
|
|
376
|
+
else:
|
|
377
|
+
# Called without parentheses: @trace_decorator
|
|
378
|
+
return decorator(func)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def tool_decorator(
|
|
382
|
+
func: Optional[F] = None,
|
|
383
|
+
*,
|
|
384
|
+
name: Optional[str] = None,
|
|
385
|
+
tool_name: Optional[str] = None,
|
|
386
|
+
attributes: Optional[dict[str, Any]] = None,
|
|
387
|
+
record_exception: bool = True,
|
|
388
|
+
set_status_on_exception: bool = True,
|
|
389
|
+
) -> Union[F, Callable[[F], F]]:
|
|
390
|
+
"""Decorator to trace a tool/function execution.
|
|
391
|
+
|
|
392
|
+
Similar to trace_decorator but uses CLIENT span kind and adds tool.name attribute.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
func: The function to decorate (when used without parentheses).
|
|
396
|
+
name: Custom span name. Defaults to the function name.
|
|
397
|
+
tool_name: Tool name attribute. Defaults to span name.
|
|
398
|
+
attributes: Dictionary of attributes to set on the span.
|
|
399
|
+
record_exception: If True, exceptions are recorded on the span.
|
|
400
|
+
set_status_on_exception: If True, span status is set to ERROR on exception.
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
Decorated function.
|
|
404
|
+
|
|
405
|
+
Example::
|
|
406
|
+
|
|
407
|
+
import mantisdk.tracing_claude as tracing
|
|
408
|
+
|
|
409
|
+
@tracing.tool
|
|
410
|
+
def search_database(query: str) -> list:
|
|
411
|
+
return db.execute(query)
|
|
412
|
+
"""
|
|
413
|
+
def decorator(fn: F) -> F:
|
|
414
|
+
span_name = name or fn.__name__
|
|
415
|
+
span_attributes = {
|
|
416
|
+
"tool.name": tool_name or span_name,
|
|
417
|
+
**(attributes or {}),
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if asyncio.iscoroutinefunction(fn):
|
|
421
|
+
@functools.wraps(fn)
|
|
422
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
423
|
+
tracer = _get_tracer()
|
|
424
|
+
with tracer.start_as_current_span(
|
|
425
|
+
name=span_name,
|
|
426
|
+
kind=SpanKind.CLIENT,
|
|
427
|
+
attributes=span_attributes,
|
|
428
|
+
record_exception=record_exception,
|
|
429
|
+
set_status_on_exception=set_status_on_exception,
|
|
430
|
+
):
|
|
431
|
+
return await fn(*args, **kwargs)
|
|
432
|
+
return async_wrapper # type: ignore
|
|
433
|
+
else:
|
|
434
|
+
@functools.wraps(fn)
|
|
435
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
436
|
+
tracer = _get_tracer()
|
|
437
|
+
with tracer.start_as_current_span(
|
|
438
|
+
name=span_name,
|
|
439
|
+
kind=SpanKind.CLIENT,
|
|
440
|
+
attributes=span_attributes,
|
|
441
|
+
record_exception=record_exception,
|
|
442
|
+
set_status_on_exception=set_status_on_exception,
|
|
443
|
+
):
|
|
444
|
+
return fn(*args, **kwargs)
|
|
445
|
+
return sync_wrapper # type: ignore
|
|
446
|
+
|
|
447
|
+
if func is None:
|
|
448
|
+
return decorator
|
|
449
|
+
else:
|
|
450
|
+
return decorator(func)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def record_exception(
|
|
454
|
+
span: Span,
|
|
455
|
+
exception: BaseException,
|
|
456
|
+
*,
|
|
457
|
+
attributes: Optional[dict[str, Any]] = None,
|
|
458
|
+
escaped: bool = False,
|
|
459
|
+
) -> None:
|
|
460
|
+
"""Record an exception on a span.
|
|
461
|
+
|
|
462
|
+
Helper function for recording exceptions with proper attributes.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
span: The span to record the exception on.
|
|
466
|
+
exception: The exception to record.
|
|
467
|
+
attributes: Additional attributes to record with the exception.
|
|
468
|
+
escaped: Whether the exception escaped the span's scope.
|
|
469
|
+
|
|
470
|
+
Example::
|
|
471
|
+
|
|
472
|
+
import mantisdk.tracing_claude as tracing
|
|
473
|
+
|
|
474
|
+
with tracing.span("operation") as s:
|
|
475
|
+
try:
|
|
476
|
+
risky_operation()
|
|
477
|
+
except ValueError as e:
|
|
478
|
+
tracing.record_exception(s, e, attributes={"input": "bad"})
|
|
479
|
+
s.set_status(StatusCode.ERROR, str(e))
|
|
480
|
+
# Handle or re-raise
|
|
481
|
+
"""
|
|
482
|
+
span.record_exception(exception, attributes=attributes, escaped=escaped)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def set_span_error(
|
|
486
|
+
span: Span,
|
|
487
|
+
message: str,
|
|
488
|
+
*,
|
|
489
|
+
exception: Optional[BaseException] = None,
|
|
490
|
+
) -> None:
|
|
491
|
+
"""Set a span's status to ERROR with a description.
|
|
492
|
+
|
|
493
|
+
Convenience function for marking a span as failed.
|
|
494
|
+
|
|
495
|
+
Args:
|
|
496
|
+
span: The span to mark as error.
|
|
497
|
+
message: Error description.
|
|
498
|
+
exception: Optional exception to record.
|
|
499
|
+
|
|
500
|
+
Example::
|
|
501
|
+
|
|
502
|
+
import mantisdk.tracing_claude as tracing
|
|
503
|
+
|
|
504
|
+
with tracing.span("operation") as s:
|
|
505
|
+
result = do_work()
|
|
506
|
+
if not result.success:
|
|
507
|
+
tracing.set_span_error(s, f"Operation failed: {result.error}")
|
|
508
|
+
"""
|
|
509
|
+
span.set_status(Status(StatusCode.ERROR, message))
|
|
510
|
+
if exception is not None:
|
|
511
|
+
span.record_exception(exception)
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def set_span_ok(span: Span, message: Optional[str] = None) -> None:
|
|
515
|
+
"""Set a span's status to OK.
|
|
516
|
+
|
|
517
|
+
Convenience function for marking a span as successful.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
span: The span to mark as OK.
|
|
521
|
+
message: Optional success message.
|
|
522
|
+
"""
|
|
523
|
+
span.set_status(Status(StatusCode.OK, message))
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def get_current_span() -> Span:
|
|
527
|
+
"""Get the currently active span.
|
|
528
|
+
|
|
529
|
+
Returns:
|
|
530
|
+
The current span, or a non-recording span if none is active.
|
|
531
|
+
|
|
532
|
+
Example::
|
|
533
|
+
|
|
534
|
+
import mantisdk.tracing_claude as tracing
|
|
535
|
+
|
|
536
|
+
def inner_function():
|
|
537
|
+
span = tracing.get_current_span()
|
|
538
|
+
span.set_attribute("inner_data", "value")
|
|
539
|
+
"""
|
|
540
|
+
return trace.get_current_span()
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
# Convenience aliases for decorator-style usage
|
|
544
|
+
# These allow: @tracing.trace instead of @tracing.trace_decorator
|
|
545
|
+
# The context manager `trace` takes precedence, but when used as decorator
|
|
546
|
+
# it works because the context manager returns a span, not a wrapper
|