telemetry-dev 0.2.6__tar.gz → 0.2.8__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: telemetry-dev
3
- Version: 0.2.6
3
+ Version: 0.2.8
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
@@ -14,9 +14,9 @@ Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Typing :: Typed
17
- Requires-Dist: opentelemetry-api>=1.35.0,<2
18
- Requires-Dist: opentelemetry-sdk>=1.35.0,<2
19
- Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.35.0,<2
17
+ Requires-Dist: opentelemetry-api>=1.39.0,<2
18
+ Requires-Dist: opentelemetry-sdk>=1.39.0,<2
19
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.39.0,<2
20
20
  Requires-Python: >=3.10
21
21
  Project-URL: Homepage, https://telemetry.dev
22
22
  Project-URL: Repository, https://github.com/telemetry-dev/telemetry
@@ -159,14 +159,19 @@ the core SDK with provider integrations to enable the new metric.
159
159
  | `log_level` | `"warn"` | SDK diagnostics level: `debug`/`info`/`warn`/`error`/`silent`. |
160
160
  | `capture_input`, `capture_output` | `True` | Global content-capture defaults. |
161
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. |
162
- | `max_attribute_length` | `65536` | Per-content-attribute cap; truncated values get an ASCII `...[truncated]` marker appended. |
162
+ | `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
163
  | `span_filter` | `None` | Export predicate `Callable[[ReadableSpan], bool]`. |
164
164
  | `sampler` | OTel environment configuration | OTel `Sampler` that overrides environment settings. Session roots use its root policy. Real parents keep their sampling decisions. |
165
- | `on_error` | `None` | Receives every internal SDK error; the SDK never raises. |
165
+ | `on_error` | `None` | Receives internal SDK errors; repeated instrumentation failures from one stream may be coalesced into one report. The SDK never raises them into application code. |
166
166
  | `disable_atexit` | `False` | Skip the automatic atexit shutdown. |
167
167
  | `timeout` | `10.0` | OTLP HTTP timeout in seconds. |
168
168
  | `span_exporter`, `log_exporter`, `metric_reader` | `None` | Test seams / offline mode; any of them enables the client without an API key. |
169
169
 
170
+ `capture_input=False` and `capture_output=False` omit captured content. Provider integrations may
171
+ still record non-content metadata such as response IDs and models, finish reasons, usage, timing,
172
+ 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
+ or exception messages and stack traces; redact those separately when needed.
174
+
170
175
  Session roots share trace IDs from the API key and session ID. The hash matches the TypeScript SDK.
171
176
  The SDK uses the configured OTel sampling policy, not the synthetic parent's flags.
172
177
  For example, `init(sampler=ParentBased(TraceIdRatioBased(0.1)))` samples sessions with OTel's ratio sampler.
@@ -135,14 +135,19 @@ the core SDK with provider integrations to enable the new metric.
135
135
  | `log_level` | `"warn"` | SDK diagnostics level: `debug`/`info`/`warn`/`error`/`silent`. |
136
136
  | `capture_input`, `capture_output` | `True` | Global content-capture defaults. |
137
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. |
138
- | `max_attribute_length` | `65536` | Per-content-attribute cap; truncated values get an ASCII `...[truncated]` marker appended. |
138
+ | `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
139
  | `span_filter` | `None` | Export predicate `Callable[[ReadableSpan], bool]`. |
140
140
  | `sampler` | OTel environment configuration | OTel `Sampler` that overrides environment settings. Session roots use its root policy. Real parents keep their sampling decisions. |
141
- | `on_error` | `None` | Receives every internal SDK error; the SDK never raises. |
141
+ | `on_error` | `None` | Receives internal SDK errors; repeated instrumentation failures from one stream may be coalesced into one report. The SDK never raises them into application code. |
142
142
  | `disable_atexit` | `False` | Skip the automatic atexit shutdown. |
143
143
  | `timeout` | `10.0` | OTLP HTTP timeout in seconds. |
144
144
  | `span_exporter`, `log_exporter`, `metric_reader` | `None` | Test seams / offline mode; any of them enables the client without an API key. |
145
145
 
146
+ `capture_input=False` and `capture_output=False` omit captured content. Provider integrations may
147
+ still record non-content metadata such as response IDs and models, finish reasons, usage, timing,
148
+ 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
+ or exception messages and stack traces; redact those separately when needed.
150
+
146
151
  Session roots share trace IDs from the API key and session ID. The hash matches the TypeScript SDK.
147
152
  The SDK uses the configured OTel sampling policy, not the synthetic parent's flags.
148
153
  For example, `init(sampler=ParentBased(TraceIdRatioBased(0.1)))` samples sessions with OTel's ratio sampler.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "telemetry-dev"
3
- version = "0.2.6"
3
+ version = "0.2.8"
4
4
  description = "telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -25,9 +25,9 @@ classifiers = [
25
25
  "Typing :: Typed",
26
26
  ]
27
27
  dependencies = [
28
- "opentelemetry-api>=1.35.0,<2",
29
- "opentelemetry-sdk>=1.35.0,<2",
30
- "opentelemetry-exporter-otlp-proto-http>=1.35.0,<2",
28
+ "opentelemetry-api>=1.39.0,<2",
29
+ "opentelemetry-sdk>=1.39.0,<2",
30
+ "opentelemetry-exporter-otlp-proto-http>=1.39.0,<2",
31
31
  ]
32
32
 
33
33
  [[project.authors]]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "telemetry-dev"
3
- version = "0.2.6"
3
+ version = "0.2.8"
4
4
  description = "telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -19,9 +19,9 @@ classifiers = [
19
19
  "Typing :: Typed",
20
20
  ]
21
21
  dependencies = [
22
- "opentelemetry-api>=1.35.0,<2",
23
- "opentelemetry-sdk>=1.35.0,<2",
24
- "opentelemetry-exporter-otlp-proto-http>=1.35.0,<2",
22
+ "opentelemetry-api>=1.39.0,<2",
23
+ "opentelemetry-sdk>=1.39.0,<2",
24
+ "opentelemetry-exporter-otlp-proto-http>=1.39.0,<2",
25
25
  ]
26
26
 
27
27
  [project.urls]
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from collections.abc import Iterable
5
+ from collections.abc import Iterable, Mapping, Sequence
6
6
  from itertools import chain
7
7
  from typing import Final, cast
8
8
 
@@ -123,16 +123,18 @@ class CaptureBudget:
123
123
  return byte_count, item_count
124
124
  seen.add(value_id)
125
125
  try:
126
- mapping: dict[object, object] | None = None
126
+ mapping: Mapping[object, object] | None = None
127
127
  children: Iterable[object] = ()
128
- if isinstance(value, dict):
129
- mapping = cast("dict[object, object]", value)
130
- elif isinstance(value, list | tuple | set | frozenset):
128
+ if isinstance(value, Mapping):
129
+ mapping = cast("Mapping[object, object]", value)
130
+ elif isinstance(value, Sequence) and not isinstance(value, str | bytes | bytearray):
131
+ children = cast("Iterable[object]", value)
132
+ elif isinstance(value, set | frozenset):
131
133
  children = cast("Iterable[object]", value)
132
134
  else:
133
135
  attributes = getattr(value, "__dict__", None)
134
- if isinstance(attributes, dict):
135
- mapping = cast("dict[object, object]", attributes)
136
+ if isinstance(attributes, Mapping):
137
+ mapping = cast("Mapping[object, object]", attributes)
136
138
 
137
139
  if mapping is not None:
138
140
  children = chain.from_iterable(mapping.items())
@@ -41,10 +41,15 @@ from ._config import (
41
41
  resolve_config,
42
42
  )
43
43
  from ._context import SessionSampler
44
- from ._metrics import GuardedOTLPMetricExporter, MetricsRecorder, OutputChunkAggregation
44
+ from ._metrics import (
45
+ GuardedOTLPMetricExporter,
46
+ MetricsRecorder,
47
+ OutputChunkAggregation,
48
+ sdk_meter_provider,
49
+ )
45
50
  from ._processor import ExportMode, StampingSpanProcessor
46
51
  from ._semconv import SCOPE_NAME
47
- from ._serialize import Mask, serialize_content
52
+ from ._serialize import DEFAULT_MAX_ATTRIBUTE_LENGTH, Mask, serialize_content
48
53
 
49
54
  _lock = threading.RLock()
50
55
  _client: Client | None = None
@@ -177,14 +182,16 @@ class Client:
177
182
  )
178
183
 
179
184
  # The funnel keeps capped content (marker included) within max_attribute_length, so
180
- # the backstop can use the same cap for raw attributes set outside the funnel.
185
+ # the backstop can use the same cap for raw attributes set outside the funnel. A zero
186
+ # cap only drops content; as a backstop it would blank every string attribute.
187
+ backstop_length = max_attribute_length or DEFAULT_MAX_ATTRIBUTE_LENGTH
181
188
  self._tracer_provider = TracerProvider(
182
189
  resource=resource,
183
190
  sampler=sampler,
184
191
  shutdown_on_exit=False,
185
192
  span_limits=SpanLimits(
186
- max_attribute_length=max_attribute_length,
187
- max_span_attribute_length=max_attribute_length,
193
+ max_attribute_length=backstop_length,
194
+ max_span_attribute_length=backstop_length,
188
195
  ),
189
196
  )
190
197
  self._tracer_provider.sampler = SessionSampler(self._tracer_provider.sampler)
@@ -207,9 +214,7 @@ class Client:
207
214
  )
208
215
  metrics_recorder: MetricsRecorder | None = None
209
216
  if metric_reader is not None:
210
- self._meter_provider = MeterProvider(
211
- metric_readers=[metric_reader], resource=resource, shutdown_on_exit=False
212
- )
217
+ self._meter_provider = sdk_meter_provider(metric_reader, resource)
213
218
  meter = self._meter_provider.get_meter(SCOPE_NAME, SDK_VERSION)
214
219
  metrics_recorder = MetricsRecorder(
215
220
  meter, on_error=on_error, output_chunks=self._output_chunks
@@ -362,6 +367,14 @@ def init(
362
367
  try:
363
368
  if session_mode not in ("explicit", "process"):
364
369
  raise ValueError(f"invalid session_mode: {session_mode!r}")
370
+ raw_max_attribute_length: Any = max_attribute_length
371
+ if (
372
+ isinstance(raw_max_attribute_length, bool)
373
+ or not isinstance(raw_max_attribute_length, int)
374
+ or raw_max_attribute_length < 0
375
+ ):
376
+ raise ValueError("max_attribute_length must be a non-negative integer")
377
+ max_attribute_length = raw_max_attribute_length
365
378
  client = Client(
366
379
  config=config,
367
380
  enabled=effective_enabled,
@@ -9,16 +9,20 @@ from typing import Any
9
9
 
10
10
  from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
11
11
  from opentelemetry.metrics import Meter
12
+ from opentelemetry.sdk.metrics import MeterProvider
12
13
  from opentelemetry.sdk.metrics.export import (
13
14
  AggregationTemporality,
14
15
  Histogram,
15
16
  HistogramDataPoint,
16
17
  Metric,
17
18
  MetricExportResult,
19
+ MetricReader,
18
20
  MetricsData,
19
21
  ResourceMetrics,
20
22
  ScopeMetrics,
21
23
  )
24
+ from opentelemetry.sdk.metrics.view import DropAggregation, View
25
+ from opentelemetry.sdk.resources import Resource
22
26
  from opentelemetry.sdk.trace import ReadableSpan
23
27
  from opentelemetry.trace import INVALID_SPAN_CONTEXT, NonRecordingSpan, set_span_in_context
24
28
 
@@ -41,6 +45,23 @@ _MAX_CHUNK_ATTRIBUTE_SETS = 2000
41
45
  _CHUNK_METRIC_NAME = "gen_ai.client.operation.time_per_output_chunk"
42
46
 
43
47
 
48
+ def sdk_meter_provider(reader: MetricReader, resource: Resource) -> MeterProvider:
49
+ return MeterProvider(
50
+ metric_readers=[reader],
51
+ resource=resource,
52
+ shutdown_on_exit=False,
53
+ # OpenTelemetry records metric-reader metrics on the reader's own provider (always in
54
+ # 1.41, opt-in from 1.42); they must not be exported to telemetry.dev.
55
+ views=[
56
+ View(
57
+ meter_name="opentelemetry-sdk",
58
+ instrument_name="otel.sdk.*",
59
+ aggregation=DropAggregation(),
60
+ )
61
+ ],
62
+ )
63
+
64
+
44
65
  @dataclass
45
66
  class _ChunkAggregate:
46
67
  count: int
@@ -31,9 +31,9 @@ def truncate(text: str, max_len: int) -> str:
31
31
  encoded = text.encode("utf-16-le")
32
32
  if len(encoded) <= max_len * 2:
33
33
  return text
34
- # Total stays within max_len so the provider's attribute_value_length_limit backstop,
35
- # set to the same cap, never slices the marker off.
36
- head = encoded[: max(max_len - len(TRUNCATION_MARKER), 0) * 2]
34
+ if max_len < len(TRUNCATION_MARKER):
35
+ return TRUNCATION_MARKER[: max(max_len, 0)]
36
+ head = encoded[: (max_len - len(TRUNCATION_MARKER)) * 2]
37
37
  try:
38
38
  return head.decode("utf-16-le") + TRUNCATION_MARKER
39
39
  except UnicodeDecodeError:
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import math
4
+ import sys
4
5
  import threading
5
6
  import time
6
7
  import traceback
@@ -267,7 +268,7 @@ def _fields_to_attributes(
267
268
  for key, value in raw.items():
268
269
  attr = coerce_attr_value(
269
270
  value,
270
- max_len=client.max_attribute_length,
271
+ max_len=sys.maxsize,
271
272
  key=key,
272
273
  on_error=client.on_error,
273
274
  )
@@ -379,6 +380,22 @@ class SpanHandle:
379
380
  self._context_token: Token[Context] | None = None
380
381
  self._ended = False
381
382
 
383
+ @property
384
+ def capture_output(self) -> bool:
385
+ return self._state.capture_output if self._state is not None else False
386
+
387
+ @property
388
+ def capture_masked(self) -> bool:
389
+ return self._client is not None and self._client.mask is not None
390
+
391
+ @property
392
+ def max_attribute_length(self) -> int:
393
+ return self._client.max_attribute_length if self._client is not None else 0
394
+
395
+ def report_error(self, cause: BaseException) -> None:
396
+ if self._client is not None:
397
+ self._client.report("provider instrumentation failed", cause)
398
+
382
399
  def _recording(self) -> bool:
383
400
  return (
384
401
  self._client is not None
@@ -22,7 +22,7 @@ from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
22
22
  from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
23
23
 
24
24
  from ._config import SDK_VERSION, logger, report_error, resolve_config
25
- from ._metrics import GuardedOTLPMetricExporter, MetricsRecorder
25
+ from ._metrics import GuardedOTLPMetricExporter, MetricsRecorder, sdk_meter_provider
26
26
  from ._processor import ExportMode, StampingSpanProcessor
27
27
  from ._semconv import SCOPE_NAME
28
28
 
@@ -137,9 +137,7 @@ class TelemetrySpanProcessor(SpanProcessor):
137
137
  else _DORMANT_METRIC_INTERVAL_MILLIS
138
138
  ),
139
139
  )
140
- self._meter_provider = MeterProvider(
141
- metric_readers=[reader], resource=resource, shutdown_on_exit=False
142
- )
140
+ self._meter_provider = sdk_meter_provider(reader, resource)
143
141
  meter = self._meter_provider.get_meter(SCOPE_NAME, SDK_VERSION)
144
142
  metrics_recorder = MetricsRecorder(meter, on_error=on_error)
145
143
 
File without changes