Skip to content

otel: preserve OpenTelemetry trace data during decoding - #4156

Open
crazy-max wants to merge 1 commit into
docker:masterfrom
crazy-max:fix-otelutil-decoding
Open

crazy-max wants to merge 1 commit into
docker:masterfrom
crazy-max:fix-otelutil-decoding

Conversation

@crazy-max

@crazy-max crazy-max commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Fix trace decoding failures and data loss affecting build history and desktop bundle exports. The existing decoder stores attribute values in any, but encoding/json decodes arrays as []any and numbers as float64. The attribute conversion code at

func (kv *keyValue) asAttributeKeyValue() (attribute.KeyValue, error) {
// value types get encoded as string
switch kv.Value.Type {
case attribute.EMPTY.String():
return attribute.KeyValue{}, errors.New("invalid value type")
case attribute.BOOL.String():
return attribute.Bool(kv.Key, kv.Value.Value.(bool)), nil
case attribute.INT64.String():
// value could be int64 or float64, so handle both cases (float64 comes
// from json unmarshal)
var v int64
switch i := kv.Value.Value.(type) {
case int64:
v = i
case float64:
v = int64(i)
}
return attribute.Int64(kv.Key, v), nil
case attribute.FLOAT64.String():
return attribute.Float64(kv.Key, kv.Value.Value.(float64)), nil
case attribute.STRING.String():
return attribute.String(kv.Key, kv.Value.Value.(string)), nil
case attribute.BOOLSLICE.String():
return attribute.BoolSlice(kv.Key, kv.Value.Value.([]bool)), nil
case attribute.INT64SLICE.String():
// handle both float64 and int64 (float64 comes from json unmarshal)
var v []int64
switch sli := kv.Value.Value.(type) {
case []int64:
v = sli
case []float64:
for i := range sli {
v = append(v, int64(sli[i]))
}
}
return attribute.Int64Slice(kv.Key, v), nil
case attribute.FLOAT64SLICE.String():
return attribute.Float64Slice(kv.Key, kv.Value.Value.([]float64)), nil
case attribute.STRINGSLICE.String():
var strSli []string
// sometimes we can get an []interface{} instead of a []string, so
// always cast to []string if that happens.
switch sli := kv.Value.Value.(type) {
case []string:
strSli = sli
case []any:
for i := range sli {
var v string
// best case we have a string, otherwise, cast it using
// fmt.Sprintf
if str, ok := sli[i].(string); ok {
v = str
} else {
v = fmt.Sprintf("%v", sli[i])
}
// add the string to the slice
strSli = append(strSli, v)
}
default:
return attribute.KeyValue{}, errors.Errorf("got unsupported type %q for %s", reflect.ValueOf(kv.Value.Value).Kind(), attribute.STRINGSLICE.String())
}
return attribute.StringSlice(kv.Key, strSli), nil
default:
return attribute.KeyValue{}, errors.Errorf("unknown value type %s", kv.Value.Type)
}
}
instead asserts typed slices or handles only typed numeric slices. Reproductions confirmed that boolean and float slices panic, integer slices become empty, and the integer 9007199254740993 becomes 9007199254740992. Decode values according to their declared OTel types so slices survive and integers retain their precision, with errors for malformed values instead of panics or implicit coercion.

Support the full attribute model exposed by the pinned OTel 1.46.0 dependency, including EMPTY, BYTESLICE, SLICE, and MAP. Previously, these valid types caused the entire trace to fail decoding. Preserve nested values and instrumentation-scope attributes, prefer InstrumentationScope when present, and retain compatibility with InstrumentationLibrary. Jaeger conversion now represents bytes as binary tags and complex values as JSON strings instead of silently dropping them.

Preserve trace flags and tracestate during decoding and bundle export. The previous decoder recognized only the exact flags string "01", so "03" lost both the sampled and random-ID bits. OTel defines the random-ID flag as 0x02, while Jaeger uses that bit for debug. Preserve the OTel flags internally and transfer only the shared sampled bit to Jaeger.

Keep older bundles readable while fixing scope-attribute preservation in new exports. The bundle sanitizer encoded spans by value, bypassing attribute.Set pointer-receiver JSON marshaler and writing scope attributes as {}. Encode spans by pointer to preserve their attributes, and continue accepting the legacy object representation.

I used Codex to investigate the issues, then cross-checked the changes with Claude Code. That review caught a compatibility issue with scope attributes in older bundles, which was reproduced and fixed before a follow-up review.

Decode attributes using their declared types to avoid slice panics and
integer precision loss. Support complex values and scope attributes from
the pinned OpenTelemetry SDK, and retain trace flags and tracestate.

Preserve these values in Jaeger conversion and bundle exports while
accepting legacy bundles with empty scope attribute objects. Map only the
sampled flag to Jaeger so OTel's random-ID flag is not treated as debug.

Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
@crazy-max
crazy-max marked this pull request as ready for review October 14, 2026 14:51
@crazy-max crazy-max added this to the v0.39.0 milestone Oct 14, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant