Skip to content

fix(otlp): preserve unknown span fields in traces when no policy matches - #298

Open
detail-app[bot] wants to merge 1 commit into
masterfrom
detail/bug-fix/fix-otlp-preserve-unknown-span-fields-in-traces-wh-5b87ae
Open

detail-app[bot] wants to merge 1 commit into
masterfrom
detail/bug-fix/fix-otlp-preserve-unknown-span-fields-in-traces-wh-5b87ae

Conversation

@detail-app

@detail-app detail-app Bot commented Sep 16, 2026

Copy link
Copy Markdown

Detail bug report: View on Detail

Bug

When trace policies are loaded, the OTLP traces pipeline (processJsonTraces and processProtobufTraces in src/signals/otlp/traces.zig) re-encoded the decoded TracesData unconditionally after filterSpansInPlace. Because decoding drops schema-unknown span-level fields — JSON via ignore_unknown_fields = true, protobuf because the generated Span struct has no _unknown_fields — every re-encode silently stripped any span-level field absent from the compiled proto whenever no span matched a policy (the common no-match regime). This was a parity gap with the OTLP logs path (processJsonLogs/processProtobufLogs in src/signals/otlp/logs.zig), which already guarded against this. Introduced when the traces functions were first created in #89.

This is forward-compatibility-only: no current OTLP v1.1 span-level field is stripped (the pinned policy_zig 0.7.3 Span includes all of them). The defect affects vendor-extension top-level/nested span fields and any future OTLP span-level additions until policy_zig is rebuilt.

Fixes ENG-699

Fix

Added the same "nothing modified → return original bytes" fast path to both traces functions, mirroring the OTLP logs-path siblings. When filterSpansInPlace reports dropped_count == 0 and !was_transformed, the original input bytes are copied into a fresh allocation and returned without re-encoding, preserving unknown fields byte-for-byte.

This closes the no-match / no-transform regime (the demonstrated defect and the logs-parity gap). The match+transform regime (e.g. sampling-threshold tracestate writeback setting was_transformed = true) still re-encodes — the same acknowledged limitation as the logs path; fully resolving it would require enabling _unknown_fields in the generated proto structs, which is out of scope here.

Testing

  • Unit tests (zig build test --summary all, Debug and ReleaseSafe): 522 pass, 1 pre-existing skip, 0 failures. Added 3 regression tests covering both paths and the negative case:
    • JSON traces with an unknown span field preserve it byte-for-byte on the no-match path.
    • Protobuf traces with an unknown wire field (field 999) preserve the tag bytes on the no-match path.
    • A matching drop policy still drops spans (the guard does not over-fire when dropped_count > 0).
  • All existing traces (26) and logs (28) inline tests remain green, confirming no regression and that the traces guard mirrors the logs guard.
  • task lint (zig fmt --check + ziglint) clean; zig build and zig build -Doptimize=ReleaseSafe succeed.
  • End-to-end conformance (bench/scaling/test-equivalence.sh, POLICY_COUNT=0): Edge and otelcol forward an equal number of logs/traces/metrics records with no semantic divergence.
  • Live spot check against a running Edge with a loaded non-matching trace policy: forwarding a JSON span with futureOtelField and a protobuf span with unknown field 999 produced forwarded bodies byte-equal to the input, with the unknown fields intact on both /v1/traces paths.

One task signoff step could not be verified: gh signoff -f requires the basecamp/gh-signoff GitHub CLI extension, which failed to install with HTTP 401 (invalid GitHub token in this environment). All other signoff components (task do, task build:safe, task test:s3-e2e against MinIO) passed.


Automatic Fixes PRs can be configured here.

@macroscopeapp

macroscopeapp Bot commented Sep 16, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved at 8cf4cff

Macroscope's review found this PR approvable — This is a narrow OTLP trace bug fix that preserves unknown JSON and protobuf span fields only when no filtering or transformation occurs, while retaining re-encoding for modified payloads. Regression tests cover both formats and the drop-policy path, with no schema or infrastructure changes.

You can add or adjust custom eligibility rules. Learn more.

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant