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: [![Maven Central](https://img.shields.io/maven-central/v/co.elastic.logging/jul-ecs-formatter.svg)](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