Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,16 @@ public class LoggingEvent internal constructor(

val builder = logger.slf4j.atLevel(logger.slf4jLevel(level))

// Global context first so per-event fields can override on the SLF4J side.
// Global context first so per-event fields can override on the SLF4J side. A global-context
// key that is also a per-event field is skipped here and emitted once below from `fields`,
// letting the per-event value win. SLF4J's addKeyValue APPENDS rather than replaces, so
// emitting both would put two KeyValuePairs with the same name on the event — invalid
// duplicate-key JSON in JSON appenders. This mirrors the MDC dedup guard below.
val gc = logger.globalContext
if (gc.isNotEmpty()) {
val perEventKeys = fields
for ((k, v) in gc) {
if (perEventKeys?.containsKey(k) == true) continue
builder.addKeyValue(k, renderForLog(v))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,25 @@ class LoggingEventTest {
assertEquals("global-trace", traceIdEntries.single().value)
}

@Test
fun `per-event field overrides globalContext key and is emitted exactly once`() {
// A key carried by both the logger's globalContext and a per-event field() must be
// emitted once, with the per-event value winning. Two KeyValuePair entries with the
// same key serialise to invalid duplicate-key JSON in JSON appenders.
val (logger, fake) = enabledLogger(mapOf("region" to "global"))
logger.atInfo().field("region", "event").log()

val rec = fake.records.single()
val regionEntries = rec.keyValues.filter { it.key == "region" }
assertEquals(
1,
regionEntries.size,
"expected exactly one region entry; a second from globalContext would be a duplicate KeyValuePair",
)
// The per-event field wins over the globalContext value.
assertEquals("event", regionEntries.single().value)
}

@Test
fun `MDC keys absent from globalContext are still folded`() {
// Guard the collision fix against over-skipping: an MDC key NOT present in
Expand Down