JuliusBrussee/caveman · error
otlp decode
Error message
otlp decode: %w
What it means
parseOTLP wraps any json.Unmarshal failure of an OTLP/JSON trace payload with the "otlp decode: " prefix and returns no rows. The library throws it because the payload bytes are not valid JSON or do not match the expected otlpPayload structure (resourceSpans/scopeSpans/spans hierarchy). It guarantees callers get an error instead of silently decoding a partial or empty result.
Solutions
- Validate the payload is complete JSON before calling Parse (e.g. json.Valid(data)).
- Confirm the exporter emits OTLP/JSON, not OTLP/protobuf; convert or request JSON format.
- Log a snippet of the failing bytes around the JSON error offset to spot truncation or encoding issues.
- Ensure the source encodes fields with types matching the OTLP/JSON spec (hex string IDs, string attributes).
Example fix
// before
rows, err := Parse(body) // "otlp decode: unexpected end of JSON input"
// after
if !json.Valid(body) {
return fmt.Errorf("payload is not valid JSON (len=%d)", len(body))
}
rows, err := Parse(body) Defensive patterns
Strategy: validation
Validate before calling
if len(data) == 0 || !json.Valid(data) {
return fmt.Errorf("otlp payload is not valid JSON (len=%d)", len(data))
} Try / catch
rows, err := Parse(data)
if err != nil {
if strings.HasPrefix(err.Error(), "otlp decode:") {
return fmt.Errorf("invalid OTLP/JSON payload: %w", err)
}
return err
} Prevention
- Run json.Valid on payloads before parsing.
- Ensure exporters emit OTLP/JSON, not protobuf.
- Checksum or size-check trace files before ingestion.
- Pin OTLP exporter/library versions to avoid schema drift.
When it happens
Trigger: Calling Parse/parseOTLP with bytes that are not valid JSON, truncated payloads, wrong content type (e.g. protobuf OTLP binary passed where OTLP/JSON is expected), or JSON with incompatible field types (e.g. traceId as number instead of string).
Common situations: Fetching traces from a collector endpoint that emits protobuf instead of JSON; reading a truncated or corrupted export file; ingesting output of a tool that logs non-JSON lines; schema drift after an OTLP version change (renamed or restructured fields).
Related errors
- kms: decode envelope
- cavemanExporterConfig: protocol
- envelope: encode scope
- envelope: marshal metadata
- envelope: parse metadata
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/cd5b916502fc1a70.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/importers/otlp.go:89
}
type otlpEvent struct {
Name string `json:"name"`
TimeUnixNano string `json:"timeUnixNano"`
Attributes []otlpKV `json:"attributes"`
}
type otlpStatus struct {
Code int `json:"code"`
Message string `json:"message"`
}
// parseOTLP decodes an OTLP/JSON trace payload and maps GenAI semantic
// conventions to Span columns. On a JSON error it returns no rows.
func parseOTLP(data []byte, opts Options) ([]Span, error) {
var p otlpPayload
if err := json.Unmarshal(data, &p); err != nil {
return nil, fmt.Errorf("otlp decode: %w", err)
}
var rows []Span
for _, rs := range p.ResourceSpans {
resAttrs := otlpKVMap(rs.Resource.Attributes)
for _, ss := range rs.ScopeSpans {
for _, sp := range ss.Spans {
rows = append(rows, mapOTLPSpan(sp, resAttrs, opts))
}
}
}
return rows, nil
}
func mapOTLPSpan(sp otlpSpan, resAttrs map[string]string, opts Options) Span {
attrs := otlpKVMap(sp.Attributes)
genAI := telemetry.ExtractGenAIFields(attrs, resAttrs, sp.Name)
View on GitHub (pinned to 3ee70a1026)