The OTLP span exporter hard-codes an empty parent_span_id:
|
.parent_span_id = (""), // TODO: get from parent context |
The root cause is one layer up: api.trace.Span has no field for the parent's span id, so the exporter has nothing to read. Tracer.startSpan takes the parent's SpanContext from options.parent_context, keeps its trace_id, and discards its span_id:
|
// Determine trace ID based on parent |
|
if (parent_span_context) |parent_sc| { |
|
trace_id = parent_sc.trace_id; |
|
span_id = self.provider.id_generator.newSpanID(trace_id); |
|
} else { |
Effect: spans created with parent_context share the parent's trace_id (verified at 8b99dba with zig 0.16.0: one parent, four children, stdout exporter), but every span arrives as a root, so backends show a flat trace. The stdout exporter's SerializableSpan has no parent field either; no configuration exports the parent today. I did not run an OTLP round trip; that half of the claim is from the exporter source.
The // TODO: get from parent context comment dates back to the first exporter implementation (zig-o11y/opentelemetry-sdk fa00ee7, 2025-09-09); I found no issue tracking it here or in zig-o11y.
Proposed fix:
- add
parent_span_id: ?SpanID = null to api.trace.Span
- set it in
Tracer.startSpan where the parent's trace_id is already copied
- emit it in
spanToOTLP (SpanID.toBinary() is already used for the span's own id) and add the field to SerializableSpan
One spot that is easy to miss: BatchingProcessor.cloneSpan copies fields one by one (span_processor.zig L375), so it needs the new field too, ideally with a test asserting the parent survives cloning.
Related: #54 fixes how ids are encoded in OTLP/JSON; this issue is about the field never being populated. Both are needed for a correct parentSpanId on the wire.
The OTLP span exporter hard-codes an empty
parent_span_id:opentelemetry-zig/opentelemetry-sdk/src/sdk/trace/exporters/otlp.zig
Line 268 in 8b99dba
The root cause is one layer up:
api.trace.Spanhas no field for the parent's span id, so the exporter has nothing to read.Tracer.startSpantakes the parent'sSpanContextfromoptions.parent_context, keeps itstrace_id, and discards itsspan_id:opentelemetry-zig/opentelemetry-sdk/src/sdk/trace/provider.zig
Lines 294 to 298 in 8b99dba
Effect: spans created with
parent_contextshare the parent'strace_id(verified at 8b99dba with zig 0.16.0: one parent, four children, stdout exporter), but every span arrives as a root, so backends show a flat trace. The stdout exporter'sSerializableSpanhas no parent field either; no configuration exports the parent today. I did not run an OTLP round trip; that half of the claim is from the exporter source.The
// TODO: get from parent contextcomment dates back to the first exporter implementation (zig-o11y/opentelemetry-sdk fa00ee7, 2025-09-09); I found no issue tracking it here or in zig-o11y.Proposed fix:
parent_span_id: ?SpanID = nulltoapi.trace.SpanTracer.startSpanwhere the parent'strace_idis already copiedspanToOTLP(SpanID.toBinary()is already used for the span's own id) and add the field toSerializableSpanOne spot that is easy to miss:
BatchingProcessor.cloneSpancopies fields one by one (span_processor.zig L375), so it needs the new field too, ideally with a test asserting the parent survives cloning.Related: #54 fixes how ids are encoded in OTLP/JSON; this issue is about the field never being populated. Both are needed for a correct
parentSpanIdon the wire.