Skip to content

fix(serviceevents): preserve map key order in mapToValue OTLP body - #1421

Merged
vastin merged 5 commits into
mainfrom
fix/serviceevents-maptovalue-order
Jul 15, 2026
Merged

vastin merged 5 commits into
mainfrom
fix/serviceevents-maptovalue-order

Conversation

@vastin

@vastin vastin commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Issue #, if available: N/A

Description of changes:

ServiceEventsOtlpEmitter.mapToValue reversed the key order of every map it
converted to an OTLP Value. The last child pushed on the result stack was
popped into the map first, so a source map ordered
exception_type -> exception_message -> stack_trace -> call_path was emitted as
call_path -> stack_trace -> exception_message -> exception_type.

This makes the incident-snapshot body.exception_info serialize in reverse of
the schema order shared with the Python and JS SDKs. CloudWatch delivers the log
body verbatim, so the reversed order reaches consumers and breaks the ordered
e2e assertion on exception_info.

Fix: pop the children into a temp array in reverse index order, then insert
forward — preserving the source map's insertion order. This mirrors the existing
ListMarker branch. Arrays were already order-correct; only maps were affected.

Testing:

  • Added testEmitIncidentSnapshotPreservesExceptionInfoFieldOrder — a
    regression guard that fails on the old reversing code.
  • Full serviceevents emitter unit suite passes (9/9).
  • Live dev-loop against CloudWatch Application Signals: the incident-snapshot
    e2e cell flips from FAIL to PASS with only this change (5/5 cells pass).

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

vastin added 4 commits July 14, 2026 16:43
mapToValue assembled each OTLP KeyValueList by iterating keys in reverse
(for i = keys.length-1 .. 0; map.put(keys[i], resultStack.pop())), so every
emitted map came out with its fields reversed. For incident snapshots this
made body.exception_info[0] serialize as
  call_path -> stack_trace -> exception_message -> exception_type
instead of the builder's
  exception_type -> exception_message -> stack_trace -> call_path,
and reversed the nested call_path entry fields too.

This diverges from the Python/JS serviceevents schema (forward order) and
from mapToValue's own ListMarker branch, which already preserves order. It
also breaks the App Signals incident-snapshot e2e: the exception cell asserts
an ordered regex ("exception_type":... then "function_name":...), which can
never match when exception_type is emitted last. (The latency cell anchors on
call_path, which happened to land first under reversal, so it passed --
masking the bug.)

Fix: pop children into a temp array in reverse index order, then insert
forward, mirroring ListMarker. Add a unit test pinning forward order for both
exception_info and its nested call_path entry.

Verified against live CloudWatch via the EC2 dev-loop: field order flips
forward and the incident-snapshot validator goes from FAIL to PASS (5/5).
@vastin
vastin marked this pull request as ready for review July 15, 2026 21:18
@vastin
vastin requested a review from a team as a code owner July 15, 2026 21:18
@github-actions

github-actions Bot commented Jul 15, 2026 •

Copy link
Copy Markdown

Claude finished @vastin's task in 1m 26s —— View job


Claude Code Review

  • Read prior review comments to avoid duplicates (none found)
  • Review the PR diff
  • Analyze code changes for quality, correctness, tests
  • Post findings

Summary

The fix is small, correct, and well-scoped. No blocking issues.

Correctness of the fix (ServiceEventsOtlpEmitter.java:473-489):
The producer side (line 524) pushes values in reverse index order, so the last-pushed child sits on top of resultStack. Popping into vals[m.keys.length - 1] first restores the intended index alignment, and forward-inserting into a LinkedHashMap preserves source insertion order. This mirrors the already-correct ListMarker branch — good consistency.

Test coverage (ServiceEventsOtlpEmitterTest.java:305-405):
The regression guard exercises both:

  • Top-level exception_info map order (exception_type → exception_message → stack_trace → call_path)
  • Nested call_path entry order (function_name → caller_function_name → duration_ns → error → is_async)

Nested-order verification is the important piece — it catches any future regression where the recursion/iteration is fixed at the top level but broken for children.

Minor observations (non-blocking):

  • The two-loop pop-then-insert pattern could be written as a single addFirst-style loop, but the current form matches the ListMarker branch exactly, which is more valuable for readability than terseness. Leave as-is.
  • The block comment at lines 476–479 is a good example of a WHY comment — the stack-order invariant is non-obvious and easy to break in future edits.
  • Consider a small unit test targeting mapToValue directly (rather than only via emitIncidentSnapshot) to isolate ordering behavior from the emitter's other logic. Not required — the current e2e-shaped test is sufficient and mirrors real use.

Nothing else to flag. LGTM.
· Branch

@github-actions

github-actions Bot commented Jul 15, 2026 •

Copy link
Copy Markdown

Claude finished @vastin's task in 57s —— View job


Claude Code Review

  • Read prior review comments to avoid duplicates
  • Review the PR diff
  • Analyze code changes for quality, correctness, tests
  • Post findings

Summary

No new findings on this synchronize. The substantive diff hasn't changed since the prior review at 21:18Z — the new commit is a merge from main (1c090ea). The fix at ServiceEventsOtlpEmitter.java:473-489 and the regression test at ServiceEventsOtlpEmitterTest.java:305-405 remain correct and well-scoped:

  • Pop-into-temp-array-then-forward-insert preserves LinkedHashMap insertion order and mirrors the already-correct ListMarker branch.
  • The regression test guards both the top-level exception_info map order and the nested call_path entry order.
  • The block comment at lines 476–479 documents the non-obvious stack-order invariant.

PR is already approved by @mxiamxia. Nothing further to flag.
· Branch

@vastin
vastin enabled auto-merge (squash) July 15, 2026 22:00
@vastin
vastin merged commit dd3d9b2 into main Jul 15, 2026
26 of 29 checks passed
@vastin
vastin deleted the fix/serviceevents-maptovalue-order branch July 15, 2026 23:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants