From e0b8238f54b6a8a37d23dfa182a3b20b972e5e65 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 21 Apr 2026 08:28:18 +0200 Subject: [PATCH 1/7] remove some duplication --- .../co/elastic/logging/EcsJsonSerializer.java | 15 ++++++++++----- .../logging/log4j2/DefaultMdcSerializer.java | 6 +----- .../logging/log4j2/CustomMdcSerializer.java | 6 +----- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java index 46c802ca..2a8f84f6 100644 --- a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java +++ b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java @@ -194,16 +194,21 @@ public static void serializeOrigin(StringBuilder builder, String fileName, Strin public static void serializeMDC(StringBuilder builder, Map properties) { if (properties != null && !properties.isEmpty()) { for (Map.Entry entry : properties.entrySet()) { - builder.append('\"'); String key = entry.getKey(); - JsonUtils.quoteAsString(key, builder); - builder.append("\":\""); - JsonUtils.quoteAsString(toNullSafeString(String.valueOf(entry.getValue())), builder); - builder.append("\","); + String value = String.valueOf(entry.getValue()); + serializeMdcEntry(builder, key, value); } } } + public static void serializeMdcEntry(StringBuilder builder, String key, String value) { + builder.append('\"'); + JsonUtils.quoteAsString(key, builder); + builder.append("\":\""); + JsonUtils.quoteAsString(toNullSafeString(value), builder); + builder.append("\","); + } + public static void serializeException(StringBuilder builder, Throwable thrown, boolean stackTraceAsArray) { if (thrown != null) { builder.append("\"error.type\":\""); diff --git a/log4j2-ecs-layout/src/main/java/co/elastic/logging/log4j2/DefaultMdcSerializer.java b/log4j2-ecs-layout/src/main/java/co/elastic/logging/log4j2/DefaultMdcSerializer.java index 3da690ef..a5fc6990 100644 --- a/log4j2-ecs-layout/src/main/java/co/elastic/logging/log4j2/DefaultMdcSerializer.java +++ b/log4j2-ecs-layout/src/main/java/co/elastic/logging/log4j2/DefaultMdcSerializer.java @@ -43,11 +43,7 @@ enum UsingContextData implements MdcSerializer { private static final TriConsumer WRITE_MDC = new TriConsumer() { @Override public void accept(final String key, final Object value, final StringBuilder stringBuilder) { - stringBuilder.append('\"'); - JsonUtils.quoteAsString(key, stringBuilder); - stringBuilder.append("\":\""); - JsonUtils.quoteAsString(EcsJsonSerializer.toNullSafeString(String.valueOf(value)), stringBuilder); - stringBuilder.append("\","); + EcsJsonSerializer.serializeMdcEntry(stringBuilder, key, String.valueOf(value)); } }; diff --git a/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/CustomMdcSerializer.java b/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/CustomMdcSerializer.java index c3bba8b1..af13021f 100644 --- a/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/CustomMdcSerializer.java +++ b/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/CustomMdcSerializer.java @@ -42,11 +42,7 @@ public void serializeMdc(LogEvent event, StringBuilder builder) { // Default function for serializing MDC entries private static final TriConsumer DEFAULT_WRITE_MDC_FUNCTION = (key, value, stringBuilder) -> { - stringBuilder.append('\"'); - JsonUtils.quoteAsString(key, stringBuilder); - stringBuilder.append("\":\""); - JsonUtils.quoteAsString(EcsJsonSerializer.toNullSafeString(String.valueOf(value)), stringBuilder); - stringBuilder.append("\","); + EcsJsonSerializer.serializeMdcEntry(stringBuilder, key, String.valueOf(value)); }; // Custom function for handling a specific key From 13ae3cce36d72c55916c075e28990638b6157b30 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:28:42 +0200 Subject: [PATCH 2/7] filter and spring cleaning --- .../co/elastic/logging/EcsJsonSerializer.java | 26 ++++++++++++++++++- .../elastic/logging/jul/EcsFormatterTest.java | 10 +++++++ .../EcsLayoutWithCustomMdcSerializerTest.java | 24 +++++++++++++++++ ...utWithNotExistCustomMdcSerializerTest.java | 24 +++++++++++++++++ .../log4j2/MdcSerializerResolverTest.java | 24 +++++++++++++++++ 5 files changed, 107 insertions(+), 1 deletion(-) diff --git a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java index 2a8f84f6..7fcba025 100644 --- a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java +++ b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java @@ -35,9 +35,30 @@ public class EcsJsonSerializer { private static final TimestampSerializer TIMESTAMP_SERIALIZER = new TimestampSerializer(); private static final ThreadLocal messageStringBuilder = new ThreadLocal(); - private static final String NEW_LINE = System.getProperty("line.separator"); + private static final String NEW_LINE = System.lineSeparator(); private static final Pattern NEW_LINE_PATTERN = Pattern.compile("\\r\\n|\\n|\\r"); + private static final Set RESERVED_KEYS = new HashSet(Arrays.asList( + // core + "@timestamp", + "message", + "log.logger", + "log.level", + "event.dataset", + "ecs.version", + // process + "process.thread.name", + "process.thread.id", + // service + "service.name", + "service.version", + "service.environment", + "service.node.name", + // error + "error.type", + "error.message", + "error.stack_trace")); + public static CharSequence toNullSafeString(final CharSequence s) { return s == null ? "" : s; } @@ -202,6 +223,9 @@ public static void serializeMDC(StringBuilder builder, Map properties } public static void serializeMdcEntry(StringBuilder builder, String key, String value) { + if (RESERVED_KEYS.contains(key)) { + return; + } builder.append('\"'); JsonUtils.quoteAsString(key, builder); builder.append("\":\""); diff --git a/jul-ecs-formatter/src/test/java/co/elastic/logging/jul/EcsFormatterTest.java b/jul-ecs-formatter/src/test/java/co/elastic/logging/jul/EcsFormatterTest.java index 573114de..8594f140 100644 --- a/jul-ecs-formatter/src/test/java/co/elastic/logging/jul/EcsFormatterTest.java +++ b/jul-ecs-formatter/src/test/java/co/elastic/logging/jul/EcsFormatterTest.java @@ -117,6 +117,16 @@ void testMdcSerialization_singleEntry() { assertThat(result.get("mdc.key").textValue()).isEqualTo("value"); } + @Test + void testMdcSerialization_filterEntries() { + Map mdc = new HashMap<>(); + TestMdcEcsFormatter mdcFormatter = new TestMdcEcsFormatter(mdc); + mdc.put("message", "mdc message"); + mdc.put("@timestamp", "mdc timestamp"); + JsonNode result = parseJson(mdcFormatter.format(record)); + assertThat(result.get("message").textValue()).isEqualTo(record.getMessage()); + } + private static JsonNode parseJson(String formatter) { try { return objectMapper.readTree(formatter); diff --git a/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/EcsLayoutWithCustomMdcSerializerTest.java b/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/EcsLayoutWithCustomMdcSerializerTest.java index 2cda0eaf..d8e09df5 100644 --- a/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/EcsLayoutWithCustomMdcSerializerTest.java +++ b/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/EcsLayoutWithCustomMdcSerializerTest.java @@ -1,3 +1,27 @@ +/*- + * #%L + * Java ECS logging + * %% + * Copyright (C) 2019 - 2026 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * #L% + */ package co.elastic.logging.log4j2; import static co.elastic.logging.log4j2.CustomMdcSerializer.CUSTOM_MDC_SERIALIZER_TEST_KEY; diff --git a/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/EcsLayoutWithNotExistCustomMdcSerializerTest.java b/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/EcsLayoutWithNotExistCustomMdcSerializerTest.java index 170660e9..38e5af48 100644 --- a/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/EcsLayoutWithNotExistCustomMdcSerializerTest.java +++ b/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/EcsLayoutWithNotExistCustomMdcSerializerTest.java @@ -1,3 +1,27 @@ +/*- + * #%L + * Java ECS logging + * %% + * Copyright (C) 2019 - 2026 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * #L% + */ package co.elastic.logging.log4j2; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/MdcSerializerResolverTest.java b/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/MdcSerializerResolverTest.java index 906fe9e6..7abdc4be 100644 --- a/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/MdcSerializerResolverTest.java +++ b/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/MdcSerializerResolverTest.java @@ -1,3 +1,27 @@ +/*- + * #%L + * Java ECS logging + * %% + * Copyright (C) 2019 - 2026 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * #L% + */ package co.elastic.logging.log4j2; import org.junit.jupiter.api.Test; From 1ad508a48fc171910cdfda65b642efcd60cef9d9 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:32:39 +0200 Subject: [PATCH 3/7] remove a few warnings --- .../co/elastic/logging/EcsJsonSerializer.java | 3 +++ .../co/elastic/logging/jul/EcsFormatterTest.java | 16 ++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java index 7fcba025..8680a081 100644 --- a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java +++ b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java @@ -26,8 +26,11 @@ import java.io.PrintWriter; import java.io.Writer; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/jul-ecs-formatter/src/test/java/co/elastic/logging/jul/EcsFormatterTest.java b/jul-ecs-formatter/src/test/java/co/elastic/logging/jul/EcsFormatterTest.java index 8594f140..ea92a0fe 100644 --- a/jul-ecs-formatter/src/test/java/co/elastic/logging/jul/EcsFormatterTest.java +++ b/jul-ecs-formatter/src/test/java/co/elastic/logging/jul/EcsFormatterTest.java @@ -55,7 +55,7 @@ void setUp() { } @Test - public void testFormatWithIncludeOriginFlag() throws Exception { + public void testFormatWithIncludeOriginFlag() { formatter.setIncludeOrigin(true); final String result = formatter.format(record); @@ -65,13 +65,13 @@ public void testFormatWithIncludeOriginFlag() throws Exception { } @Test - public void testFormatWithoutIncludeOriginFlag() throws Exception { + public void testFormatWithoutIncludeOriginFlag() { final JsonNode result = parseJson(formatter.format(record)); assertThat(result.get("log.origin")).isNull(); } @Test - public void testFormatWithoutLoggerName() throws Exception { + public void testFormatWithoutLoggerName() { record.setLoggerName(null); final JsonNode result = parseJson(formatter.format(record)); @@ -80,7 +80,7 @@ public void testFormatWithoutLoggerName() throws Exception { } @Test - public void testFormatWithEmptyLoggerName() throws Exception { + public void testFormatWithEmptyLoggerName() { record.setLoggerName(""); final JsonNode result = parseJson(formatter.format(record)); @@ -89,7 +89,7 @@ public void testFormatWithEmptyLoggerName() throws Exception { } @Test - public void testFormatWithInnerClassName() throws Exception { + public void testFormatWithInnerClassName() { formatter.setIncludeOrigin(true); record.setSourceClassName("test.ExampleClass$InnerClass"); @@ -99,7 +99,7 @@ public void testFormatWithInnerClassName() throws Exception { } @Test - public void testFormatWithInvalidClassName() throws Exception { + public void testFormatWithInvalidClassName() { formatter.setIncludeOrigin(true); record.setSourceClassName("$test.ExampleClass"); @@ -110,7 +110,7 @@ public void testFormatWithInvalidClassName() throws Exception { @Test void testMdcSerialization_singleEntry() { - Map mdc = new HashMap<>(); + Map mdc = new HashMap(); TestMdcEcsFormatter mdcFormatter = new TestMdcEcsFormatter(mdc); mdc.put("mdc.key", "value"); JsonNode result = parseJson(mdcFormatter.format(record)); @@ -119,7 +119,7 @@ void testMdcSerialization_singleEntry() { @Test void testMdcSerialization_filterEntries() { - Map mdc = new HashMap<>(); + Map mdc = new HashMap(); TestMdcEcsFormatter mdcFormatter = new TestMdcEcsFormatter(mdc); mdc.put("message", "mdc message"); mdc.put("@timestamp", "mdc timestamp"); From 8a4e8dd4113cbb4eb132daa5e28d60b5037b99f6 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:46:54 +0200 Subject: [PATCH 4/7] add test for mdc serialization --- .../logging/EcsJsonSerializerTest.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/ecs-logging-core/src/test/java/co/elastic/logging/EcsJsonSerializerTest.java b/ecs-logging-core/src/test/java/co/elastic/logging/EcsJsonSerializerTest.java index 3eca5e9e..267e62c1 100644 --- a/ecs-logging-core/src/test/java/co/elastic/logging/EcsJsonSerializerTest.java +++ b/ecs-logging-core/src/test/java/co/elastic/logging/EcsJsonSerializerTest.java @@ -32,7 +32,9 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -99,7 +101,7 @@ void testEscaping() throws IOException { } @Test - void serializeNullDoesNotThrowAnException() throws JsonProcessingException { + void serializeNullDoesNotThrowAnException() { StringBuilder stringBuilder = new StringBuilder(); EcsJsonSerializer.serializeFormattedMessage(stringBuilder, null); assertThat(stringBuilder.toString()).isEqualTo("\"message\":\"null\","); @@ -197,4 +199,24 @@ private void assertRemoveIfEndsWith(String builder, String ending, String expect EcsJsonSerializer.removeIfEndsWith(sb, ending); assertThat(sb.toString()).isEqualTo(expected); } + + @Test + void serializeMdc() throws JsonProcessingException { + StringBuilder jsonBuilder = new StringBuilder(); + EcsJsonSerializer.serializeObjectStart(jsonBuilder, 0); + Map mdc = new HashMap(); + mdc.put("message", "mdc message"); + mdc.put("@timestamp", "mdc timestamp"); + mdc.put("mdc.key1", "mdc value 1"); + mdc.put("mdc_key2", "mdc value 2"); + EcsJsonSerializer.serializeFormattedMessage(jsonBuilder, "formatted message"); + EcsJsonSerializer.serializeMDC(jsonBuilder, mdc); + EcsJsonSerializer.serializeObjectEnd(jsonBuilder); + + JsonNode jsonNode = objectMapper.readTree(jsonBuilder.toString()); + assertThat(jsonNode.get("message").textValue()).isEqualTo("formatted message"); + assertThat(jsonNode.get("@timestamp").textValue()).isEqualTo("1970-01-01T00:00:00.000Z"); + assertThat(jsonNode.get("mdc.key1").textValue()).isEqualTo("mdc value 1"); + assertThat(jsonNode.get("mdc_key2").textValue()).isEqualTo("mdc value 2"); + } } From 750fc23a04af2dabdf31e1e951263661be66ef37 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:00:17 +0200 Subject: [PATCH 5/7] simplify to minimum and still allow to use service.* --- .../co/elastic/logging/EcsJsonSerializer.java | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java index 8680a081..d41bdadc 100644 --- a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java +++ b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java @@ -41,26 +41,15 @@ public class EcsJsonSerializer { private static final String NEW_LINE = System.lineSeparator(); private static final Pattern NEW_LINE_PATTERN = Pattern.compile("\\r\\n|\\n|\\r"); + // Those keys are not expected to be used in MDC, thus we filter-out those keys to prevent major issues + // when they are present as top-level MDC keys. private static final Set RESERVED_KEYS = new HashSet(Arrays.asList( - // core "@timestamp", "message", "log.logger", "log.level", "event.dataset", - "ecs.version", - // process - "process.thread.name", - "process.thread.id", - // service - "service.name", - "service.version", - "service.environment", - "service.node.name", - // error - "error.type", - "error.message", - "error.stack_trace")); + "ecs.version")); public static CharSequence toNullSafeString(final CharSequence s) { return s == null ? "" : s; From 91c3380712902403ed0131b4ea84f6a18e7282a3 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:14:38 +0200 Subject: [PATCH 6/7] fix compatibility with old jdk --- .../src/main/java/co/elastic/logging/EcsJsonSerializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java index d41bdadc..d7287f79 100644 --- a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java +++ b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java @@ -38,7 +38,7 @@ public class EcsJsonSerializer { private static final TimestampSerializer TIMESTAMP_SERIALIZER = new TimestampSerializer(); private static final ThreadLocal messageStringBuilder = new ThreadLocal(); - private static final String NEW_LINE = System.lineSeparator(); + private static final String NEW_LINE = System.getProperty("line.separator"); private static final Pattern NEW_LINE_PATTERN = Pattern.compile("\\r\\n|\\n|\\r"); // Those keys are not expected to be used in MDC, thus we filter-out those keys to prevent major issues From 88c65710503ff2fe7e378d6137f13ffe45e9421c Mon Sep 17 00:00:00 2001 From: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:36:55 +0200 Subject: [PATCH 7/7] Update ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java Co-authored-by: jackshirazi --- .../src/main/java/co/elastic/logging/EcsJsonSerializer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java index d7287f79..1f56073c 100644 --- a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java +++ b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java @@ -49,6 +49,8 @@ public class EcsJsonSerializer { "log.logger", "log.level", "event.dataset", + "process.thread.name", + "process.thread.id", "ecs.version")); public static CharSequence toNullSafeString(final CharSequence s) {