custodia-sdk 0.1.0__tar.gz
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.
- custodia_sdk-0.1.0/LICENSE +21 -0
- custodia_sdk-0.1.0/PKG-INFO +69 -0
- custodia_sdk-0.1.0/README.md +53 -0
- custodia_sdk-0.1.0/pyproject.toml +27 -0
- custodia_sdk-0.1.0/src/custodia/.DS_Store +0 -0
- custodia_sdk-0.1.0/src/custodia/__init__.py +4 -0
- custodia_sdk-0.1.0/src/custodia/_tracer.py +36 -0
- custodia_sdk-0.1.0/src/custodia/py.typed +0 -0
- custodia_sdk-0.1.0/src/custodia/trace.py +237 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright (c) 2026 Neurona. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software and associated documentation files (the "Software") are the
|
|
4
|
+
proprietary property of Neurona ("Licensor"). The Software is made available
|
|
5
|
+
for download and inspection only. No license, right, or permission to use,
|
|
6
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
7
|
+
the Software is granted except as expressly agreed in a separate written
|
|
8
|
+
commercial license agreement with Licensor.
|
|
9
|
+
|
|
10
|
+
Use of the Software without a valid, active commercial license from Licensor
|
|
11
|
+
is strictly prohibited.
|
|
12
|
+
|
|
13
|
+
To obtain a commercial license, contact: michael.gunawan2002@gmail.com
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: custodia-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Custodia tracing SDK
|
|
5
|
+
Author: ssabrut
|
|
6
|
+
Author-email: ssabrut <michael.gunawan2002@gmail.com>
|
|
7
|
+
License-Expression: LicenseRef-Proprietary
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Dist: httpx>=0.28.1
|
|
12
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.44.0
|
|
13
|
+
Requires-Dist: opentelemetry-sdk>=1.44.0
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# custodia-sdk
|
|
18
|
+
|
|
19
|
+
Python tracing SDK for Custodia. Instruments function calls as OpenTelemetry
|
|
20
|
+
spans and exports them via OTLP/HTTP.
|
|
21
|
+
|
|
22
|
+
> **License:** proprietary. Use requires a paid commercial license from
|
|
23
|
+
> Neurona. See [LICENSE](./LICENSE). Contact michael.gunawan2002@gmail.com
|
|
24
|
+
> to obtain one.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install custodia-sdk
|
|
30
|
+
# or
|
|
31
|
+
uv add custodia-sdk
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
Set via environment variables:
|
|
37
|
+
|
|
38
|
+
| Variable | Description | Default |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `CUSTODIA_INGEST_URL` | OTLP/HTTP traces endpoint | `http://localhost:4318/v1/traces` |
|
|
41
|
+
| `CUSTODIA_API_KEY` | Bearer token sent with exported spans | *(none)* |
|
|
42
|
+
| `CUSTODIA_SERVICE_NAME` | `service.name` resource attribute | `unknown-service` |
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from custodia import trace, trace_async, trace_span, atrace_span
|
|
48
|
+
|
|
49
|
+
@trace(name="fetch_user", metadata={"component": "db"})
|
|
50
|
+
def get_user(user_id: str):
|
|
51
|
+
return db.query(user_id)
|
|
52
|
+
|
|
53
|
+
@trace_async(name="call_llm")
|
|
54
|
+
async def generate(prompt: str) -> str:
|
|
55
|
+
return await llm_client.complete(prompt)
|
|
56
|
+
|
|
57
|
+
with trace_span("parse_response") as span:
|
|
58
|
+
data = json.loads(raw)
|
|
59
|
+
span.set_attribute("record_count", len(data))
|
|
60
|
+
|
|
61
|
+
async with atrace_span("call_downstream") as span:
|
|
62
|
+
resp = await http_client.get(url)
|
|
63
|
+
span.set_attribute("http.status_code", resp.status_code)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- `trace` / `trace_async`: decorators that auto-capture args/return value as
|
|
67
|
+
`gen_ai.prompt` / `gen_ai.completion` span attributes.
|
|
68
|
+
- `trace_span` / `atrace_span`: context managers for manual span control,
|
|
69
|
+
no automatic I/O capture.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# custodia-sdk
|
|
2
|
+
|
|
3
|
+
Python tracing SDK for Custodia. Instruments function calls as OpenTelemetry
|
|
4
|
+
spans and exports them via OTLP/HTTP.
|
|
5
|
+
|
|
6
|
+
> **License:** proprietary. Use requires a paid commercial license from
|
|
7
|
+
> Neurona. See [LICENSE](./LICENSE). Contact michael.gunawan2002@gmail.com
|
|
8
|
+
> to obtain one.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install custodia-sdk
|
|
14
|
+
# or
|
|
15
|
+
uv add custodia-sdk
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Configuration
|
|
19
|
+
|
|
20
|
+
Set via environment variables:
|
|
21
|
+
|
|
22
|
+
| Variable | Description | Default |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `CUSTODIA_INGEST_URL` | OTLP/HTTP traces endpoint | `http://localhost:4318/v1/traces` |
|
|
25
|
+
| `CUSTODIA_API_KEY` | Bearer token sent with exported spans | *(none)* |
|
|
26
|
+
| `CUSTODIA_SERVICE_NAME` | `service.name` resource attribute | `unknown-service` |
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from custodia import trace, trace_async, trace_span, atrace_span
|
|
32
|
+
|
|
33
|
+
@trace(name="fetch_user", metadata={"component": "db"})
|
|
34
|
+
def get_user(user_id: str):
|
|
35
|
+
return db.query(user_id)
|
|
36
|
+
|
|
37
|
+
@trace_async(name="call_llm")
|
|
38
|
+
async def generate(prompt: str) -> str:
|
|
39
|
+
return await llm_client.complete(prompt)
|
|
40
|
+
|
|
41
|
+
with trace_span("parse_response") as span:
|
|
42
|
+
data = json.loads(raw)
|
|
43
|
+
span.set_attribute("record_count", len(data))
|
|
44
|
+
|
|
45
|
+
async with atrace_span("call_downstream") as span:
|
|
46
|
+
resp = await http_client.get(url)
|
|
47
|
+
span.set_attribute("http.status_code", resp.status_code)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- `trace` / `trace_async`: decorators that auto-capture args/return value as
|
|
51
|
+
`gen_ai.prompt` / `gen_ai.completion` span attributes.
|
|
52
|
+
- `trace_span` / `atrace_span`: context managers for manual span control,
|
|
53
|
+
no automatic I/O capture.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "custodia-sdk"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Custodia tracing SDK"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "LicenseRef-Proprietary"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "ssabrut", email = "michael.gunawan2002@gmail.com" }
|
|
10
|
+
]
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"httpx>=0.28.1",
|
|
14
|
+
"opentelemetry-exporter-otlp-proto-http>=1.44.0",
|
|
15
|
+
"opentelemetry-sdk>=1.44.0",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[tool.uv.build-backend]
|
|
23
|
+
module-name = "custodia"
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["uv_build>=0.11.30,<0.12.0"]
|
|
27
|
+
build-backend = "uv_build"
|
|
Binary file
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from opentelemetry import trace as otel_trace
|
|
4
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
5
|
+
from opentelemetry.sdk.resources import Resource
|
|
6
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
7
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
8
|
+
|
|
9
|
+
_initialized = False
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _ensure_initialized() -> None:
|
|
13
|
+
global _initialized
|
|
14
|
+
if _initialized:
|
|
15
|
+
return
|
|
16
|
+
|
|
17
|
+
endpoint = os.environ.get("CUSTODIA_INGEST_URL", "http://localhost:4318/v1/traces")
|
|
18
|
+
api_key = os.environ.get("CUSTODIA_API_KEY", "")
|
|
19
|
+
|
|
20
|
+
provider = TracerProvider(
|
|
21
|
+
resource=Resource.create(
|
|
22
|
+
{"service.name": os.environ.get("CUSTODIA_SERVICE_NAME", "unknown-service")}
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
exporter = OTLPSpanExporter(
|
|
26
|
+
endpoint=endpoint,
|
|
27
|
+
headers={"Authorization": f"Bearer {api_key}"} if api_key else {},
|
|
28
|
+
)
|
|
29
|
+
provider.add_span_processor(BatchSpanProcessor(exporter))
|
|
30
|
+
otel_trace.set_tracer_provider(provider)
|
|
31
|
+
_initialized = True
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_tracer() -> otel_trace.Tracer:
|
|
35
|
+
_ensure_initialized()
|
|
36
|
+
return otel_trace.get_tracer("custodia-sdk")
|
|
File without changes
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import json
|
|
3
|
+
from contextlib import asynccontextmanager, contextmanager
|
|
4
|
+
from typing import Any, AsyncGenerator, Callable, Generator, Optional
|
|
5
|
+
|
|
6
|
+
from opentelemetry.trace import Status, StatusCode
|
|
7
|
+
|
|
8
|
+
from ._tracer import get_tracer
|
|
9
|
+
|
|
10
|
+
GENAI_PROMPT_ATTR = "gen_ai.prompt"
|
|
11
|
+
GENAI_COMPLETION_ATTR = "gen_ai.completion"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _set_io_attributes(span: Any, args: tuple, kwargs: dict, result: Any) -> None:
|
|
15
|
+
try:
|
|
16
|
+
span.set_attribute(
|
|
17
|
+
GENAI_PROMPT_ATTR,
|
|
18
|
+
json.dumps({"args": _safe_repr(args), "kwargs": _safe_repr(kwargs)}),
|
|
19
|
+
)
|
|
20
|
+
span.set_attribute(GENAI_COMPLETION_ATTR, _safe_repr(result))
|
|
21
|
+
except TypeError:
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _safe_repr(value: Any) -> str:
|
|
26
|
+
try:
|
|
27
|
+
return json.dumps(value, default=str)
|
|
28
|
+
except (TypeError, ValueError):
|
|
29
|
+
return repr(value)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _set_span_metadata(span: Any, metadata: Optional[dict]) -> None:
|
|
33
|
+
if metadata:
|
|
34
|
+
for key, value in metadata.items():
|
|
35
|
+
span.set_attribute(f"metadata.{key}", _safe_repr(value))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _record_error(span: Any, exc: Exception) -> None:
|
|
39
|
+
span.set_status(Status(StatusCode.ERROR, str(exc)))
|
|
40
|
+
span.record_exception(exc)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def trace(name: Optional[str] = None, metadata: Optional[dict] = None) -> Callable:
|
|
44
|
+
"""Decorator that wraps a synchronous function in an OpenTelemetry span.
|
|
45
|
+
|
|
46
|
+
On each call, starts a span named `name` (or the function's `__name__`
|
|
47
|
+
if omitted), attaches `metadata` as `metadata.<key>` span attributes,
|
|
48
|
+
invokes the wrapped function, and records the call's arguments and
|
|
49
|
+
return value as `gen_ai.prompt` / `gen_ai.completion` span attributes
|
|
50
|
+
(JSON-encoded, falling back to `repr()` for non-serializable values).
|
|
51
|
+
|
|
52
|
+
If the wrapped function raises, the exception is recorded on the span,
|
|
53
|
+
the span status is set to ERROR, and the exception is re-raised
|
|
54
|
+
unchanged.
|
|
55
|
+
|
|
56
|
+
Use this for a whole function call that represents one logical
|
|
57
|
+
operation worth tracing (e.g. an LLM call), where capturing all
|
|
58
|
+
inputs/outputs automatically is desired. For tracing only part of a
|
|
59
|
+
function, or to control which attributes are recorded, use
|
|
60
|
+
`trace_span` instead.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
name: Span name. Defaults to the decorated function's `__name__`.
|
|
64
|
+
metadata: Optional static key/value pairs attached to every span
|
|
65
|
+
created by this decorator, as `metadata.<key>` attributes.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
A decorator that wraps a sync function, preserving its signature
|
|
69
|
+
and return value via `functools.wraps`.
|
|
70
|
+
|
|
71
|
+
Example:
|
|
72
|
+
```python
|
|
73
|
+
@trace(name="fetch_user", metadata={"component": "db"})
|
|
74
|
+
def get_user(user_id: str) -> User:
|
|
75
|
+
return db.query(user_id)
|
|
76
|
+
```
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def decorator(fn: Callable) -> Callable:
|
|
80
|
+
span_name = name or fn.__name__
|
|
81
|
+
|
|
82
|
+
@functools.wraps(fn)
|
|
83
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
84
|
+
tracer = get_tracer()
|
|
85
|
+
with tracer.start_as_current_span(span_name) as span:
|
|
86
|
+
_set_span_metadata(span, metadata)
|
|
87
|
+
try:
|
|
88
|
+
result = fn(*args, **kwargs)
|
|
89
|
+
except Exception as exc:
|
|
90
|
+
_record_error(span, exc)
|
|
91
|
+
raise
|
|
92
|
+
_set_io_attributes(span, args, kwargs, result)
|
|
93
|
+
return result
|
|
94
|
+
|
|
95
|
+
return wrapper
|
|
96
|
+
|
|
97
|
+
return decorator
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def trace_async(name: Optional[str] = None, metadata: Optional[dict] = None) -> Callable:
|
|
101
|
+
"""Decorator that wraps an async function in an OpenTelemetry span.
|
|
102
|
+
|
|
103
|
+
Async counterpart to `trace`. Behaves identically — starts a span,
|
|
104
|
+
attaches `metadata`, awaits the wrapped coroutine function, records
|
|
105
|
+
its arguments and return value as `gen_ai.prompt` / `gen_ai.completion`
|
|
106
|
+
span attributes, and records/reraises any exception with the span
|
|
107
|
+
marked as ERROR — but wraps a coroutine function (`async def`) instead
|
|
108
|
+
of a plain function.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
name: Span name. Defaults to the decorated function's `__name__`.
|
|
112
|
+
metadata: Optional static key/value pairs attached to every span
|
|
113
|
+
created by this decorator, as `metadata.<key>` attributes.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
A decorator that wraps an async function, preserving its signature
|
|
117
|
+
and return value via `functools.wraps`.
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
```python
|
|
121
|
+
@trace_async(name="call_llm")
|
|
122
|
+
async def generate(prompt: str) -> str:
|
|
123
|
+
return await llm_client.complete(prompt)
|
|
124
|
+
```
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def decorator(fn: Callable) -> Callable:
|
|
128
|
+
span_name = name or fn.__name__
|
|
129
|
+
|
|
130
|
+
@functools.wraps(fn)
|
|
131
|
+
async def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
132
|
+
tracer = get_tracer()
|
|
133
|
+
with tracer.start_as_current_span(span_name) as span:
|
|
134
|
+
_set_span_metadata(span, metadata)
|
|
135
|
+
try:
|
|
136
|
+
result = await fn(*args, **kwargs)
|
|
137
|
+
except Exception as exc:
|
|
138
|
+
_record_error(span, exc)
|
|
139
|
+
raise
|
|
140
|
+
_set_io_attributes(span, args, kwargs, result)
|
|
141
|
+
return result
|
|
142
|
+
|
|
143
|
+
return wrapper
|
|
144
|
+
|
|
145
|
+
return decorator
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@contextmanager
|
|
149
|
+
def trace_span(
|
|
150
|
+
name: str, metadata: Optional[dict] = None
|
|
151
|
+
) -> Generator[Any, None, None]:
|
|
152
|
+
"""Context manager that starts an OpenTelemetry span around a block of
|
|
153
|
+
synchronous code.
|
|
154
|
+
|
|
155
|
+
Starts a span named `name`, attaches `metadata` as `metadata.<key>`
|
|
156
|
+
span attributes, and yields the span object to the caller for manual
|
|
157
|
+
attribute/event setting. Unlike `trace`, no automatic prompt/completion
|
|
158
|
+
(input/output) attributes are recorded — the caller has full control
|
|
159
|
+
over what gets attached to the span.
|
|
160
|
+
|
|
161
|
+
If an exception propagates out of the `with` block, it is recorded on
|
|
162
|
+
the span, the span status is set to ERROR, and the exception is
|
|
163
|
+
re-raised unchanged.
|
|
164
|
+
|
|
165
|
+
Use this instead of `trace` when only part of a function should be
|
|
166
|
+
traced, when a single function performs multiple distinct operations
|
|
167
|
+
that each deserve their own span, when tracing code with no dedicated
|
|
168
|
+
function to decorate, or when automatic capture of args/return values
|
|
169
|
+
is undesirable (e.g. large or sensitive payloads).
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
name: Span name.
|
|
173
|
+
metadata: Optional key/value pairs attached to the span as
|
|
174
|
+
`metadata.<key>` attributes.
|
|
175
|
+
|
|
176
|
+
Yields:
|
|
177
|
+
The active span object, for setting custom attributes
|
|
178
|
+
(`span.set_attribute(...)`) or recording events during the block.
|
|
179
|
+
|
|
180
|
+
Example:
|
|
181
|
+
```python
|
|
182
|
+
with trace_span("parse_response", metadata={"format": "json"}) as span:
|
|
183
|
+
data = json.loads(raw)
|
|
184
|
+
span.set_attribute("record_count", len(data))
|
|
185
|
+
```
|
|
186
|
+
"""
|
|
187
|
+
tracer = get_tracer()
|
|
188
|
+
with tracer.start_as_current_span(name) as span:
|
|
189
|
+
_set_span_metadata(span, metadata)
|
|
190
|
+
try:
|
|
191
|
+
yield span
|
|
192
|
+
except Exception as exc:
|
|
193
|
+
_record_error(span, exc)
|
|
194
|
+
raise
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@asynccontextmanager
|
|
198
|
+
async def atrace_span(
|
|
199
|
+
name: str, metadata: Optional[dict] = None
|
|
200
|
+
) -> AsyncGenerator[Any, None]:
|
|
201
|
+
"""Async context manager that starts an OpenTelemetry span around a
|
|
202
|
+
block of async code.
|
|
203
|
+
|
|
204
|
+
Async counterpart to `trace_span`. Starts a span named `name`, attaches
|
|
205
|
+
`metadata` as `metadata.<key>` span attributes, and yields the span
|
|
206
|
+
object for manual attribute setting inside an `async with` block. No
|
|
207
|
+
automatic prompt/completion attributes are recorded — same
|
|
208
|
+
caller-controlled attribution as `trace_span`.
|
|
209
|
+
|
|
210
|
+
If an exception propagates out of the `async with` block, it is
|
|
211
|
+
recorded on the span, the span status is set to ERROR, and the
|
|
212
|
+
exception is re-raised unchanged.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
name: Span name.
|
|
216
|
+
metadata: Optional key/value pairs attached to the span as
|
|
217
|
+
`metadata.<key>` attributes.
|
|
218
|
+
|
|
219
|
+
Yields:
|
|
220
|
+
The active span object, for setting custom attributes
|
|
221
|
+
(`span.set_attribute(...)`) or recording events during the block.
|
|
222
|
+
|
|
223
|
+
Example:
|
|
224
|
+
```python
|
|
225
|
+
async with atrace_span("call_downstream") as span:
|
|
226
|
+
resp = await http_client.get(url)
|
|
227
|
+
span.set_attribute("http.status_code", resp.status_code)
|
|
228
|
+
```
|
|
229
|
+
"""
|
|
230
|
+
tracer = get_tracer()
|
|
231
|
+
with tracer.start_as_current_span(name) as span:
|
|
232
|
+
_set_span_metadata(span, metadata)
|
|
233
|
+
try:
|
|
234
|
+
yield span
|
|
235
|
+
except Exception as exc:
|
|
236
|
+
_record_error(span, exc)
|
|
237
|
+
raise
|