telemetry-dev 0.2.8__tar.gz → 0.2.10__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.
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/PKG-INFO +77 -2
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/README.md +76 -1
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/pyproject.toml +1 -1
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/pyproject.toml.orig +1 -1
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/src/telemetry_dev/_capture.py +46 -14
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/src/telemetry_dev/_serialize.py +1 -2
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/src/telemetry_dev/_spans.py +13 -0
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/LICENSE +0 -0
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/src/telemetry_dev/__init__.py +0 -0
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/src/telemetry_dev/_client.py +0 -0
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/src/telemetry_dev/_config.py +0 -0
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/src/telemetry_dev/_context.py +0 -0
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/src/telemetry_dev/_logs.py +0 -0
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/src/telemetry_dev/_metrics.py +0 -0
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/src/telemetry_dev/_observe.py +0 -0
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/src/telemetry_dev/_processor.py +0 -0
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/src/telemetry_dev/_semconv.py +0 -0
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/src/telemetry_dev/otel.py +0 -0
- {telemetry_dev-0.2.8 → telemetry_dev-0.2.10}/src/telemetry_dev/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: telemetry-dev
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.10
|
|
4
4
|
Summary: telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics
|
|
5
5
|
Keywords: telemetry,opentelemetry,llm,genai,tracing,observability
|
|
6
6
|
Author: telemetry.dev
|
|
@@ -28,6 +28,8 @@ telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and m
|
|
|
28
28
|
ergonomic functions over OTel spans (`gen_ai.*` semantic conventions), exported as OTLP
|
|
29
29
|
protobuf to the telemetry.dev ingest.
|
|
30
30
|
|
|
31
|
+
Docs: https://telemetry.dev/docs/sdk/python
|
|
32
|
+
|
|
31
33
|
## Install
|
|
32
34
|
|
|
33
35
|
```sh
|
|
@@ -100,6 +102,7 @@ later `init()` generates a new ID. It is not persisted across processes.
|
|
|
100
102
|
| `@observe` / `@observe(name=, type=, capture_input=, capture_output=, attributes=)` | Wrap a sync/async function (or generator) in a span. Arguments become `input` (param-name dict, `self`/`cls` dropped), the return value becomes `output`, exceptions are captured and re-raised. |
|
|
101
103
|
| `start_span(name, *, type="span", ...) -> SpanHandle` | Start a span. `with` activates it in the current context; without `with` it is a detached handle you must `.end()`. |
|
|
102
104
|
| `SpanHandle.update(**fields)` / `.end(**fields, end_time=)` / `.record_output_chunk(timestamp_ms=None)` / `.traceparent()` | Update attributes, end, record arrival of a non-empty output chunk, or read the W3C traceparent. |
|
|
105
|
+
| `SpanHandle.serialize_content(value, key) -> str \| None` | Prepare content an integration writes outside `input` and `output`, such as an error message or a raw attribute, the way `input` and `output` are prepared: the configured `mask` with `key`, JSON serialization (strings pass through), then truncation to `max_attribute_length`. Returns `None` when the mask raises (reported through `on_error`) or the value cannot be serialized; write nothing then. Does not check `capture_input` / `capture_output`. Integrations that support older cores should treat a missing method as a configured mask. |
|
|
103
106
|
| `update_current_span(**fields)` | Apply the update field set to the currently active span (no-op without one). |
|
|
104
107
|
| `propagate_attributes(*, user_id=, session_id=, metadata=)` | Context manager stamping `user.id` / `gen_ai.conversation.id` / `td.metadata.*` on every span and log record started inside (threads/asyncio included via contextvars). |
|
|
105
108
|
| `log(message, *, level="info", event_name=None, attributes=None)` | Emit an OTLP log record to `/v1/logs`, correlated with the current trace. Levels: `debug`/`info`/`warn`/`error` (`"warning"` is accepted as an alias of `warn`). |
|
|
@@ -154,11 +157,12 @@ the core SDK with provider integrations to enable the new metric.
|
|
|
154
157
|
| --- | --- | --- |
|
|
155
158
|
| `api_key`, `base_url`, `environment`, `service_name` | env vars | Connection + resource settings. |
|
|
156
159
|
| `enabled` | `True` | `False` = hard kill switch (tests). |
|
|
160
|
+
| `session_mode` | `"explicit"` | `"process"` groups otherwise uncorrelated spans and logs under one generated session ID per `init()` (see [Environment variables](#environment-variables)). Other values call `on_error` and install a disabled client. |
|
|
157
161
|
| `register_global` | `False` | Also register the tracer provider globally. Applies a default export filter (only `telemetry_dev`-scoped spans are exported); pass `span_filter=lambda s: True` to export everything. |
|
|
158
162
|
| `export_mode` | `"batched"` | `"immediate"` exports synchronously per span/log (serverless). |
|
|
159
163
|
| `log_level` | `"warn"` | SDK diagnostics level: `debug`/`info`/`warn`/`error`/`silent`. |
|
|
160
164
|
| `capture_input`, `capture_output` | `True` | Global content-capture defaults. |
|
|
161
|
-
| `mask` | `None` | `Callable[[Any, MaskContext], Any]` redaction hook, runs before JSON serialization on input/output/log messages (`MaskContext.key` is the attribute being written). Not applied to correlation identifiers. |
|
|
165
|
+
| `mask` | `None` | `Callable[[Any, MaskContext], Any]` redaction hook, runs before JSON serialization on input/output/log messages and `SpanHandle.serialize_content` (`MaskContext.key` is the attribute being written). Not applied to correlation identifiers. |
|
|
162
166
|
| `max_attribute_length` | `65536` | Non-negative integer per-content-attribute cap. Invalid values call `on_error`, do not raise into application code, and install a disabled client; truncated values use the ASCII `...[truncated]` marker, or its prefix when the cap is shorter than the marker. The cap also applies, with the marker, to string metadata and log attributes; any other string attribute is cut at the cap without a marker, except that a cap of `0` leaves those at 65536. |
|
|
163
167
|
| `span_filter` | `None` | Export predicate `Callable[[ReadableSpan], bool]`. |
|
|
164
168
|
| `sampler` | OTel environment configuration | OTel `Sampler` that overrides environment settings. Session roots use its root policy. Real parents keep their sampling decisions. |
|
|
@@ -172,6 +176,11 @@ still record non-content metadata such as response IDs and models, finish reason
|
|
|
172
176
|
and errors. Capture flags do not gate stop sequences, tool descriptions and definitions (`gen_ai.tool.description`, `gen_ai.tool.definitions`), caller-supplied metadata or raw attributes,
|
|
173
177
|
or exception messages and stack traces; redact those separately when needed.
|
|
174
178
|
|
|
179
|
+
`mask` receives input and output content, system instructions, log messages, and content that
|
|
180
|
+
integrations pass to `SpanHandle.serialize_content`. `MaskContext.key` names the attribute being
|
|
181
|
+
written. It does not receive span or propagated `metadata`, raw `attributes`, log attributes, or
|
|
182
|
+
exception messages and stack traces; redact those separately.
|
|
183
|
+
|
|
175
184
|
Session roots share trace IDs from the API key and session ID. The hash matches the TypeScript SDK.
|
|
176
185
|
The SDK uses the configured OTel sampling policy, not the synthetic parent's flags.
|
|
177
186
|
For example, `init(sampler=ParentBased(TraceIdRatioBased(0.1)))` samples sessions with OTel's ratio sampler.
|
|
@@ -252,6 +261,68 @@ safe to attach unconditionally, with only a debug log. Auto-metrics are on by de
|
|
|
252
261
|
GenAI spans, use the `service_name` / `environment` resource settings above, and are skipped
|
|
253
262
|
without an API key.
|
|
254
263
|
|
|
264
|
+
### Native Claude SDK spans
|
|
265
|
+
|
|
266
|
+
The Python `anthropic` SDK 1.14.0 and later emits OpenTelemetry spans natively. The Claude SDK uses the global tracer provider of the application.
|
|
267
|
+
To send these spans to telemetry.dev, register a global tracer provider with `TelemetrySpanProcessor`:
|
|
268
|
+
|
|
269
|
+
```sh
|
|
270
|
+
pip install telemetry-dev "anthropic>=1.14.0"
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
from anthropic import Anthropic
|
|
275
|
+
from opentelemetry import trace
|
|
276
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
277
|
+
from telemetry_dev.otel import TelemetrySpanProcessor
|
|
278
|
+
|
|
279
|
+
provider = TracerProvider()
|
|
280
|
+
provider.add_span_processor(TelemetrySpanProcessor())
|
|
281
|
+
trace.set_tracer_provider(provider)
|
|
282
|
+
|
|
283
|
+
client = Anthropic()
|
|
284
|
+
client.messages.create(
|
|
285
|
+
model="claude-sonnet-4-6",
|
|
286
|
+
max_tokens=256,
|
|
287
|
+
messages=[{"role": "user", "content": "Say hello."}],
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
provider.shutdown()
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
If you use `telemetry_dev.init(register_global=True)`, also pass `span_filter=lambda span: True`. The default filter of that mode exports only the spans of this SDK.
|
|
294
|
+
|
|
295
|
+
By default, the spans contain metadata only. To also send prompts and responses, set `ANTHROPIC_OPEN_TELEMETRY_TRACES_CONTENT_MODE=content`. The client reads this variable one time, when you make the client.
|
|
296
|
+
|
|
297
|
+
Do not use the native spans and `telemetry-dev-anthropic` together. If you use the two, each request has two spans.
|
|
298
|
+
|
|
299
|
+
You can also send the native spans without this package. Use an OTLP/HTTP protobuf exporter:
|
|
300
|
+
|
|
301
|
+
```sh
|
|
302
|
+
pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
import os
|
|
307
|
+
|
|
308
|
+
from opentelemetry import trace
|
|
309
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
310
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
311
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
312
|
+
|
|
313
|
+
exporter = OTLPSpanExporter(
|
|
314
|
+
endpoint="https://ingest.telemetry.dev/v1/traces",
|
|
315
|
+
headers={"Authorization": f"Bearer {os.environ['TELEMETRY_DEV_API_KEY']}"},
|
|
316
|
+
)
|
|
317
|
+
provider = TracerProvider()
|
|
318
|
+
provider.add_span_processor(BatchSpanProcessor(exporter))
|
|
319
|
+
trace.set_tracer_provider(provider)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
The Claude API can also send server-side spans to telemetry.dev through a Console destination. Set the endpoint to `https://ingest.telemetry.dev`. Set the `Authorization` header to `Bearer <telemetry.dev API key>`. For more information, refer to [Claude Platform](https://telemetry.dev/docs/integrations/claude-platform).
|
|
323
|
+
|
|
324
|
+
When the SDK and the Claude API send the spans of a request to the same telemetry.dev project, telemetry.dev counts the tokens of the request one time, from the API span. The SDK span stays in the trace with no tokens. telemetry.dev matches the two spans by the request ID or the response ID, so the API span does not have to be a child of the SDK span. If the Console destination uses an API key that is scoped to one environment, its API spans take over only the SDK spans of that environment.
|
|
325
|
+
|
|
255
326
|
## Limitations
|
|
256
327
|
|
|
257
328
|
- `register_global=True` cannot be undone on `shutdown()`: OpenTelemetry Python has no public
|
|
@@ -274,3 +345,7 @@ uv build # build wheel + sdist
|
|
|
274
345
|
|
|
275
346
|
`examples/quickstart.py` is runnable against a real ingest:
|
|
276
347
|
`TELEMETRY_DEV_API_KEY=td_live_... uv run examples/quickstart.py`.
|
|
348
|
+
|
|
349
|
+
## License
|
|
350
|
+
|
|
351
|
+
MIT. See [LICENSE](https://github.com/telemetry-dev/telemetry/blob/main/sdks/python/LICENSE).
|
|
@@ -4,6 +4,8 @@ telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and m
|
|
|
4
4
|
ergonomic functions over OTel spans (`gen_ai.*` semantic conventions), exported as OTLP
|
|
5
5
|
protobuf to the telemetry.dev ingest.
|
|
6
6
|
|
|
7
|
+
Docs: https://telemetry.dev/docs/sdk/python
|
|
8
|
+
|
|
7
9
|
## Install
|
|
8
10
|
|
|
9
11
|
```sh
|
|
@@ -76,6 +78,7 @@ later `init()` generates a new ID. It is not persisted across processes.
|
|
|
76
78
|
| `@observe` / `@observe(name=, type=, capture_input=, capture_output=, attributes=)` | Wrap a sync/async function (or generator) in a span. Arguments become `input` (param-name dict, `self`/`cls` dropped), the return value becomes `output`, exceptions are captured and re-raised. |
|
|
77
79
|
| `start_span(name, *, type="span", ...) -> SpanHandle` | Start a span. `with` activates it in the current context; without `with` it is a detached handle you must `.end()`. |
|
|
78
80
|
| `SpanHandle.update(**fields)` / `.end(**fields, end_time=)` / `.record_output_chunk(timestamp_ms=None)` / `.traceparent()` | Update attributes, end, record arrival of a non-empty output chunk, or read the W3C traceparent. |
|
|
81
|
+
| `SpanHandle.serialize_content(value, key) -> str \| None` | Prepare content an integration writes outside `input` and `output`, such as an error message or a raw attribute, the way `input` and `output` are prepared: the configured `mask` with `key`, JSON serialization (strings pass through), then truncation to `max_attribute_length`. Returns `None` when the mask raises (reported through `on_error`) or the value cannot be serialized; write nothing then. Does not check `capture_input` / `capture_output`. Integrations that support older cores should treat a missing method as a configured mask. |
|
|
79
82
|
| `update_current_span(**fields)` | Apply the update field set to the currently active span (no-op without one). |
|
|
80
83
|
| `propagate_attributes(*, user_id=, session_id=, metadata=)` | Context manager stamping `user.id` / `gen_ai.conversation.id` / `td.metadata.*` on every span and log record started inside (threads/asyncio included via contextvars). |
|
|
81
84
|
| `log(message, *, level="info", event_name=None, attributes=None)` | Emit an OTLP log record to `/v1/logs`, correlated with the current trace. Levels: `debug`/`info`/`warn`/`error` (`"warning"` is accepted as an alias of `warn`). |
|
|
@@ -130,11 +133,12 @@ the core SDK with provider integrations to enable the new metric.
|
|
|
130
133
|
| --- | --- | --- |
|
|
131
134
|
| `api_key`, `base_url`, `environment`, `service_name` | env vars | Connection + resource settings. |
|
|
132
135
|
| `enabled` | `True` | `False` = hard kill switch (tests). |
|
|
136
|
+
| `session_mode` | `"explicit"` | `"process"` groups otherwise uncorrelated spans and logs under one generated session ID per `init()` (see [Environment variables](#environment-variables)). Other values call `on_error` and install a disabled client. |
|
|
133
137
|
| `register_global` | `False` | Also register the tracer provider globally. Applies a default export filter (only `telemetry_dev`-scoped spans are exported); pass `span_filter=lambda s: True` to export everything. |
|
|
134
138
|
| `export_mode` | `"batched"` | `"immediate"` exports synchronously per span/log (serverless). |
|
|
135
139
|
| `log_level` | `"warn"` | SDK diagnostics level: `debug`/`info`/`warn`/`error`/`silent`. |
|
|
136
140
|
| `capture_input`, `capture_output` | `True` | Global content-capture defaults. |
|
|
137
|
-
| `mask` | `None` | `Callable[[Any, MaskContext], Any]` redaction hook, runs before JSON serialization on input/output/log messages (`MaskContext.key` is the attribute being written). Not applied to correlation identifiers. |
|
|
141
|
+
| `mask` | `None` | `Callable[[Any, MaskContext], Any]` redaction hook, runs before JSON serialization on input/output/log messages and `SpanHandle.serialize_content` (`MaskContext.key` is the attribute being written). Not applied to correlation identifiers. |
|
|
138
142
|
| `max_attribute_length` | `65536` | Non-negative integer per-content-attribute cap. Invalid values call `on_error`, do not raise into application code, and install a disabled client; truncated values use the ASCII `...[truncated]` marker, or its prefix when the cap is shorter than the marker. The cap also applies, with the marker, to string metadata and log attributes; any other string attribute is cut at the cap without a marker, except that a cap of `0` leaves those at 65536. |
|
|
139
143
|
| `span_filter` | `None` | Export predicate `Callable[[ReadableSpan], bool]`. |
|
|
140
144
|
| `sampler` | OTel environment configuration | OTel `Sampler` that overrides environment settings. Session roots use its root policy. Real parents keep their sampling decisions. |
|
|
@@ -148,6 +152,11 @@ still record non-content metadata such as response IDs and models, finish reason
|
|
|
148
152
|
and errors. Capture flags do not gate stop sequences, tool descriptions and definitions (`gen_ai.tool.description`, `gen_ai.tool.definitions`), caller-supplied metadata or raw attributes,
|
|
149
153
|
or exception messages and stack traces; redact those separately when needed.
|
|
150
154
|
|
|
155
|
+
`mask` receives input and output content, system instructions, log messages, and content that
|
|
156
|
+
integrations pass to `SpanHandle.serialize_content`. `MaskContext.key` names the attribute being
|
|
157
|
+
written. It does not receive span or propagated `metadata`, raw `attributes`, log attributes, or
|
|
158
|
+
exception messages and stack traces; redact those separately.
|
|
159
|
+
|
|
151
160
|
Session roots share trace IDs from the API key and session ID. The hash matches the TypeScript SDK.
|
|
152
161
|
The SDK uses the configured OTel sampling policy, not the synthetic parent's flags.
|
|
153
162
|
For example, `init(sampler=ParentBased(TraceIdRatioBased(0.1)))` samples sessions with OTel's ratio sampler.
|
|
@@ -228,6 +237,68 @@ safe to attach unconditionally, with only a debug log. Auto-metrics are on by de
|
|
|
228
237
|
GenAI spans, use the `service_name` / `environment` resource settings above, and are skipped
|
|
229
238
|
without an API key.
|
|
230
239
|
|
|
240
|
+
### Native Claude SDK spans
|
|
241
|
+
|
|
242
|
+
The Python `anthropic` SDK 1.14.0 and later emits OpenTelemetry spans natively. The Claude SDK uses the global tracer provider of the application.
|
|
243
|
+
To send these spans to telemetry.dev, register a global tracer provider with `TelemetrySpanProcessor`:
|
|
244
|
+
|
|
245
|
+
```sh
|
|
246
|
+
pip install telemetry-dev "anthropic>=1.14.0"
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from anthropic import Anthropic
|
|
251
|
+
from opentelemetry import trace
|
|
252
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
253
|
+
from telemetry_dev.otel import TelemetrySpanProcessor
|
|
254
|
+
|
|
255
|
+
provider = TracerProvider()
|
|
256
|
+
provider.add_span_processor(TelemetrySpanProcessor())
|
|
257
|
+
trace.set_tracer_provider(provider)
|
|
258
|
+
|
|
259
|
+
client = Anthropic()
|
|
260
|
+
client.messages.create(
|
|
261
|
+
model="claude-sonnet-4-6",
|
|
262
|
+
max_tokens=256,
|
|
263
|
+
messages=[{"role": "user", "content": "Say hello."}],
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
provider.shutdown()
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
If you use `telemetry_dev.init(register_global=True)`, also pass `span_filter=lambda span: True`. The default filter of that mode exports only the spans of this SDK.
|
|
270
|
+
|
|
271
|
+
By default, the spans contain metadata only. To also send prompts and responses, set `ANTHROPIC_OPEN_TELEMETRY_TRACES_CONTENT_MODE=content`. The client reads this variable one time, when you make the client.
|
|
272
|
+
|
|
273
|
+
Do not use the native spans and `telemetry-dev-anthropic` together. If you use the two, each request has two spans.
|
|
274
|
+
|
|
275
|
+
You can also send the native spans without this package. Use an OTLP/HTTP protobuf exporter:
|
|
276
|
+
|
|
277
|
+
```sh
|
|
278
|
+
pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
import os
|
|
283
|
+
|
|
284
|
+
from opentelemetry import trace
|
|
285
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
286
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
287
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
288
|
+
|
|
289
|
+
exporter = OTLPSpanExporter(
|
|
290
|
+
endpoint="https://ingest.telemetry.dev/v1/traces",
|
|
291
|
+
headers={"Authorization": f"Bearer {os.environ['TELEMETRY_DEV_API_KEY']}"},
|
|
292
|
+
)
|
|
293
|
+
provider = TracerProvider()
|
|
294
|
+
provider.add_span_processor(BatchSpanProcessor(exporter))
|
|
295
|
+
trace.set_tracer_provider(provider)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The Claude API can also send server-side spans to telemetry.dev through a Console destination. Set the endpoint to `https://ingest.telemetry.dev`. Set the `Authorization` header to `Bearer <telemetry.dev API key>`. For more information, refer to [Claude Platform](https://telemetry.dev/docs/integrations/claude-platform).
|
|
299
|
+
|
|
300
|
+
When the SDK and the Claude API send the spans of a request to the same telemetry.dev project, telemetry.dev counts the tokens of the request one time, from the API span. The SDK span stays in the trace with no tokens. telemetry.dev matches the two spans by the request ID or the response ID, so the API span does not have to be a child of the SDK span. If the Console destination uses an API key that is scoped to one environment, its API spans take over only the SDK spans of that environment.
|
|
301
|
+
|
|
231
302
|
## Limitations
|
|
232
303
|
|
|
233
304
|
- `register_global=True` cannot be undone on `shutdown()`: OpenTelemetry Python has no public
|
|
@@ -250,3 +321,7 @@ uv build # build wheel + sdist
|
|
|
250
321
|
|
|
251
322
|
`examples/quickstart.py` is runnable against a real ingest:
|
|
252
323
|
`TELEMETRY_DEV_API_KEY=td_live_... uv run examples/quickstart.py`.
|
|
324
|
+
|
|
325
|
+
## License
|
|
326
|
+
|
|
327
|
+
MIT. See [LICENSE](https://github.com/telemetry-dev/telemetry/blob/main/sdks/python/LICENSE).
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import re
|
|
5
6
|
from collections.abc import Iterable, Mapping, Sequence
|
|
6
7
|
from itertools import chain
|
|
7
8
|
from typing import Final, cast
|
|
@@ -11,6 +12,36 @@ _DEFAULT_MAX_ITEMS: Final = 1024
|
|
|
11
12
|
_MAX_DEPTH: Final = 32
|
|
12
13
|
_BASE_ITEM_BYTES: Final = 16
|
|
13
14
|
|
|
15
|
+
# Code points whose JSON form is longer than their UTF-8 form, mapped to the extra bytes.
|
|
16
|
+
_SHORT_ESCAPE_EXTRA: Final = 1 # '"', '\\', \b \t \n \f \r: 1 byte raw, 2 escaped
|
|
17
|
+
_CONTROL_ESCAPE_EXTRA: Final = 5 # other code points below 0x20: 1 byte raw, 6 as \u00XX
|
|
18
|
+
_SURROGATE_ESCAPE_EXTRA: Final = 3 # a surrogate code point: 3 bytes raw, 6 as \uXXXX
|
|
19
|
+
_JSON_ESCAPED: Final = re.compile('["\\\\\x00-\x1f\ud800-\udfff]')
|
|
20
|
+
_JSON_STRING_CHUNK: Final = 4096
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _escape_extra(character: str) -> int:
|
|
24
|
+
code = ord(character)
|
|
25
|
+
if character in '"\\\b\t\n\f\r':
|
|
26
|
+
return _SHORT_ESCAPE_EXTRA
|
|
27
|
+
if code < 0x20:
|
|
28
|
+
return _CONTROL_ESCAPE_EXTRA
|
|
29
|
+
return _SURROGATE_ESCAPE_EXTRA
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _json_string_bytes(value: str, limit: int | None) -> int:
|
|
33
|
+
# Every code point costs at least one byte, so a string longer than the limit needs no scan.
|
|
34
|
+
if limit is not None and len(value) > limit:
|
|
35
|
+
return len(value)
|
|
36
|
+
size = 0
|
|
37
|
+
for start in range(0, len(value), _JSON_STRING_CHUNK):
|
|
38
|
+
chunk = value[start : start + _JSON_STRING_CHUNK]
|
|
39
|
+
size += len(chunk.encode("utf-8", "surrogatepass"))
|
|
40
|
+
size += sum(_escape_extra(match.group()) for match in _JSON_ESCAPED.finditer(chunk))
|
|
41
|
+
if limit is not None and size > limit:
|
|
42
|
+
return size
|
|
43
|
+
return size
|
|
44
|
+
|
|
14
45
|
|
|
15
46
|
class CaptureBudget:
|
|
16
47
|
"""Track a hard byte and item budget before retaining streamed provider data."""
|
|
@@ -32,6 +63,18 @@ class CaptureBudget:
|
|
|
32
63
|
max_bytes=client.max_attribute_length if client is not None else _DEFAULT_MAX_BYTES
|
|
33
64
|
)
|
|
34
65
|
|
|
66
|
+
@staticmethod
|
|
67
|
+
def json_string_bytes(value: str, limit: int | None = None) -> int:
|
|
68
|
+
"""Return the UTF-8 bytes ``value`` occupies inside a JSON string, without the quotes.
|
|
69
|
+
|
|
70
|
+
Matches the TypeScript SDK's measure: ``"``, ``\\``, and ``\\b \\t \\n \\f \\r`` cost 2
|
|
71
|
+
bytes, other control characters cost 6 (``\\u00XX``), and every surrogate code point
|
|
72
|
+
costs 6 (``\\uXXXX``); anything else costs its UTF-8 width. The budget charges a string
|
|
73
|
+
this plus its per-item cost, which covers the quotes and separators. Counting stops
|
|
74
|
+
once the total passes ``limit``, so a result above ``limit`` is only a lower bound.
|
|
75
|
+
"""
|
|
76
|
+
return _json_string_bytes(value, limit)
|
|
77
|
+
|
|
35
78
|
@property
|
|
36
79
|
def remaining_bytes(self) -> int:
|
|
37
80
|
"""Return bytes still available for retained capture state."""
|
|
@@ -98,20 +141,9 @@ class CaptureBudget:
|
|
|
98
141
|
if value is None or isinstance(value, bool | int | float):
|
|
99
142
|
return byte_count, item_count
|
|
100
143
|
if isinstance(value, str):
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
1
|
|
105
|
-
if codepoint <= 0x7F
|
|
106
|
-
else 2
|
|
107
|
-
if codepoint <= 0x7FF
|
|
108
|
-
else 3
|
|
109
|
-
if codepoint <= 0xFFFF
|
|
110
|
-
else 4
|
|
111
|
-
)
|
|
112
|
-
if byte_count > remaining_bytes:
|
|
113
|
-
return None
|
|
114
|
-
return byte_count, item_count
|
|
144
|
+
# Mapping keys come through here too, so they are charged at their escaped size.
|
|
145
|
+
byte_count += _json_string_bytes(value, remaining_bytes - byte_count)
|
|
146
|
+
return (byte_count, item_count) if byte_count <= remaining_bytes else None
|
|
115
147
|
if isinstance(value, bytes | bytearray | memoryview):
|
|
116
148
|
length = value.nbytes if isinstance(value, memoryview) else len(value)
|
|
117
149
|
# Structured attributes serialize binary values as base64 text.
|
|
@@ -66,11 +66,10 @@ def serialize_content(
|
|
|
66
66
|
report_error(on_error, f"mask hook raised for attribute '{key}'", exc)
|
|
67
67
|
return None
|
|
68
68
|
try:
|
|
69
|
-
|
|
69
|
+
return truncate(stringify(value), max_len)
|
|
70
70
|
except BaseException as exc:
|
|
71
71
|
report_error(on_error, f"failed to serialize attribute '{key}'", exc)
|
|
72
72
|
return None
|
|
73
|
-
return truncate(text, max_len)
|
|
74
73
|
|
|
75
74
|
|
|
76
75
|
def coerce_attr_value(
|
|
@@ -396,6 +396,19 @@ class SpanHandle:
|
|
|
396
396
|
if self._client is not None:
|
|
397
397
|
self._client.report("provider instrumentation failed", cause)
|
|
398
398
|
|
|
399
|
+
def serialize_content(self, value: Any, key: str) -> str | None:
|
|
400
|
+
"""Prepare content written outside ``input`` and ``output`` the way those are prepared.
|
|
401
|
+
|
|
402
|
+
Runs the configured mask with ``key`` (the attribute the result is written to), JSON
|
|
403
|
+
serialization (strings pass through), then truncation to ``max_attribute_length``.
|
|
404
|
+
Returns None when the mask raises (reported to ``on_error``) or the value cannot be
|
|
405
|
+
serialized. Capture flags are not checked. Cores that predate this method lack it;
|
|
406
|
+
treat a missing method as masked.
|
|
407
|
+
"""
|
|
408
|
+
if self._client is None:
|
|
409
|
+
return None
|
|
410
|
+
return self._client.serialize(value, key)
|
|
411
|
+
|
|
399
412
|
def _recording(self) -> bool:
|
|
400
413
|
return (
|
|
401
414
|
self._client is not None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|