Skip to content

Commit 584230f

Browse files
feat(traces): OTLP span encoding and client-side validity (#950)
* feat(traces): OTLP span encoding and client-side validity Adds the OTLP JSON encoder for spans: AnyValue encoding per the traces spec (int64 as strings, out-of-range ints and non-finite floats as strings, None values and empty keys dropped, unpaired surrogates replaced with U+FFFD, a bounded walk that terminates on cycles), the span record builder with the W3C flags byte and OTel remoteness bits, the one-resource/one-scope envelope with service.name always present, and the name and timestamp sanitizers. One bad value otherwise gets the whole batch rejected. Not reachable from the client. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TkZAsCciW4PV8ZdcCHmAbA * fix(traces): reject a float time whose nanosecond value overflows A finite float like 1e308 becomes infinite once scaled to nanoseconds, so round() raised instead of the value falling back to the derived time. * test(traces): move the golden OTLP payload to a snapshot file Keeps the ingestion-shape fixture in posthog/test/snapshots alongside the other server payload snapshots instead of a 100-line inline dict. * refactor(traces): name the OTLP sampled bit apart from the header flags TRACE_FLAGS_SAMPLED was a hex string in _traceparent and an int here. The module docstring now says which inputs the encoder trusts. * fix(traces): count attributes the encoder budget cuts, and encode the resource once When the node budget ran out, every remaining attribute vanished with a debug log and no droppedAttributesCount, so an oversized first value silently erased later keys. The cut is now counted on the span and on each event. The resource is encoded once by the caller instead of on every batch. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 0affb06 commit 584230f

5 files changed

Lines changed: 1272 additions & 0 deletions

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
{
2+
"resourceSpans": [
3+
{
4+
"resource": {
5+
"attributes": [
6+
{
7+
"key": "service.name",
8+
"value": {
9+
"stringValue": "checkout-api"
10+
}
11+
},
12+
{
13+
"key": "telemetry.sdk.name",
14+
"value": {
15+
"stringValue": "posthog-python"
16+
}
17+
}
18+
]
19+
},
20+
"scopeSpans": [
21+
{
22+
"scope": {
23+
"name": "posthog-python",
24+
"version": "<SDK_VERSION>"
25+
},
26+
"spans": [
27+
{
28+
"attributes": [
29+
{
30+
"key": "posthogDistinctId",
31+
"value": {
32+
"stringValue": "user-123"
33+
}
34+
},
35+
{
36+
"key": "sessionId",
37+
"value": {
38+
"stringValue": "session-123"
39+
}
40+
},
41+
{
42+
"key": "http.status_code",
43+
"value": {
44+
"intValue": "500"
45+
}
46+
},
47+
{
48+
"key": "http.duration_ratio",
49+
"value": {
50+
"doubleValue": 0.25
51+
}
52+
},
53+
{
54+
"key": "cached",
55+
"value": {
56+
"boolValue": false
57+
}
58+
}
59+
],
60+
"endTimeUnixNano": "1700000000080000000",
61+
"events": [
62+
{
63+
"attributes": [
64+
{
65+
"key": "exception.type",
66+
"value": {
67+
"stringValue": "TypeError"
68+
}
69+
},
70+
{
71+
"key": "exception.message",
72+
"value": {
73+
"stringValue": "boom"
74+
}
75+
}
76+
],
77+
"name": "exception",
78+
"timeUnixNano": "1700000000040000000"
79+
}
80+
],
81+
"flags": 257,
82+
"kind": 2,
83+
"name": "GET /users/:id",
84+
"parentSpanId": "b7ad6b7169203331",
85+
"spanId": "00f067aa0ba902b7",
86+
"startTimeUnixNano": "1700000000000000000",
87+
"status": {
88+
"code": 2,
89+
"message": "boom"
90+
},
91+
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736"
92+
}
93+
]
94+
}
95+
]
96+
}
97+
]
98+
}

0 commit comments

Comments
 (0)