Skip to content

Commit 2ad74ad

Browse files
feat: support customization for MDC serializer
1 parent 7ff2b43 commit 2ad74ad

File tree

5 files changed

+126
-3
lines changed

5 files changed

+126
-3
lines changed

log4j2-ecs-layout/src/main/java/co/elastic/logging/log4j2/MdcSerializer.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,42 @@
2424
*/
2525
package co.elastic.logging.log4j2;
2626

27+
import java.util.ServiceLoader;
28+
2729
import co.elastic.logging.EcsJsonSerializer;
2830
import co.elastic.logging.JsonUtils;
2931
import org.apache.logging.log4j.core.LogEvent;
3032
import org.apache.logging.log4j.util.TriConsumer;
3133

32-
interface MdcSerializer {
34+
public interface MdcSerializer {
3335

3436
void serializeMdc(LogEvent event, StringBuilder builder);
3537

3638
class Resolver {
3739

3840
public static MdcSerializer resolve() {
41+
MdcSerializer serializer = resolveUserDefinedMdcSerializer();
42+
if (serializer != null) {
43+
return serializer;
44+
}
3945
try {
4046
LogEvent.class.getMethod("getContextData");
4147
return (MdcSerializer) Class.forName("co.elastic.logging.log4j2.MdcSerializer$UsingContextData").getEnumConstants()[0];
42-
} catch (Exception ignore) {
43-
} catch (LinkageError ignore) {
48+
} catch (Exception | LinkageError ignore) {
4449
}
4550
return UsingContextMap.INSTANCE;
4651
}
4752

53+
private static MdcSerializer resolveUserDefinedMdcSerializer() {
54+
for (MdcSerializerFactory factory : ServiceLoader.load(MdcSerializerFactory.class)) {
55+
MdcSerializer serializer = factory.create();
56+
if (serializer != null) {
57+
return serializer;
58+
}
59+
}
60+
return null;
61+
}
62+
4863
}
4964

5065
/**
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*-
2+
* #%L
3+
* Java ECS logging
4+
* %%
5+
* Copyright (C) 2019 - 2020 Elastic and contributors
6+
* %%
7+
* Licensed to Elasticsearch B.V. under one or more contributor
8+
* license agreements. See the NOTICE file distributed with
9+
* this work for additional information regarding copyright
10+
* ownership. Elasticsearch B.V. licenses this file to you under
11+
* the Apache License, Version 2.0 (the "License"); you may
12+
* not use this file except in compliance with the License.
13+
* You may obtain a copy of the License at
14+
*
15+
* http://www.apache.org/licenses/LICENSE-2.0
16+
*
17+
* Unless required by applicable law or agreed to in writing,
18+
* software distributed under the License is distributed on an
19+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20+
* KIND, either express or implied. See the License for the
21+
* specific language governing permissions and limitations
22+
* under the License.
23+
* #L%
24+
*/
25+
package co.elastic.logging.log4j2;
26+
27+
/**
28+
* To customize the {@link MdcSerializer} used for serializing MDC (Mapped Diagnostic Context) data,
29+
* implement this interface and add the fully qualified class name to
30+
* {@code src/main/resources/META-INF/services/co.elastic.logging.log4j2.MdcSerializerFactory}.
31+
*/
32+
public interface MdcSerializerFactory {
33+
MdcSerializer create();
34+
}

log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/AbstractLog4j2EcsLayoutTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
import java.util.List;
3939

40+
import static co.elastic.logging.log4j2.TestMdcSerializerFactory.CUSTOM_MDC_SERIALIZER_TEST_KEY;
4041
import static org.assertj.core.api.Assertions.assertThat;
4142

4243
abstract class AbstractLog4j2EcsLayoutTest extends AbstractEcsLoggingTest {
@@ -52,6 +53,7 @@ void tearDown() throws Exception {
5253
void testAdditionalFieldsWithLookup() throws Exception {
5354
putMdc("trace.id", "foo");
5455
putMdc("foo", "bar");
56+
putMdc(CUSTOM_MDC_SERIALIZER_TEST_KEY, "some_text_lower_case");
5557
debug("test");
5658
assertThat(getAndValidateLastLogLine().get("cluster.uuid").textValue()).isEqualTo("9fe9134b-20b0-465e-acf9-8cc09ac9053b");
5759
assertThat(getAndValidateLastLogLine().get("node.id").textValue()).isEqualTo("foo");
@@ -60,6 +62,7 @@ void testAdditionalFieldsWithLookup() throws Exception {
6062
assertThat(getAndValidateLastLogLine().get("clazz").textValue()).startsWith(getClass().getPackageName());
6163
assertThat(getAndValidateLastLogLine().get("404")).isNull();
6264
assertThat(getAndValidateLastLogLine().get("foo").textValue()).isEqualTo("bar");
65+
assertThat(getAndValidateLastLogLine().get(CUSTOM_MDC_SERIALIZER_TEST_KEY).textValue()).isEqualTo("SOME_TEXT_LOWER_CASE");
6366
}
6467

6568
@Test
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*-
2+
* #%L
3+
* Java ECS logging
4+
* %%
5+
* Copyright (C) 2019 - 2020 Elastic and contributors
6+
* %%
7+
* Licensed to Elasticsearch B.V. under one or more contributor
8+
* license agreements. See the NOTICE file distributed with
9+
* this work for additional information regarding copyright
10+
* ownership. Elasticsearch B.V. licenses this file to you under
11+
* the Apache License, Version 2.0 (the "License"); you may
12+
* not use this file except in compliance with the License.
13+
* You may obtain a copy of the License at
14+
*
15+
* http://www.apache.org/licenses/LICENSE-2.0
16+
*
17+
* Unless required by applicable law or agreed to in writing,
18+
* software distributed under the License is distributed on an
19+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20+
* KIND, either express or implied. See the License for the
21+
* specific language governing permissions and limitations
22+
* under the License.
23+
* #L%
24+
*/
25+
package co.elastic.logging.log4j2;
26+
27+
import org.apache.logging.log4j.util.TriConsumer;
28+
29+
import co.elastic.logging.EcsJsonSerializer;
30+
import co.elastic.logging.JsonUtils;
31+
32+
public class TestMdcSerializerFactory implements MdcSerializerFactory {
33+
34+
protected static final String CUSTOM_MDC_SERIALIZER_TEST_KEY = "SPECIAL_TEST_CUSTOM_KEY";
35+
36+
@Override
37+
public MdcSerializer create() {
38+
return (event, builder) -> event.getContextData()
39+
.forEach((key, value) -> getWriteFunctionForKey(key).accept(key, value, builder));
40+
}
41+
42+
// Default function for serializing MDC entries
43+
private static final TriConsumer<String, Object, StringBuilder> DEFAULT_WRITE_MDC_FUNCTION = (key, value, stringBuilder) -> {
44+
stringBuilder.append('\"');
45+
JsonUtils.quoteAsString(key, stringBuilder);
46+
stringBuilder.append("\":\"");
47+
JsonUtils.quoteAsString(EcsJsonSerializer.toNullSafeString(String.valueOf(value)), stringBuilder);
48+
stringBuilder.append("\",");
49+
};
50+
51+
// Custom function for handling a specific key
52+
private static final TriConsumer<String, Object, StringBuilder> CUSTOM_KEY_WRITE_MDC_FUNCTION = (key, value, stringBuilder) -> DEFAULT_WRITE_MDC_FUNCTION.accept(
53+
key,
54+
value.toString().toUpperCase(),
55+
stringBuilder
56+
);
57+
58+
/**
59+
* Returns the appropriate function to write an MDC entry based on the key.
60+
*
61+
* @param key MDC key.
62+
* @return The function to serialize the MDC entry value.
63+
*/
64+
private TriConsumer<String, Object, StringBuilder> getWriteFunctionForKey(String key) {
65+
if (CUSTOM_MDC_SERIALIZER_TEST_KEY.equals(key)) {
66+
return CUSTOM_KEY_WRITE_MDC_FUNCTION;
67+
}
68+
return DEFAULT_WRITE_MDC_FUNCTION;
69+
}
70+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
co.elastic.logging.log4j2.TestMdcSerializerFactory

0 commit comments

Comments
 (0)