Skip to content

fix: let per-event log fields override global-context keys instead of duplicating them - #81

Merged
OmarAlJarrah merged 1 commit into
mainfrom
fix/structured-log-duplicate-keys
Jun 16, 2026
Merged

OmarAlJarrah merged 1 commit into
mainfrom
fix/structured-log-duplicate-keys

Conversation

@OmarAlJarrah

Copy link
Copy Markdown
Member

Summary

LoggingEvent.log() builds a single SLF4J event from three key/value sources: the logger's global context, the MDC, and the per-event field(...) map. SLF4J's addKeyValue appends a key/value pair rather than replacing an existing one, and only the MDC source was being de-duplicated against the others.

As a result, a key present in both the global context and a per-event field was emitted twice — two KeyValuePair entries with the same name on one event, which serializes to invalid duplicate-key JSON in JSON appenders. The long-standing comment that "per-event fields can override on the SLF4J side" was therefore not actually true for the global-context/field overlap.

Change

The global-context loop now skips any key that is also present in the per-event fields, mirroring the existing MDC guard. The per-event value is then emitted once, by the fields loop — so per-event fields win, and each key appears exactly once. The MDC guard (which already skips keys present in either the fields or the global context) is unchanged.

Tests

Adds a regression test: a key set in both the global context and a per-event field is emitted exactly once, with the per-event value winning. Modeled on the existing global-context/MDC dedup test.

Internal change only — no public API change.

Closes #12

LoggingEvent.log() emitted key/value pairs from three sources into one
SLF4J builder: the logger's global context, folded MDC entries, and the
per-event fields set via field(). The MDC fold already skipped keys that
were also present in the global context or the per-event fields, but the
global-context loop had no such guard.

SLF4J's addKeyValue appends rather than replaces, so a key supplied by
both the global context and a per-event field() was emitted twice,
producing two KeyValuePair entries with the same name. JSON appenders
render that as invalid duplicate-key JSON, and the documented contract
that per-event fields override the global context was not actually honored.

Skip a global-context key when the same key is set as a per-event field,
so the per-event value is emitted exactly once and wins, mirroring the
existing MDC dedup guard. Add a regression test asserting that a key
present in both the global context and a field() yields a single entry
carrying the per-event value.
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.

Structured log emits a duplicate key when it appears in both the global context and a per-event field

1 participant