telemetry-dev 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.
- telemetry_dev-0.1.0/PKG-INFO +235 -0
- telemetry_dev-0.1.0/README.md +212 -0
- telemetry_dev-0.1.0/pyproject.toml +56 -0
- telemetry_dev-0.1.0/src/telemetry_dev/__init__.py +55 -0
- telemetry_dev-0.1.0/src/telemetry_dev/_client.py +390 -0
- telemetry_dev-0.1.0/src/telemetry_dev/_config.py +81 -0
- telemetry_dev-0.1.0/src/telemetry_dev/_context.py +101 -0
- telemetry_dev-0.1.0/src/telemetry_dev/_logs.py +65 -0
- telemetry_dev-0.1.0/src/telemetry_dev/_metrics.py +120 -0
- telemetry_dev-0.1.0/src/telemetry_dev/_observe.py +142 -0
- telemetry_dev-0.1.0/src/telemetry_dev/_processor.py +77 -0
- telemetry_dev-0.1.0/src/telemetry_dev/_semconv.py +115 -0
- telemetry_dev-0.1.0/src/telemetry_dev/_serialize.py +103 -0
- telemetry_dev-0.1.0/src/telemetry_dev/_spans.py +676 -0
- telemetry_dev-0.1.0/src/telemetry_dev/otel.py +186 -0
- telemetry_dev-0.1.0/src/telemetry_dev/py.typed +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: telemetry-dev
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics
|
|
5
|
+
Keywords: telemetry,opentelemetry,llm,genai,tracing,observability
|
|
6
|
+
Author: telemetry.dev
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Dist: opentelemetry-api>=1.35.0,<2
|
|
17
|
+
Requires-Dist: opentelemetry-sdk>=1.35.0,<2
|
|
18
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.35.0,<2
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Project-URL: Homepage, https://telemetry.dev
|
|
21
|
+
Project-URL: Repository, https://github.com/telemetry-dev/telemetry.dev
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# telemetry-dev
|
|
25
|
+
|
|
26
|
+
telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics. Thin
|
|
27
|
+
ergonomic functions over OTel spans (`gen_ai.*` semantic conventions), exported as OTLP
|
|
28
|
+
protobuf to the telemetry.dev ingest.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
pip install telemetry-dev
|
|
34
|
+
# or
|
|
35
|
+
uv add telemetry-dev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Requires Python >= 3.10.
|
|
39
|
+
|
|
40
|
+
## Quickstart
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import telemetry_dev
|
|
44
|
+
from telemetry_dev import log, observe, propagate_attributes, start_span, update_current_span
|
|
45
|
+
|
|
46
|
+
telemetry_dev.init() # reads TELEMETRY_DEV_API_KEY from the environment
|
|
47
|
+
|
|
48
|
+
@observe # arguments -> input, return value -> output, errors captured + re-raised
|
|
49
|
+
def lookup_weather(city: str) -> dict:
|
|
50
|
+
return {"forecast": "sunny"}
|
|
51
|
+
|
|
52
|
+
with propagate_attributes(user_id="user_123", session_id="session_456"):
|
|
53
|
+
with start_span(
|
|
54
|
+
"chat gpt-4o",
|
|
55
|
+
type="generation",
|
|
56
|
+
model="gpt-4o",
|
|
57
|
+
provider="openai",
|
|
58
|
+
input=[{"role": "user", "content": "Plan a day trip"}],
|
|
59
|
+
):
|
|
60
|
+
log("calling the model")
|
|
61
|
+
update_current_span(
|
|
62
|
+
output=[{"role": "assistant", "content": "Here you go..."}],
|
|
63
|
+
usage={"input_tokens": 11, "output_tokens": 7},
|
|
64
|
+
finish_reason="stop",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
lookup_weather("Kyoto")
|
|
68
|
+
|
|
69
|
+
telemetry_dev.flush()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The SDK fails open: without an API key every call is a silent no-op, and internal errors are
|
|
73
|
+
routed to the `on_error` hook / `telemetry_dev` logger — never raised into your code.
|
|
74
|
+
|
|
75
|
+
## Environment variables
|
|
76
|
+
|
|
77
|
+
| Variable | Default | Purpose |
|
|
78
|
+
| --- | --- | --- |
|
|
79
|
+
| `TELEMETRY_DEV_API_KEY` | — | Ingest key (`td_live_...`). Absent = SDK is a no-op. |
|
|
80
|
+
| `TELEMETRY_DEV_BASE_URL` | `https://ingest.telemetry.dev` | Ingest base URL (trailing slashes stripped). |
|
|
81
|
+
| `TELEMETRY_DEV_ENVIRONMENT` | `production` | Deployment environment label. |
|
|
82
|
+
| `OTEL_SERVICE_NAME` | `unknown_service` | Service name on every trace. |
|
|
83
|
+
|
|
84
|
+
Explicit `init()` arguments take precedence over environment variables.
|
|
85
|
+
|
|
86
|
+
## API reference
|
|
87
|
+
|
|
88
|
+
| Name | Description |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| `init(**options) -> Client` | Initialize the SDK (see options below). Calling again replaces the previous client. |
|
|
91
|
+
| `@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. |
|
|
92
|
+
| `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()`. |
|
|
93
|
+
| `SpanHandle.update(**fields)` / `.end(**fields, end_time=)` / `.traceparent()` | Update attributes, end (accepts the full update field set), or read the W3C traceparent. |
|
|
94
|
+
| `update_current_span(**fields)` | Apply the update field set to the currently active span (no-op without one). |
|
|
95
|
+
| `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). |
|
|
96
|
+
| `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`). |
|
|
97
|
+
| `get_traceparent() -> str \| None` | W3C traceparent of the current context. |
|
|
98
|
+
| `flush(timeout_s=10.0)` / `shutdown(timeout_s=10.0)` | Force-flush / tear down traces + logs + metrics. Shutdown also runs atexit unless `disable_atexit=True`. |
|
|
99
|
+
| `TelemetrySpanProcessor` / `telemetry_dev.otel.create_telemetry_span_exporter` | Bring-your-own-OTel helpers (below). |
|
|
100
|
+
| `MaskContext`, `Usage`, `SpanHandle`, `Client`, `NOT_GIVEN` | Supporting types. |
|
|
101
|
+
|
|
102
|
+
### Span types
|
|
103
|
+
|
|
104
|
+
`type=` maps to `gen_ai.operation.name`:
|
|
105
|
+
|
|
106
|
+
| `type` | operation | input / output attributes |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| `"span"` (default) | `function` | `gen_ai.input.messages` / `gen_ai.output.messages` |
|
|
109
|
+
| `"generation"` | `chat` | `gen_ai.input.messages` / `gen_ai.output.messages` |
|
|
110
|
+
| `"tool"` | `execute_tool` | `gen_ai.tool.call.arguments` / `gen_ai.tool.call.result` |
|
|
111
|
+
| `"agent"` | `invoke_agent` | `gen_ai.input.messages` / `gen_ai.output.messages` |
|
|
112
|
+
| `"embedding"` | `embeddings` | `gen_ai.input.messages` / `gen_ai.output.messages` |
|
|
113
|
+
|
|
114
|
+
### Span fields (start/update/end)
|
|
115
|
+
|
|
116
|
+
`input`, `output`, `model`, `provider`, `system_instructions`, `response_model`, `response_id`,
|
|
117
|
+
`output_type`, `finish_reason`, `usage` (dict with exactly `input_tokens`, `output_tokens`,
|
|
118
|
+
`total_tokens`, `cache_read_input_tokens`, `cache_creation_input_tokens`,
|
|
119
|
+
`reasoning_output_tokens`), `cost_usd`, `temperature`, `top_p`, `top_k`, `max_tokens`,
|
|
120
|
+
`stop_sequences`, `seed`, `frequency_penalty`, `presence_penalty`, `time_to_first_chunk_ms`,
|
|
121
|
+
`tool_name`, `tool_call_id`, `tool_description`, `agent_name`, `agent_id`, `metadata`
|
|
122
|
+
(→ `td.metadata.*`, this span only), `attributes` (raw escape hatch, merged last), `error`.
|
|
123
|
+
`start_span` additionally accepts `parent` (traceparent string, OTel `Context`, or
|
|
124
|
+
`SpanContext`), `start_time`, and per-call `capture_input` / `capture_output` overrides;
|
|
125
|
+
`end()` additionally accepts `end_time`.
|
|
126
|
+
|
|
127
|
+
### init() options
|
|
128
|
+
|
|
129
|
+
| Option | Default | Purpose |
|
|
130
|
+
| --- | --- | --- |
|
|
131
|
+
| `api_key`, `base_url`, `environment`, `service_name` | env vars | Connection + resource settings. |
|
|
132
|
+
| `enabled` | `True` | `False` = hard kill switch (tests). |
|
|
133
|
+
| `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
|
+
| `export_mode` | `"batched"` | `"immediate"` exports synchronously per span/log (serverless). |
|
|
135
|
+
| `log_level` | `"warn"` | SDK diagnostics level: `debug`/`info`/`warn`/`error`/`silent`. |
|
|
136
|
+
| `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. |
|
|
138
|
+
| `max_attribute_length` | `65536` | Per-content-attribute cap; truncated values get an ASCII `...[truncated]` marker appended. |
|
|
139
|
+
| `span_filter` | `None` | Export predicate `Callable[[ReadableSpan], bool]`. |
|
|
140
|
+
| `on_error` | `None` | Receives every internal SDK error; the SDK never raises. |
|
|
141
|
+
| `disable_atexit` | `False` | Skip the automatic atexit shutdown. |
|
|
142
|
+
| `timeout` | `10.0` | OTLP HTTP timeout in seconds. |
|
|
143
|
+
| `span_exporter`, `log_exporter`, `metric_reader` | `None` | Test seams / offline mode; any of them enables the client without an API key. |
|
|
144
|
+
|
|
145
|
+
## Auto-metrics
|
|
146
|
+
|
|
147
|
+
Ended spans automatically record two histograms (DELTA temporality, exported every 60s):
|
|
148
|
+
|
|
149
|
+
- `gen_ai.client.operation.duration` (unit `s`) for `chat`, `invoke_agent`, `embeddings`,
|
|
150
|
+
`execute_tool`
|
|
151
|
+
- `gen_ai.client.token.usage` (unit `{token}`, attribute `gen_ai.token.type=input|output`) for
|
|
152
|
+
`chat`, `invoke_agent`, `embeddings`
|
|
153
|
+
|
|
154
|
+
Plain spans (`function`) record no metrics. Quiet intervals produce zero metric requests.
|
|
155
|
+
|
|
156
|
+
## Serverless
|
|
157
|
+
|
|
158
|
+
Use `export_mode="immediate"` and/or call `telemetry_dev.flush()` before the runtime freezes:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
telemetry_dev.init(export_mode="immediate")
|
|
162
|
+
...
|
|
163
|
+
telemetry_dev.flush() # force-flush traces + logs + metrics
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Bring your own OTel (`telemetry_dev.otel`)
|
|
167
|
+
|
|
168
|
+
If you already run an OpenTelemetry SDK, attach the telemetry.dev processor to your provider
|
|
169
|
+
instead of calling `init()`:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from telemetry_dev.otel import TelemetrySpanProcessor
|
|
173
|
+
|
|
174
|
+
your_tracer_provider.add_span_processor(TelemetrySpanProcessor()) # reads TELEMETRY_DEV_API_KEY
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
If you need to wire your own span processor stack, create the telemetry.dev exporter directly:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
181
|
+
from telemetry_dev.otel import create_telemetry_span_exporter
|
|
182
|
+
|
|
183
|
+
span_exporter = create_telemetry_span_exporter()
|
|
184
|
+
your_tracer_provider.add_span_processor(BatchSpanProcessor(span_exporter))
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`TelemetrySpanProcessor` remains available from the package root for compatibility:
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from telemetry_dev import TelemetrySpanProcessor
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
| Option | Default | Purpose |
|
|
194
|
+
| --- | --- | --- |
|
|
195
|
+
| `api_key` | `TELEMETRY_DEV_API_KEY` | Ingest key. Absent with no `span_exporter` = inert no-op. |
|
|
196
|
+
| `base_url` | `TELEMETRY_DEV_BASE_URL` or `https://ingest.telemetry.dev` | Ingest base URL; trailing slashes are stripped. |
|
|
197
|
+
| `export_mode` | `"batched"` | `"batched"` or `"immediate"` span export. |
|
|
198
|
+
| `max_export_batch_size` | `64` | BatchSpanProcessor export batch size. |
|
|
199
|
+
| `schedule_delay_millis` | `1000` | BatchSpanProcessor schedule delay. |
|
|
200
|
+
| `max_queue_size` | `2048` | BatchSpanProcessor queue size. |
|
|
201
|
+
| `export_timeout_millis` | `30000` | BatchSpanProcessor export timeout. |
|
|
202
|
+
| `span_filter` | `None` | Export predicate `Callable[[ReadableSpan], bool]`; failures export the span. |
|
|
203
|
+
| `metrics` | `True` | Auto-record GenAI duration/token histograms for exported spans when an API key is available. |
|
|
204
|
+
| `service_name` | `OTEL_SERVICE_NAME` or `unknown_service` | `service.name` on the metrics resource. |
|
|
205
|
+
| `environment` | `TELEMETRY_DEV_ENVIRONMENT` or `production` | `deployment.environment.name` on the metrics resource. |
|
|
206
|
+
| `on_error` | `None` | Receives internal processor errors; errors are never raised into your code. |
|
|
207
|
+
| `span_exporter` | `None` | Advanced/test seam replacing the telemetry.dev OTLP trace exporter. |
|
|
208
|
+
|
|
209
|
+
Without an API key and without `span_exporter`, `TelemetrySpanProcessor()` is an inert no-op:
|
|
210
|
+
safe to attach unconditionally, with only a debug log. Auto-metrics are on by default for exported
|
|
211
|
+
GenAI spans, use the `service_name` / `environment` resource settings above, and are skipped
|
|
212
|
+
without an API key.
|
|
213
|
+
|
|
214
|
+
## Limitations
|
|
215
|
+
|
|
216
|
+
- `register_global=True` cannot be undone on `shutdown()`: OpenTelemetry Python has no public
|
|
217
|
+
API to unregister a global `TracerProvider`, so a later `init(register_global=True)` in the
|
|
218
|
+
same process cannot reclaim the global slot. Prefer the isolated default (or the BYO
|
|
219
|
+
processor) for processes that re-initialize.
|
|
220
|
+
|
|
221
|
+
## Development
|
|
222
|
+
|
|
223
|
+
Uses [uv](https://docs.astral.sh/uv/):
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
uv sync # install (writes uv.lock)
|
|
227
|
+
uv run pytest # tests
|
|
228
|
+
uv run ruff format . # format
|
|
229
|
+
uv run ruff check . # lint
|
|
230
|
+
uv run pyright # type-check
|
|
231
|
+
uv build # build wheel + sdist
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`examples/quickstart.py` is runnable against a real ingest:
|
|
235
|
+
`TELEMETRY_DEV_API_KEY=td_live_... uv run examples/quickstart.py`.
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# telemetry-dev
|
|
2
|
+
|
|
3
|
+
telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics. Thin
|
|
4
|
+
ergonomic functions over OTel spans (`gen_ai.*` semantic conventions), exported as OTLP
|
|
5
|
+
protobuf to the telemetry.dev ingest.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip install telemetry-dev
|
|
11
|
+
# or
|
|
12
|
+
uv add telemetry-dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Python >= 3.10.
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import telemetry_dev
|
|
21
|
+
from telemetry_dev import log, observe, propagate_attributes, start_span, update_current_span
|
|
22
|
+
|
|
23
|
+
telemetry_dev.init() # reads TELEMETRY_DEV_API_KEY from the environment
|
|
24
|
+
|
|
25
|
+
@observe # arguments -> input, return value -> output, errors captured + re-raised
|
|
26
|
+
def lookup_weather(city: str) -> dict:
|
|
27
|
+
return {"forecast": "sunny"}
|
|
28
|
+
|
|
29
|
+
with propagate_attributes(user_id="user_123", session_id="session_456"):
|
|
30
|
+
with start_span(
|
|
31
|
+
"chat gpt-4o",
|
|
32
|
+
type="generation",
|
|
33
|
+
model="gpt-4o",
|
|
34
|
+
provider="openai",
|
|
35
|
+
input=[{"role": "user", "content": "Plan a day trip"}],
|
|
36
|
+
):
|
|
37
|
+
log("calling the model")
|
|
38
|
+
update_current_span(
|
|
39
|
+
output=[{"role": "assistant", "content": "Here you go..."}],
|
|
40
|
+
usage={"input_tokens": 11, "output_tokens": 7},
|
|
41
|
+
finish_reason="stop",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
lookup_weather("Kyoto")
|
|
45
|
+
|
|
46
|
+
telemetry_dev.flush()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The SDK fails open: without an API key every call is a silent no-op, and internal errors are
|
|
50
|
+
routed to the `on_error` hook / `telemetry_dev` logger — never raised into your code.
|
|
51
|
+
|
|
52
|
+
## Environment variables
|
|
53
|
+
|
|
54
|
+
| Variable | Default | Purpose |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| `TELEMETRY_DEV_API_KEY` | — | Ingest key (`td_live_...`). Absent = SDK is a no-op. |
|
|
57
|
+
| `TELEMETRY_DEV_BASE_URL` | `https://ingest.telemetry.dev` | Ingest base URL (trailing slashes stripped). |
|
|
58
|
+
| `TELEMETRY_DEV_ENVIRONMENT` | `production` | Deployment environment label. |
|
|
59
|
+
| `OTEL_SERVICE_NAME` | `unknown_service` | Service name on every trace. |
|
|
60
|
+
|
|
61
|
+
Explicit `init()` arguments take precedence over environment variables.
|
|
62
|
+
|
|
63
|
+
## API reference
|
|
64
|
+
|
|
65
|
+
| Name | Description |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `init(**options) -> Client` | Initialize the SDK (see options below). Calling again replaces the previous client. |
|
|
68
|
+
| `@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. |
|
|
69
|
+
| `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()`. |
|
|
70
|
+
| `SpanHandle.update(**fields)` / `.end(**fields, end_time=)` / `.traceparent()` | Update attributes, end (accepts the full update field set), or read the W3C traceparent. |
|
|
71
|
+
| `update_current_span(**fields)` | Apply the update field set to the currently active span (no-op without one). |
|
|
72
|
+
| `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). |
|
|
73
|
+
| `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`). |
|
|
74
|
+
| `get_traceparent() -> str \| None` | W3C traceparent of the current context. |
|
|
75
|
+
| `flush(timeout_s=10.0)` / `shutdown(timeout_s=10.0)` | Force-flush / tear down traces + logs + metrics. Shutdown also runs atexit unless `disable_atexit=True`. |
|
|
76
|
+
| `TelemetrySpanProcessor` / `telemetry_dev.otel.create_telemetry_span_exporter` | Bring-your-own-OTel helpers (below). |
|
|
77
|
+
| `MaskContext`, `Usage`, `SpanHandle`, `Client`, `NOT_GIVEN` | Supporting types. |
|
|
78
|
+
|
|
79
|
+
### Span types
|
|
80
|
+
|
|
81
|
+
`type=` maps to `gen_ai.operation.name`:
|
|
82
|
+
|
|
83
|
+
| `type` | operation | input / output attributes |
|
|
84
|
+
| --- | --- | --- |
|
|
85
|
+
| `"span"` (default) | `function` | `gen_ai.input.messages` / `gen_ai.output.messages` |
|
|
86
|
+
| `"generation"` | `chat` | `gen_ai.input.messages` / `gen_ai.output.messages` |
|
|
87
|
+
| `"tool"` | `execute_tool` | `gen_ai.tool.call.arguments` / `gen_ai.tool.call.result` |
|
|
88
|
+
| `"agent"` | `invoke_agent` | `gen_ai.input.messages` / `gen_ai.output.messages` |
|
|
89
|
+
| `"embedding"` | `embeddings` | `gen_ai.input.messages` / `gen_ai.output.messages` |
|
|
90
|
+
|
|
91
|
+
### Span fields (start/update/end)
|
|
92
|
+
|
|
93
|
+
`input`, `output`, `model`, `provider`, `system_instructions`, `response_model`, `response_id`,
|
|
94
|
+
`output_type`, `finish_reason`, `usage` (dict with exactly `input_tokens`, `output_tokens`,
|
|
95
|
+
`total_tokens`, `cache_read_input_tokens`, `cache_creation_input_tokens`,
|
|
96
|
+
`reasoning_output_tokens`), `cost_usd`, `temperature`, `top_p`, `top_k`, `max_tokens`,
|
|
97
|
+
`stop_sequences`, `seed`, `frequency_penalty`, `presence_penalty`, `time_to_first_chunk_ms`,
|
|
98
|
+
`tool_name`, `tool_call_id`, `tool_description`, `agent_name`, `agent_id`, `metadata`
|
|
99
|
+
(→ `td.metadata.*`, this span only), `attributes` (raw escape hatch, merged last), `error`.
|
|
100
|
+
`start_span` additionally accepts `parent` (traceparent string, OTel `Context`, or
|
|
101
|
+
`SpanContext`), `start_time`, and per-call `capture_input` / `capture_output` overrides;
|
|
102
|
+
`end()` additionally accepts `end_time`.
|
|
103
|
+
|
|
104
|
+
### init() options
|
|
105
|
+
|
|
106
|
+
| Option | Default | Purpose |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| `api_key`, `base_url`, `environment`, `service_name` | env vars | Connection + resource settings. |
|
|
109
|
+
| `enabled` | `True` | `False` = hard kill switch (tests). |
|
|
110
|
+
| `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. |
|
|
111
|
+
| `export_mode` | `"batched"` | `"immediate"` exports synchronously per span/log (serverless). |
|
|
112
|
+
| `log_level` | `"warn"` | SDK diagnostics level: `debug`/`info`/`warn`/`error`/`silent`. |
|
|
113
|
+
| `capture_input`, `capture_output` | `True` | Global content-capture defaults. |
|
|
114
|
+
| `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. |
|
|
115
|
+
| `max_attribute_length` | `65536` | Per-content-attribute cap; truncated values get an ASCII `...[truncated]` marker appended. |
|
|
116
|
+
| `span_filter` | `None` | Export predicate `Callable[[ReadableSpan], bool]`. |
|
|
117
|
+
| `on_error` | `None` | Receives every internal SDK error; the SDK never raises. |
|
|
118
|
+
| `disable_atexit` | `False` | Skip the automatic atexit shutdown. |
|
|
119
|
+
| `timeout` | `10.0` | OTLP HTTP timeout in seconds. |
|
|
120
|
+
| `span_exporter`, `log_exporter`, `metric_reader` | `None` | Test seams / offline mode; any of them enables the client without an API key. |
|
|
121
|
+
|
|
122
|
+
## Auto-metrics
|
|
123
|
+
|
|
124
|
+
Ended spans automatically record two histograms (DELTA temporality, exported every 60s):
|
|
125
|
+
|
|
126
|
+
- `gen_ai.client.operation.duration` (unit `s`) for `chat`, `invoke_agent`, `embeddings`,
|
|
127
|
+
`execute_tool`
|
|
128
|
+
- `gen_ai.client.token.usage` (unit `{token}`, attribute `gen_ai.token.type=input|output`) for
|
|
129
|
+
`chat`, `invoke_agent`, `embeddings`
|
|
130
|
+
|
|
131
|
+
Plain spans (`function`) record no metrics. Quiet intervals produce zero metric requests.
|
|
132
|
+
|
|
133
|
+
## Serverless
|
|
134
|
+
|
|
135
|
+
Use `export_mode="immediate"` and/or call `telemetry_dev.flush()` before the runtime freezes:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
telemetry_dev.init(export_mode="immediate")
|
|
139
|
+
...
|
|
140
|
+
telemetry_dev.flush() # force-flush traces + logs + metrics
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Bring your own OTel (`telemetry_dev.otel`)
|
|
144
|
+
|
|
145
|
+
If you already run an OpenTelemetry SDK, attach the telemetry.dev processor to your provider
|
|
146
|
+
instead of calling `init()`:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from telemetry_dev.otel import TelemetrySpanProcessor
|
|
150
|
+
|
|
151
|
+
your_tracer_provider.add_span_processor(TelemetrySpanProcessor()) # reads TELEMETRY_DEV_API_KEY
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
If you need to wire your own span processor stack, create the telemetry.dev exporter directly:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
158
|
+
from telemetry_dev.otel import create_telemetry_span_exporter
|
|
159
|
+
|
|
160
|
+
span_exporter = create_telemetry_span_exporter()
|
|
161
|
+
your_tracer_provider.add_span_processor(BatchSpanProcessor(span_exporter))
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`TelemetrySpanProcessor` remains available from the package root for compatibility:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from telemetry_dev import TelemetrySpanProcessor
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
| Option | Default | Purpose |
|
|
171
|
+
| --- | --- | --- |
|
|
172
|
+
| `api_key` | `TELEMETRY_DEV_API_KEY` | Ingest key. Absent with no `span_exporter` = inert no-op. |
|
|
173
|
+
| `base_url` | `TELEMETRY_DEV_BASE_URL` or `https://ingest.telemetry.dev` | Ingest base URL; trailing slashes are stripped. |
|
|
174
|
+
| `export_mode` | `"batched"` | `"batched"` or `"immediate"` span export. |
|
|
175
|
+
| `max_export_batch_size` | `64` | BatchSpanProcessor export batch size. |
|
|
176
|
+
| `schedule_delay_millis` | `1000` | BatchSpanProcessor schedule delay. |
|
|
177
|
+
| `max_queue_size` | `2048` | BatchSpanProcessor queue size. |
|
|
178
|
+
| `export_timeout_millis` | `30000` | BatchSpanProcessor export timeout. |
|
|
179
|
+
| `span_filter` | `None` | Export predicate `Callable[[ReadableSpan], bool]`; failures export the span. |
|
|
180
|
+
| `metrics` | `True` | Auto-record GenAI duration/token histograms for exported spans when an API key is available. |
|
|
181
|
+
| `service_name` | `OTEL_SERVICE_NAME` or `unknown_service` | `service.name` on the metrics resource. |
|
|
182
|
+
| `environment` | `TELEMETRY_DEV_ENVIRONMENT` or `production` | `deployment.environment.name` on the metrics resource. |
|
|
183
|
+
| `on_error` | `None` | Receives internal processor errors; errors are never raised into your code. |
|
|
184
|
+
| `span_exporter` | `None` | Advanced/test seam replacing the telemetry.dev OTLP trace exporter. |
|
|
185
|
+
|
|
186
|
+
Without an API key and without `span_exporter`, `TelemetrySpanProcessor()` is an inert no-op:
|
|
187
|
+
safe to attach unconditionally, with only a debug log. Auto-metrics are on by default for exported
|
|
188
|
+
GenAI spans, use the `service_name` / `environment` resource settings above, and are skipped
|
|
189
|
+
without an API key.
|
|
190
|
+
|
|
191
|
+
## Limitations
|
|
192
|
+
|
|
193
|
+
- `register_global=True` cannot be undone on `shutdown()`: OpenTelemetry Python has no public
|
|
194
|
+
API to unregister a global `TracerProvider`, so a later `init(register_global=True)` in the
|
|
195
|
+
same process cannot reclaim the global slot. Prefer the isolated default (or the BYO
|
|
196
|
+
processor) for processes that re-initialize.
|
|
197
|
+
|
|
198
|
+
## Development
|
|
199
|
+
|
|
200
|
+
Uses [uv](https://docs.astral.sh/uv/):
|
|
201
|
+
|
|
202
|
+
```sh
|
|
203
|
+
uv sync # install (writes uv.lock)
|
|
204
|
+
uv run pytest # tests
|
|
205
|
+
uv run ruff format . # format
|
|
206
|
+
uv run ruff check . # lint
|
|
207
|
+
uv run pyright # type-check
|
|
208
|
+
uv build # build wheel + sdist
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
`examples/quickstart.py` is runnable against a real ingest:
|
|
212
|
+
`TELEMETRY_DEV_API_KEY=td_live_... uv run examples/quickstart.py`.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "telemetry-dev"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
authors = [{ name = "telemetry.dev" }]
|
|
9
|
+
keywords = ["telemetry", "opentelemetry", "llm", "genai", "tracing", "observability"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.10",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3.13",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"opentelemetry-api>=1.35.0,<2",
|
|
22
|
+
"opentelemetry-sdk>=1.35.0,<2",
|
|
23
|
+
"opentelemetry-exporter-otlp-proto-http>=1.35.0,<2",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://telemetry.dev"
|
|
28
|
+
Repository = "https://github.com/telemetry-dev/telemetry.dev"
|
|
29
|
+
|
|
30
|
+
[dependency-groups]
|
|
31
|
+
dev = [
|
|
32
|
+
"pytest>=8.3",
|
|
33
|
+
"pytest-asyncio>=0.25",
|
|
34
|
+
"ruff>=0.9",
|
|
35
|
+
"pyright>=1.1.390",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["uv_build>=0.9.0,<0.10.0"]
|
|
40
|
+
build-backend = "uv_build"
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
asyncio_mode = "auto"
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
line-length = 100
|
|
48
|
+
target-version = "py310"
|
|
49
|
+
|
|
50
|
+
[tool.ruff.lint]
|
|
51
|
+
select = ["E", "F", "I", "UP", "B", "RUF"]
|
|
52
|
+
|
|
53
|
+
[tool.pyright]
|
|
54
|
+
include = ["src", "tests", "examples"]
|
|
55
|
+
typeCheckingMode = "strict"
|
|
56
|
+
pythonVersion = "3.10"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics.
|
|
2
|
+
|
|
3
|
+
Quickstart::
|
|
4
|
+
|
|
5
|
+
import telemetry_dev
|
|
6
|
+
|
|
7
|
+
telemetry_dev.init() # reads TELEMETRY_DEV_API_KEY
|
|
8
|
+
|
|
9
|
+
with telemetry_dev.start_span("chat gpt-4o", type="generation", model="gpt-4o",
|
|
10
|
+
provider="openai", input=messages) as span:
|
|
11
|
+
...
|
|
12
|
+
span.update(output=completion, usage={"input_tokens": 11, "output_tokens": 7})
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from ._client import Client, flush, get_client, init, shutdown
|
|
16
|
+
from ._config import SDK_VERSION as __version__
|
|
17
|
+
from ._config import LogLevelOption
|
|
18
|
+
from ._context import get_traceparent, propagate_attributes
|
|
19
|
+
from ._logs import log
|
|
20
|
+
from ._observe import observe
|
|
21
|
+
from ._semconv import LogLevel, SpanType
|
|
22
|
+
from ._serialize import AttributeValue, Mask, MaskContext
|
|
23
|
+
from ._spans import (
|
|
24
|
+
NOT_GIVEN,
|
|
25
|
+
SpanHandle,
|
|
26
|
+
Usage,
|
|
27
|
+
start_span,
|
|
28
|
+
update_current_span,
|
|
29
|
+
)
|
|
30
|
+
from .otel import TelemetrySpanProcessor
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"NOT_GIVEN",
|
|
34
|
+
"AttributeValue",
|
|
35
|
+
"Client",
|
|
36
|
+
"LogLevel",
|
|
37
|
+
"LogLevelOption",
|
|
38
|
+
"Mask",
|
|
39
|
+
"MaskContext",
|
|
40
|
+
"SpanHandle",
|
|
41
|
+
"SpanType",
|
|
42
|
+
"TelemetrySpanProcessor",
|
|
43
|
+
"Usage",
|
|
44
|
+
"__version__",
|
|
45
|
+
"flush",
|
|
46
|
+
"get_client",
|
|
47
|
+
"get_traceparent",
|
|
48
|
+
"init",
|
|
49
|
+
"log",
|
|
50
|
+
"observe",
|
|
51
|
+
"propagate_attributes",
|
|
52
|
+
"shutdown",
|
|
53
|
+
"start_span",
|
|
54
|
+
"update_current_span",
|
|
55
|
+
]
|