JuliusBrussee/caveman · error

otlp decode: %w

Error message

otlp decode: %w

What it means

parseOTLP unmarshals the payload into a strictly typed otlpPayload (OTLP/JSON trace export shape: resourceSpans -> scopeSpans -> spans). Any JSON error is wrapped as 'otlp decode: %w'. Note the doc comment: on a JSON error it returns no rows — partial data is never emitted. Type mismatches on nested OTLP fields (e.g. traceId expected as hex string but given object) surface here too.

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 27d5a3981a)

Solutions

  1. Confirm the payload is OTLP/JSON traces (resourceSpans at the root), not protobuf or another signal type
  2. Read the wrapped json error for the offending offset/field and validate the document against the OTLP JSON schema
  3. If exporting from a collector, set exporters to JSON encoding explicitly
  4. Re-fetch or re-export the batch if truncation is suspected

Example fix

// before
rows, sum, err := importers.Import(importers.FormatOTLP, protoBytes, opts) // binary OTLP

// after
jsonBytes, _ := protoToJSONOtlp(protoBytes) // encode via collector or protojson
rows, sum, err := importers.Import(importers.FormatOTLP, jsonBytes, opts)
Defensive patterns

Strategy: validation

Validate before calling

func looksLikeOTLPJSON(data []byte) bool {
	d := bytes.TrimSpace(data)
	return len(d) > 0 && d[0] == '{' && bytes.Contains(d, []byte("resourceSpans"))
}

Try / catch

if _, _, err := importers.Import(importers.FormatOTLP, raw, opts); err != nil { if strings.Contains(err.Error(), "otlp decode") { return fmt.Errorf("payload is not OTLP/JSON traces: %w", err) } }

Prevention

When it happens

Trigger: Feeding OTLP/proto (binary) bytes to a JSON parser; a truncated export; passing an OTLP metrics or logs export instead of traces; an exporter emitting non-standard JSON where a span field has the wrong type; feeding the generic {'data':...} shape to the otlp format.

Common situations: Confusing content-type between protobuf and JSON OTLP encodings on the collector; version skew with pre-release OTLP JSON schemas; proxy or LB truncating large trace batches; testing with a hand-written JSON file missing required nesting.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/cd5b916502fc1a70. Report an issue: GitHub.