diff --git a/README.md b/README.md
index c1ed29c2..f7e7eb33 100644
--- a/README.md
+++ b/README.md
@@ -112,6 +112,7 @@ For nested structures consider prefixing with `custom.` to make sure you won't g
- [Logback](logback-ecs-encoder/README.md) (default for Spring Boot)
- [Log4j2](log4j2-ecs-layout/README.md)
- [Log4j](log4j-ecs-layout/README.md)
+- [java.util.logging](jul-ecs-formatter/README.md)
### Step 2: Enable APM log correlation (optional)
If you are using the Elastic APM Java agent,
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 c23f4a0e..b40a0a29 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
@@ -52,9 +52,11 @@ public static void serializeObjectEnd(StringBuilder builder) {
}
public static void serializeLoggerName(StringBuilder builder, String loggerName) {
- builder.append("\"log.logger\":\"");
- JsonUtils.quoteAsString(loggerName, builder);
- builder.append("\",");
+ if (loggerName != null) {
+ builder.append("\"log.logger\":\"");
+ JsonUtils.quoteAsString(loggerName, builder);
+ builder.append("\",");
+ }
}
public static void serializeThreadName(StringBuilder builder, String threadName) {
@@ -65,6 +67,11 @@ public static void serializeThreadName(StringBuilder builder, String threadName)
}
}
+ public static void serializeThreadId(StringBuilder builder, long threadId) {
+ builder.append("\"process.thread.id\":");
+ builder.append(threadId);
+ builder.append(",");
+ }
public static void serializeFormattedMessage(StringBuilder builder, String message) {
builder.append("\"message\":\"");
JsonUtils.quoteAsString(message, builder);
@@ -134,9 +141,12 @@ public static void serializeOrigin(StringBuilder builder, String fileName, Strin
builder.append("\",");
builder.append("\"function\":\"");
JsonUtils.quoteAsString(methodName, builder);
- builder.append("\",");
- builder.append("\"file.line\":");
- builder.append(lineNumber);
+ builder.append('"');
+ if (lineNumber >= 0) {
+ builder.append(',');
+ builder.append("\"file.line\":");
+ builder.append(lineNumber);
+ }
builder.append("},");
}
diff --git a/ecs-logging-core/src/test/java/co/elastic/logging/AbstractEcsLoggingTest.java b/ecs-logging-core/src/test/java/co/elastic/logging/AbstractEcsLoggingTest.java
index a1c1d32d..e3046cde 100644
--- a/ecs-logging-core/src/test/java/co/elastic/logging/AbstractEcsLoggingTest.java
+++ b/ecs-logging-core/src/test/java/co/elastic/logging/AbstractEcsLoggingTest.java
@@ -61,9 +61,10 @@ void testSimpleLog() throws Exception {
@Test
void testThreadContext() throws Exception {
- putMdc("foo", "bar");
- debug("test");
- assertThat(getLastLogLine().get("foo").textValue()).isEqualTo("bar");
+ if (putMdc("foo", "bar")) {
+ debug("test");
+ assertThat(getLastLogLine().get("foo").textValue()).isEqualTo("bar");
+ }
}
@Test
@@ -76,14 +77,16 @@ void testThreadContextStack() throws Exception {
@Test
void testMdc() throws Exception {
- putMdc("transaction.id", "0af7651916cd43dd8448eb211c80319c");
- putMdc("span.id", "foo");
- putMdc("foo", "bar");
- debug("test");
- assertThat(getLastLogLine().get("labels.transaction.id")).isNull();
- assertThat(getLastLogLine().get("transaction.id").textValue()).isEqualTo("0af7651916cd43dd8448eb211c80319c");
- assertThat(getLastLogLine().get("span.id").textValue()).isEqualTo("foo");
- assertThat(getLastLogLine().get("foo").textValue()).isEqualTo("bar");
+ if (putMdc("transaction.id", "0af7651916cd43dd8448eb211c80319c")) {
+ putMdc("span.id", "foo");
+ putMdc("foo", "bar");
+ debug("test");
+ assertThat(getLastLogLine().get("labels.transaction.id")).isNull();
+ assertThat(getLastLogLine().get("transaction.id").textValue())
+ .isEqualTo("0af7651916cd43dd8448eb211c80319c");
+ assertThat(getLastLogLine().get("span.id").textValue()).isEqualTo("foo");
+ assertThat(getLastLogLine().get("foo").textValue()).isEqualTo("bar");
+ }
}
@Test
@@ -113,7 +116,9 @@ void testLogOrigin() throws Exception {
assertThat(getLastLogLine().get("log.origin").get("file.line").intValue()).isPositive();
}
- public abstract void putMdc(String key, String value);
+ public boolean putMdc(String key, String value) {
+ return false;
+ }
public boolean putNdc(String message) {
return false;
diff --git a/jul-ecs-formatter/README.md b/jul-ecs-formatter/README.md
new file mode 100644
index 00000000..3736aa59
--- /dev/null
+++ b/jul-ecs-formatter/README.md
@@ -0,0 +1,41 @@
+# ECS formatter for JUL
+
+Formatter for JUL (java.util.logging) which produce ECS-compatible records. May be useful for applications which use JUL as primary logging framework (e.g. Apache Tomcat).
+
+## Step 1: add dependency
+
+Latest version: [](https://search.maven.org/search?q=g:co.elastic.logging%20AND%20a:jul-ecs-formatter)
+
+Add a dependency to your application
+```xml
+
+ co.elastic.logging
+ jul-ecs-formatter
+ ${ecs-logging-java.version}
+
+```
+If you are not using a dependency management tool, like maven, you have to add both, `jul-ecs-formatter` and `ecs-logging-core` jars manually to the classpath. For example to the `$CATALINA_HOME/lib` directory.
+
+## Step 2: use the `EcsFormatter`
+
+Specify `co.elastic.logging.jul.EcsFormatter` as `formatter` for the required log handler.
+
+## Example
+For example `$CATALINA_HOME/conf/logging.properties`
+
+```properties
+java.util.logging.ConsoleHandler.level = FINE
+java.util.logging.ConsoleHandler.formatter = co.elastic.logging.jul.EcsFormatter
+co.elastic.logging.jul.EcsFormatter.serviceName=my-app
+```
+
+## Layout Parameters
+
+|Parameter name |Type |Default|Description|
+|-----------------|-------|-------|-----------|
+|serviceName |String | |Sets the `service.name` field so you can filter your logs by a particular service |
+|eventDataset |String |`${serviceName}.log`|Sets the `event.dataset` field used by the machine learning job of the Logs app to look for anomalies in the log rate. |
+|stackTraceAsArray|boolean|`false`|Serializes the [`error.stack_trace`](https://www.elastic.co/guide/en/ecs/current/ecs-error.html) as a JSON array where each element is in a new line to improve readability. Note that this requires a slightly more complex [Filebeat configuration](../README.md#when-stacktraceasarray-is-enabled).|
+|includeOrigin |boolean|`false`|If `true`, adds the [`log.origin.file.name`](https://www.elastic.co/guide/en/ecs/current/ecs-log.html), [`log.origin.file.line`](https://www.elastic.co/guide/en/ecs/current/ecs-log.html) and [`log.origin.function`](https://www.elastic.co/guide/en/ecs/current/ecs-log.html) fields. Note that JUL does not stores line number and `log.origin.file.line` will have '1' value. |
+
+
diff --git a/jul-ecs-formatter/pom.xml b/jul-ecs-formatter/pom.xml
new file mode 100644
index 00000000..c9a9adb3
--- /dev/null
+++ b/jul-ecs-formatter/pom.xml
@@ -0,0 +1,32 @@
+
+
+
+ ecs-logging-java-parent
+ co.elastic.logging
+ 0.3.1-SNAPSHOT
+
+ 4.0.0
+
+
+ ${project.basedir}/..
+
+
+ jul-ecs-formatter
+ ${project.groupId}:${project.artifactId}
+
+
+
+ ${project.groupId}
+ ecs-logging-core
+ ${project.version}
+
+
+ ${project.groupId}
+ ecs-logging-core
+ ${project.version}
+ test-jar
+ test
+
+
+
+
\ No newline at end of file
diff --git a/jul-ecs-formatter/src/main/java/co/elastic/logging/jul/EcsFormatter.java b/jul-ecs-formatter/src/main/java/co/elastic/logging/jul/EcsFormatter.java
new file mode 100644
index 00000000..fa06af68
--- /dev/null
+++ b/jul-ecs-formatter/src/main/java/co/elastic/logging/jul/EcsFormatter.java
@@ -0,0 +1,116 @@
+/*-
+ * #%L
+ * Java ECS logging
+ * %%
+ * Copyright (C) 2019 - 2020 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.jul;
+
+import java.util.logging.Formatter;
+import java.util.logging.LogManager;
+import java.util.logging.LogRecord;
+
+import co.elastic.logging.EcsJsonSerializer;
+
+public class EcsFormatter extends Formatter {
+
+ private static final String UNKNOWN_FILE = "";
+
+ private boolean stackTraceAsArray;
+ private String serviceName;
+ private boolean includeOrigin;
+ private String eventDataset;
+
+ /**
+ * Default constructor. Will read configuration from LogManager properties.
+ */
+ public EcsFormatter() {
+ serviceName = getProperty("co.elastic.logging.jul.EcsFormatter.serviceName", null);
+ includeOrigin = Boolean.getBoolean(getProperty("co.elastic.logging.jul.EcsFormatter.includeOrigin", "false"));
+ stackTraceAsArray = Boolean
+ .getBoolean(getProperty("co.elastic.logging.jul.EcsFormatter.stackTraceAsArray", "false"));
+ eventDataset = getProperty("co.elastic.logging.jul.EcsFormatter.eventDataset", null);
+ eventDataset = EcsJsonSerializer.computeEventDataset(eventDataset, serviceName);
+ }
+
+ @Override
+ public String format(final LogRecord record) {
+ final StringBuilder builder = new StringBuilder();
+ EcsJsonSerializer.serializeObjectStart(builder, record.getMillis());
+ EcsJsonSerializer.serializeLogLevel(builder, record.getLevel().getName());
+ EcsJsonSerializer.serializeFormattedMessage(builder, record.getMessage());
+ EcsJsonSerializer.serializeServiceName(builder, serviceName);
+ EcsJsonSerializer.serializeEventDataset(builder, eventDataset);
+ EcsJsonSerializer.serializeThreadId(builder, record.getThreadID());
+ EcsJsonSerializer.serializeLoggerName(builder, record.getLoggerName());
+ if (includeOrigin && record.getSourceClassName() != null && record.getSourceMethodName() != null) {
+ EcsJsonSerializer.serializeOrigin(builder, buildFileName(record.getSourceClassName()), record.getSourceMethodName(), -1);
+ }
+ final Throwable throwableInformation = record.getThrown();
+ if (throwableInformation != null) {
+ EcsJsonSerializer.serializeException(builder, throwableInformation, stackTraceAsArray);
+ }
+ EcsJsonSerializer.serializeObjectEnd(builder);
+ return builder.toString();
+ }
+
+ protected void setIncludeOrigin(final boolean includeOrigin) {
+ this.includeOrigin = includeOrigin;
+ }
+
+ protected void setServiceName(final String serviceName) {
+ this.serviceName = serviceName;
+ }
+
+ protected void setStackTraceAsArray(final boolean stackTraceAsArray) {
+ this.stackTraceAsArray = stackTraceAsArray;
+ }
+
+ public void setEventDataset(String eventDataset) {
+ this.eventDataset = eventDataset;
+ }
+
+ private String getProperty(final String name, final String defaultValue) {
+ String value = LogManager.getLogManager().getProperty(name);
+ if (value == null) {
+ value = defaultValue;
+ } else {
+ value = value.trim();
+ }
+ return value;
+ }
+
+ private String buildFileName(String className) {
+ String result = UNKNOWN_FILE;
+ if (className != null) {
+ int fileNameEnd = className.indexOf('$');
+ if (fileNameEnd < 0) {
+ fileNameEnd = className.length();
+ }
+ int classNameStart = className.lastIndexOf('.');
+ if (classNameStart < fileNameEnd) {
+ result = className.substring(classNameStart + 1, fileNameEnd) + ".java";
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/jul-ecs-formatter/src/main/resources/META-INF/NOTICE b/jul-ecs-formatter/src/main/resources/META-INF/NOTICE
new file mode 100644
index 00000000..5bbf1958
--- /dev/null
+++ b/jul-ecs-formatter/src/main/resources/META-INF/NOTICE
@@ -0,0 +1,2 @@
+ecs-logging-java
+Copyright 2019 - 2020 Elasticsearch B.V.
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
new file mode 100644
index 00000000..c86d1f12
--- /dev/null
+++ b/jul-ecs-formatter/src/test/java/co/elastic/logging/jul/EcsFormatterTest.java
@@ -0,0 +1,113 @@
+/*-
+ * #%L
+ * Java ECS logging
+ * %%
+ * Copyright (C) 2019 - 2020 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.jul;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.time.Instant;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class EcsFormatterTest {
+
+ private final EcsFormatter formatter = new EcsFormatter();
+
+ private final LogRecord record = new LogRecord(Level.INFO, "Example Meesage");
+
+ @Test
+ public void testFormatWithIncludeOriginFlag() {
+
+ formatter.setIncludeOrigin(true);
+
+ final String result = formatter.format(record);
+
+ assertThat(result).isEqualTo(
+ "{\"@timestamp\":\"1970-01-01T00:00:00.005Z\", \"log.level\": \"INFO\", \"message\":\"Example Meesage\", \"process.thread.id\":7,\"log.logger\":\"ExampleLogger\",\"log.origin\":{\"file.name\":\"ExampleClass.java\",\"function\":\"exampleMethod\"}}\n");
+ }
+
+ @Test
+ public void testFormatWithoutIncludeOriginFlag() {
+
+ final String result = formatter.format(record);
+
+ assertThat(result).isEqualTo(
+ "{\"@timestamp\":\"1970-01-01T00:00:00.005Z\", \"log.level\": \"INFO\", \"message\":\"Example Meesage\", \"process.thread.id\":7,\"log.logger\":\"ExampleLogger\"}\n");
+ }
+
+ @Test
+ public void testFormatWithoutLoggerName() {
+ record.setLoggerName(null);
+
+ final String result = formatter.format(record);
+
+ assertThat(result).isEqualTo(
+ "{\"@timestamp\":\"1970-01-01T00:00:00.005Z\", \"log.level\": \"INFO\", \"message\":\"Example Meesage\", \"process.thread.id\":7}\n");
+ }
+
+ @Test
+ public void testFormatWithEmptyLoggerName() {
+ record.setLoggerName("");
+
+ final String result = formatter.format(record);
+
+ assertThat(result).isEqualTo(
+ "{\"@timestamp\":\"1970-01-01T00:00:00.005Z\", \"log.level\": \"INFO\", \"message\":\"Example Meesage\", \"process.thread.id\":7,\"log.logger\":\"\"}\n");
+ }
+
+ @Test
+ public void testFormatWithInnerClassName() {
+ formatter.setIncludeOrigin(true);
+ record.setSourceClassName("test.ExampleClass$InnerClass");
+
+ final String result = formatter.format(record);
+
+ assertThat(result).isEqualTo(
+ "{\"@timestamp\":\"1970-01-01T00:00:00.005Z\", \"log.level\": \"INFO\", \"message\":\"Example Meesage\", \"process.thread.id\":7,\"log.logger\":\"ExampleLogger\",\"log.origin\":{\"file.name\":\"ExampleClass.java\",\"function\":\"exampleMethod\"}}\n");
+ }
+
+ @Test
+ public void testFormatWithInvalidClassName() {
+ formatter.setIncludeOrigin(true);
+ record.setSourceClassName("$test.ExampleClass");
+
+ final String result = formatter.format(record);
+
+ assertThat(result).isEqualTo(
+ "{\"@timestamp\":\"1970-01-01T00:00:00.005Z\", \"log.level\": \"INFO\", \"message\":\"Example Meesage\", \"process.thread.id\":7,\"log.logger\":\"ExampleLogger\",\"log.origin\":{\"file.name\":\"\",\"function\":\"exampleMethod\"}}\n");
+ }
+
+ @BeforeEach
+ void setUp() {
+ record.setInstant(Instant.ofEpochMilli(5));
+ record.setSourceClassName("ExampleClass");
+ record.setSourceMethodName("exampleMethod");
+ record.setThreadID(7);
+ record.setLoggerName("ExampleLogger");
+ }
+
+}
diff --git a/jul-ecs-formatter/src/test/java/co/elastic/logging/jul/JulLoggingTestTest.java b/jul-ecs-formatter/src/test/java/co/elastic/logging/jul/JulLoggingTestTest.java
new file mode 100644
index 00000000..e0148da8
--- /dev/null
+++ b/jul-ecs-formatter/src/test/java/co/elastic/logging/jul/JulLoggingTestTest.java
@@ -0,0 +1,152 @@
+/*-
+ * #%L
+ * Java ECS logging
+ * %%
+ * Copyright (C) 2019 - 2020 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.jul;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.within;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.logging.StreamHandler;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import co.elastic.logging.AbstractEcsLoggingTest;
+
+public class JulLoggingTestTest extends AbstractEcsLoggingTest {
+
+ private final class InMemoryStreamHandler extends StreamHandler {
+ private InMemoryStreamHandler(OutputStream out, Formatter formatter) {
+ super(out, formatter);
+ }
+
+ /**
+ * Override {@code StreamHandler.close} to do a flush but not
+ * to close the output stream. That is, we do not
+ * close {@code System.err}.
+ */
+ @Override
+ public void close() {
+ flush();
+ }
+
+ @Override
+ public void publish(LogRecord record) {
+ super.publish(record);
+ flush();
+ }
+ }
+
+ private final EcsFormatter formatter = new EcsFormatter();
+
+ private Logger logger = Logger.getLogger("");
+
+ private ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ private LogRecord record;
+
+ @Override
+ public void debug(String message) {
+ logger.log(Level.FINE, message);
+ }
+
+ @Override
+ public void error(String message, Throwable t) {
+ logger.log(Level.SEVERE, message, t);
+ }
+
+ @Override
+ public JsonNode getLastLogLine() throws IOException {
+ return objectMapper.readTree(out.toString());
+ }
+
+ @BeforeEach
+ void setUp() {
+ clearHandlers();
+
+ formatter.setIncludeOrigin(true);
+ formatter.setStackTraceAsArray(true);
+ formatter.setServiceName("test");
+ formatter.setEventDataset("testdataset.log");
+
+ Handler handler = new InMemoryStreamHandler(out, formatter);
+ handler.setLevel(Level.ALL);
+
+ logger.addHandler(handler);
+ logger.setLevel(Level.ALL);
+ }
+
+ @Test
+ void testLogException() throws Exception {
+ error("test", new RuntimeException("test"));
+ assertThat(getLastLogLine().get("log.level").textValue()).isEqualTo("SEVERE");
+ assertThat(getLastLogLine().get("error.message").textValue()).isEqualTo("test");
+ assertThat(getLastLogLine().get("error.type").textValue()).isEqualTo(RuntimeException.class.getName());
+ String stackTrace = StreamSupport.stream(getLastLogLine().get("error.stack_trace").spliterator(), false)
+ .map(JsonNode::textValue)
+ .collect(Collectors.joining("\n", "", "\n"));
+ assertThat(stackTrace).contains("at co.elastic.logging.jul.JulLoggingTestTest.testLogException");
+ }
+
+ @Test
+ void testMetadata() throws Exception {
+ debug("test");
+ assertThat(getLastLogLine().get("process.thread.id").longValue()).isEqualTo(Thread.currentThread().getId());
+ assertThat(getLastLogLine().get("service.name").textValue()).isEqualTo("test");
+ assertThat(Instant.parse(getLastLogLine().get("@timestamp").textValue())).isCloseTo(Instant.now(), within(1, ChronoUnit.MINUTES));
+ assertThat(getLastLogLine().get("log.level").textValue()).isEqualTo("FINE");
+ assertThat(getLastLogLine().get("log.logger")).isNotNull();
+ assertThat(getLastLogLine().get("event.dataset").textValue()).isEqualTo("testdataset.log");
+ }
+
+ @Test
+ void testLogOrigin() throws Exception {
+ debug("test");
+ assertThat(getLastLogLine().get("log.origin").get("file.name").textValue()).endsWith(".java");
+ assertThat(getLastLogLine().get("log.origin").get("function").textValue()).isEqualTo("debug");
+ //No file.line for JUL
+ }
+
+
+ private void clearHandlers() {
+ for (Handler handler : logger.getHandlers()) {
+ logger.removeHandler(handler);
+ }
+ }
+
+}
diff --git a/log4j-ecs-layout/src/test/java/co/elastic/logging/log4j/Log4jEcsLayoutTest.java b/log4j-ecs-layout/src/test/java/co/elastic/logging/log4j/Log4jEcsLayoutTest.java
index 3f4174fd..002fc0b8 100644
--- a/log4j-ecs-layout/src/test/java/co/elastic/logging/log4j/Log4jEcsLayoutTest.java
+++ b/log4j-ecs-layout/src/test/java/co/elastic/logging/log4j/Log4jEcsLayoutTest.java
@@ -65,9 +65,10 @@ void tearDown() {
}
@Override
- public void putMdc(String key, String value) {
+ public boolean putMdc(String key, String value) {
MDC.put(key, value);
Assumptions.assumeTrue(value.equals(MDC.get(key)));
+ return true;
}
@Override
diff --git a/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/AbstractLog4j2EcsLayoutTest.java b/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/AbstractLog4j2EcsLayoutTest.java
index ed4e8390..f8f8c851 100644
--- a/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/AbstractLog4j2EcsLayoutTest.java
+++ b/log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/AbstractLog4j2EcsLayoutTest.java
@@ -178,8 +178,9 @@ public void setBaz(boolean baz) {
}
@Override
- public void putMdc(String key, String value) {
+ public boolean putMdc(String key, String value) {
ThreadContext.put(key, value);
+ return true;
}
@Override
diff --git a/logback-ecs-encoder/src/test/java/co/elastic/logging/logback/AbstractEcsEncoderTest.java b/logback-ecs-encoder/src/test/java/co/elastic/logging/logback/AbstractEcsEncoderTest.java
index 70f23d0c..63fd1bba 100644
--- a/logback-ecs-encoder/src/test/java/co/elastic/logging/logback/AbstractEcsEncoderTest.java
+++ b/logback-ecs-encoder/src/test/java/co/elastic/logging/logback/AbstractEcsEncoderTest.java
@@ -38,8 +38,9 @@ abstract class AbstractEcsEncoderTest extends AbstractEcsLoggingTest {
protected Logger logger;
@Override
- public void putMdc(String key, String value) {
+ public boolean putMdc(String key, String value) {
MDC.put(key, value);
+ return true;
}
@Override
diff --git a/pom.xml b/pom.xml
index c312b1ed..ba0c88d2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,6 +11,7 @@
log4j-ecs-layout
log4j2-ecs-layout
logback-ecs-encoder
+ jul-ecs-formatter
pom
2019