diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 73bd99f9fa..2a7e1d7fbe 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -28,7 +28,7 @@ endif::[] [float] ===== Features -* Cool new feature: {pull}2526[#2526] +* Add support for log correlation for `java.util.logging` (JUL) - {pull}2724[#2724] [float] ===== Bug fixes diff --git a/apm-agent-attach-cli/pom.xml b/apm-agent-attach-cli/pom.xml index 93d8ecfcb2..b6ba71c740 100644 --- a/apm-agent-attach-cli/pom.xml +++ b/apm-agent-attach-cli/pom.xml @@ -42,7 +42,7 @@ co.elastic.logging log4j2-ecs-layout - ${version.log4j2-ecs-layout} + ${version.ecs.logging} org.bouncycastle diff --git a/apm-agent-core/pom.xml b/apm-agent-core/pom.xml index 91b172aa7d..409f163283 100644 --- a/apm-agent-core/pom.xml +++ b/apm-agent-core/pom.xml @@ -77,7 +77,7 @@ co.elastic.logging log4j2-ecs-layout - ${version.log4j2-ecs-layout} + ${version.ecs.logging} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ClassLoaderNameMatcher.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ClassLoaderNameMatcher.java index c9421639c4..a9f3965889 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ClassLoaderNameMatcher.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ClassLoaderNameMatcher.java @@ -47,8 +47,6 @@ public boolean matches(ClassLoader target) { }; } - - public static ElementMatcher.Junction isReflectionClassLoader() { return classLoaderWithName("sun.reflect.DelegatingClassLoader") .or(classLoaderWithName("jdk.internal.reflect.DelegatingClassLoader")); diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/CustomElementMatchers.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/CustomElementMatchers.java index c7c821df08..27b023cf17 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/CustomElementMatchers.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/CustomElementMatchers.java @@ -53,6 +53,7 @@ public class CustomElementMatchers { private static final Logger logger = LoggerFactory.getLogger(CustomElementMatchers.class); + private static final ElementMatcher.Junction.AbstractBase AGENT_CLASS_LOADER_MATCHER = new ElementMatcher.Junction.AbstractBase() { @Override public boolean matches(@Nullable ClassLoader classLoader) { @@ -60,6 +61,15 @@ public boolean matches(@Nullable ClassLoader classLoader) { } }; + private static final ElementMatcher.Junction.AbstractBase INTERNAL_PLUGIN_CLASS_LOADER_MATCHER = new ElementMatcher.Junction.AbstractBase() { + @Override + public boolean matches(@Nullable ClassLoader classLoader) { + + boolean result = ClassLoaderUtils.isInternalPluginClassLoader(classLoader); + return result; + } + }; + public static ElementMatcher.Junction isInAnyPackage(Collection includedPackages, ElementMatcher.Junction defaultIfEmpty) { if (includedPackages.isEmpty()) { @@ -74,6 +84,7 @@ public static ElementMatcher.Junction isInAnyPackage(Collection @@ -188,6 +198,10 @@ public static ElementMatcher.Junction isAgentClassLoader() { return AGENT_CLASS_LOADER_MATCHER; } + public static ElementMatcher.Junction isInternalPluginClassLoader() { + return INTERNAL_PLUGIN_CLASS_LOADER_MATCHER; + } + private enum Matcher { LTE { @Override @@ -202,6 +216,7 @@ > boolean match(T c1, T c2) { } }; + abstract > boolean match(T c1, T c2); } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/util/ClassLoaderUtils.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/ClassLoaderUtils.java index d21902ac98..b10c09d78a 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/util/ClassLoaderUtils.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/ClassLoaderUtils.java @@ -18,31 +18,12 @@ */ package co.elastic.apm.agent.util; +import co.elastic.apm.agent.bci.classloading.IndyPluginClassLoader; + import javax.annotation.Nullable; public class ClassLoaderUtils { - /** - * Checks whether the provided {@link ClassLoader} may be unloaded like a web application class loader, for example. - *

- * If the class loader can't be unloaded, it is safe to use {@link ThreadLocal}s and to reuse the {@code WeakConcurrentMap.LookupKey}. - * Otherwise, the use of {@link ThreadLocal}s may lead to class loader leaks as it prevents the class loader this class - * is loaded by to unload. - *

- * - * @param classLoader The class loader to check. - * @return {@code true} if the provided class loader can be unloaded. - */ - public static boolean isPersistentClassLoader(@Nullable ClassLoader classLoader) { - try { - return classLoader == null // bootstrap class loader - || classLoader == ClassLoader.getSystemClassLoader() - || classLoader == ClassLoader.getSystemClassLoader().getParent(); // ext/platfrom class loader; - } catch (Throwable ignored) { - return false; - } - } - public static boolean isAgentClassLoader(@Nullable ClassLoader classLoader) { return (classLoader != null && classLoader.getClass().getName().startsWith("co.elastic.apm")) || // This one also covers unit tests, where the app class loader loads the agent @@ -52,4 +33,11 @@ public static boolean isAgentClassLoader(@Nullable ClassLoader classLoader) { public static boolean isBootstrapClassLoader(@Nullable ClassLoader classLoader) { return classLoader == null; } + + public static boolean isInternalPluginClassLoader(@Nullable ClassLoader classLoader) { + if (classLoader == null) { + return false; + } + return IndyPluginClassLoader.class.getName().equals(classLoader.getClass().getName()); + } } diff --git a/apm-agent-plugins/apm-ecs-logging-plugin/pom.xml b/apm-agent-plugins/apm-ecs-logging-plugin/pom.xml index 644651d966..6745ad7cce 100644 --- a/apm-agent-plugins/apm-ecs-logging-plugin/pom.xml +++ b/apm-agent-plugins/apm-ecs-logging-plugin/pom.xml @@ -19,13 +19,25 @@ co.elastic.logging log4j-ecs-layout - 1.4.0 + ${version.ecs.logging} provided co.elastic.logging log4j2-ecs-layout - 1.4.0 + ${version.ecs.logging} + provided + + + co.elastic.logging + jul-ecs-formatter + ${version.ecs.logging} + provided + + + co.elastic.apm + apm-logging-plugin-common + ${project.version} provided @@ -39,6 +51,7 @@ ivy test + diff --git a/apm-agent-plugins/apm-ecs-logging-plugin/src/main/java/co/elastic/apm/agent/ecs_logging/JulEcsFormatterInstrumentation.java b/apm-agent-plugins/apm-ecs-logging-plugin/src/main/java/co/elastic/apm/agent/ecs_logging/JulEcsFormatterInstrumentation.java new file mode 100644 index 0000000000..424fbe3589 --- /dev/null +++ b/apm-agent-plugins/apm-ecs-logging-plugin/src/main/java/co/elastic/apm/agent/ecs_logging/JulEcsFormatterInstrumentation.java @@ -0,0 +1,77 @@ +/* + * 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. + */ +package co.elastic.apm.agent.ecs_logging; + +import co.elastic.apm.agent.bci.TracerAwareInstrumentation; +import co.elastic.apm.agent.bci.bytebuddy.CustomElementMatchers; +import co.elastic.apm.agent.loginstr.correlation.CorrelationIdMapAdapter; +import co.elastic.logging.jul.EcsFormatter; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; + +/** + * Instruments {@link EcsFormatter#getMdcEntries()} to provide correlation IDs at runtime. + * Application(s) copies of ecs-logging and the one in the agent will be instrumented, hence providing log correlation + * for all of them. + */ +@SuppressWarnings("JavadocReference") +public class JulEcsFormatterInstrumentation extends TracerAwareInstrumentation { + + @Override + public ElementMatcher.Junction getClassLoaderMatcher() { + // ECS formatter that is loaded within the agent should not be instrumented + return not(CustomElementMatchers.isInternalPluginClassLoader()); + } + + @Override + public Collection getInstrumentationGroupNames() { + return Arrays.asList("logging", "jul-ecs"); + } + + @Override + public ElementMatcher getTypeMatcher() { + return named("co.elastic.logging.jul.EcsFormatter"); + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("getMdcEntries"); + } + + public static class AdviceClass { + + @Advice.AssignReturned.ToReturned + @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false) + public static Map onExit() { + return CorrelationIdMapAdapter.get(); + } + + } + + +} diff --git a/apm-agent-plugins/apm-ecs-logging-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-ecs-logging-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation index 58294e83a5..55b6a4dc57 100644 --- a/apm-agent-plugins/apm-ecs-logging-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation +++ b/apm-agent-plugins/apm-ecs-logging-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation @@ -1,2 +1,4 @@ co.elastic.apm.agent.ecs_logging.Log4j2ServiceNameInstrumentation co.elastic.apm.agent.ecs_logging.Log4j2ServiceVersionInstrumentation + +co.elastic.apm.agent.ecs_logging.JulEcsFormatterInstrumentation diff --git a/apm-agent-plugins/apm-ecs-logging-plugin/src/test/java/co/elastic/apm/agent/ecs_logging/JulEcsFormatterInstrumentationTest.java b/apm-agent-plugins/apm-ecs-logging-plugin/src/test/java/co/elastic/apm/agent/ecs_logging/JulEcsFormatterInstrumentationTest.java new file mode 100644 index 0000000000..d83e85f390 --- /dev/null +++ b/apm-agent-plugins/apm-ecs-logging-plugin/src/test/java/co/elastic/apm/agent/ecs_logging/JulEcsFormatterInstrumentationTest.java @@ -0,0 +1,82 @@ +/* + * 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. + */ +package co.elastic.apm.agent.ecs_logging; + +import co.elastic.apm.agent.AbstractInstrumentationTest; +import co.elastic.apm.agent.impl.error.ErrorCapture; +import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.logging.jul.EcsFormatter; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import static co.elastic.apm.agent.testutils.assertions.Assertions.assertThat; + +public class JulEcsFormatterInstrumentationTest extends AbstractInstrumentationTest { + + @Test + void testNoCorrelation() { + JsonNode logLine = logSomething(); + + assertThat(logLine.get("transaction.id")).isNull(); + assertThat(logLine.get("trace.id")).isNull(); + } + + @Test + void testActiveTransaction() { + Transaction transaction = startTestRootTransaction("log"); + try { + JsonNode logLine = logSomething(); + + assertThat(logLine.get("transaction.id").textValue()).isEqualTo(transaction.getTraceContext().getTransactionId().toString()); + assertThat(logLine.get("trace.id").textValue()).isEqualTo(transaction.getTraceContext().getTraceId().toString()); + } finally { + transaction.deactivate().end(); + } + } + + @Test + void testActiveError() { + ErrorCapture error = new ErrorCapture(tracer); + + error.activate(); + try { + JsonNode logLine = logSomething(); + + assertThat(logLine.get("error.id").textValue()).isEqualTo(error.getTraceContext().getId().toString()); + } finally { + error.deactivate(); + } + + } + + private static JsonNode logSomething() { + EcsFormatter formatter = new EcsFormatter(); + LogRecord record = new LogRecord(Level.INFO, "msg"); + try { + return new ObjectMapper().readTree(formatter.format(record)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/java/co/elastic/apm/agent/jul/JulInstrumentation.java b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/java/co/elastic/apm/agent/jul/JulInstrumentation.java new file mode 100644 index 0000000000..ede02eb161 --- /dev/null +++ b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/java/co/elastic/apm/agent/jul/JulInstrumentation.java @@ -0,0 +1,33 @@ +/* + * 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. + */ +package co.elastic.apm.agent.jul; + +import co.elastic.apm.agent.loginstr.AbstractLogIntegrationInstrumentation; + +import java.util.Collection; + +public abstract class JulInstrumentation extends AbstractLogIntegrationInstrumentation { + + @Override + public Collection getInstrumentationGroupNames() { + Collection ret = super.getInstrumentationGroupNames(); + ret.add("jul-ecs"); + return ret; + } +} diff --git a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/java/co/elastic/apm/agent/jul/error/JulLoggerErrorCapturingInstrumentation.java b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/java/co/elastic/apm/agent/jul/error/JulLoggerErrorCapturingInstrumentation.java new file mode 100644 index 0000000000..83e54aaaff --- /dev/null +++ b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/java/co/elastic/apm/agent/jul/error/JulLoggerErrorCapturingInstrumentation.java @@ -0,0 +1,84 @@ +/* + * 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. + */ +package co.elastic.apm.agent.jul.error; + +import co.elastic.apm.agent.loginstr.error.AbstractLoggerErrorCapturingInstrumentation; +import co.elastic.apm.agent.loginstr.error.LoggerErrorHelper; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.logging.LogRecord; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +public class JulLoggerErrorCapturingInstrumentation extends AbstractLoggerErrorCapturingInstrumentation { + + @Override + public Collection getInstrumentationGroupNames() { + Collection ret = super.getInstrumentationGroupNames(); + ret.add("jul-error"); + return ret; + } + + @Override + public ElementMatcher getTypeMatcher() { + return named("java.util.logging.Logger"); + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("log") + .and(takesArgument(0, named("java.util.logging.LogRecord"))); + } + + @Override + public String getAdviceClassName() { + return "co.elastic.apm.agent.jul.error.JulLoggerErrorCapturingInstrumentation$LoggingAdvice"; + } + + public static class LoggingAdvice { + + private static final LoggerErrorHelper helper = new LoggerErrorHelper(LoggingAdvice.class, tracer); + + @Nullable + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + public static Object onEnter(@Advice.Argument(0) LogRecord record, @Advice.Origin Class clazz) { + Throwable thrown = record.getThrown(); + + // ignore levels < SEVERE + if (record.getLevel().intValue() < 1000) { + thrown = null; + } + + return helper.enter(thrown, clazz); + } + + @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false) + public static void onExit(@Advice.Enter @Nullable Object errorCaptureObj) { + helper.exit(errorCaptureObj); + } + } + + +} diff --git a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/java/co/elastic/apm/agent/jul/reformatting/JulEcsReformattingHelper.java b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/java/co/elastic/apm/agent/jul/reformatting/JulEcsReformattingHelper.java index 32d41785d4..b015ff0a07 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/java/co/elastic/apm/agent/jul/reformatting/JulEcsReformattingHelper.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/java/co/elastic/apm/agent/jul/reformatting/JulEcsReformattingHelper.java @@ -18,6 +18,7 @@ */ package co.elastic.apm.agent.jul.reformatting; +import co.elastic.apm.agent.loginstr.correlation.CorrelationIdMapAdapter; import co.elastic.apm.agent.loginstr.reformatting.AbstractEcsReformattingHelper; import co.elastic.apm.agent.loginstr.reformatting.Utils; import co.elastic.apm.agent.sdk.logging.Logger; @@ -86,7 +87,13 @@ protected String getAppenderName(StreamHandler handler) { protected Formatter createEcsFormatter(String eventDataset, @Nullable String serviceName, @Nullable String serviceVersion, @Nullable String serviceNodeName, @Nullable Map additionalFields, Formatter originalFormatter) { - EcsFormatter ecsFormatter = new EcsFormatter(); + EcsFormatter ecsFormatter = new EcsFormatter() { + @Override + protected Map getMdcEntries() { + // using internal tracer state as ECS formatter is not instrumented within the agent plugin + return CorrelationIdMapAdapter.get(); + } + }; ecsFormatter.setServiceName(serviceName); ecsFormatter.setServiceVersion(serviceVersion); ecsFormatter.setServiceNodeName(serviceNodeName); diff --git a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/java/co/elastic/apm/agent/jul/reformatting/JulLogReformattingInstrumentation.java b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/java/co/elastic/apm/agent/jul/reformatting/JulLogReformattingInstrumentation.java index 6a8d2eec7f..6add897e8f 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/java/co/elastic/apm/agent/jul/reformatting/JulLogReformattingInstrumentation.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/java/co/elastic/apm/agent/jul/reformatting/JulLogReformattingInstrumentation.java @@ -18,12 +18,11 @@ */ package co.elastic.apm.agent.jul.reformatting; -import co.elastic.apm.agent.loginstr.AbstractLogIntegrationInstrumentation; +import co.elastic.apm.agent.jul.JulInstrumentation; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; -import java.util.Collection; import java.util.logging.Handler; import java.util.logging.LogRecord; @@ -32,14 +31,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; -public abstract class JulLogReformattingInstrumentation extends AbstractLogIntegrationInstrumentation { - - @Override - public Collection getInstrumentationGroupNames() { - Collection ret = super.getInstrumentationGroupNames(); - ret.add("jul-ecs"); - return ret; - } +public abstract class JulLogReformattingInstrumentation extends JulInstrumentation { @Override public ElementMatcher.Junction getClassLoaderMatcher() { @@ -99,7 +91,8 @@ public static class StopAppenderInstrumentation extends JulLogReformattingInstru @Override public ElementMatcher getTypeMatcher() { - return named("java.util.logging.ConsoleHandler").or(named("java.util.logging.FileHandler")); + return named("java.util.logging.ConsoleHandler") + .or(named("java.util.logging.FileHandler")); } /** diff --git a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation index 582843d242..6a74b3bc0b 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation +++ b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation @@ -3,4 +3,8 @@ co.elastic.apm.agent.jul.reformatting.JulLogReformattingInstrumentation$FileRefo co.elastic.apm.agent.jul.reformatting.JulLogReformattingInstrumentation$ConsoleReformattingInstrumentation co.elastic.apm.agent.jul.reformatting.JulLogReformattingInstrumentation$StopAppenderInstrumentation +# Trace context correlation +# Provided through ECS logging instrumentation as there is no dedicated MDC for JUL. + # Error creation +co.elastic.apm.agent.jul.error.JulLoggerErrorCapturingInstrumentation diff --git a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/test/java/co/elastic/apm/agent/jul/JulInstrumentationTest.java b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/test/java/co/elastic/apm/agent/jul/JulInstrumentationTest.java index 568d98a36e..b3217b8253 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/test/java/co/elastic/apm/agent/jul/JulInstrumentationTest.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/test/java/co/elastic/apm/agent/jul/JulInstrumentationTest.java @@ -18,8 +18,8 @@ */ package co.elastic.apm.agent.jul; -import co.elastic.apm.agent.loginstr.LoggingInstrumentationTest; import co.elastic.apm.agent.loginstr.LoggerFacade; +import co.elastic.apm.agent.loginstr.LoggingInstrumentationTest; import java.io.File; import java.io.IOException; @@ -51,11 +51,6 @@ protected String getLogReformattingFilePath() { return super.getLogReformattingFilePath() + ".0"; } - @Override - protected boolean isLogCorrelationSupported() { - return false; - } - /** * Custom log levels that match other logging frameworks */ diff --git a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/test/java/co/elastic/apm/agent/jul/error/JulLoggerErrorCapturingInstrumentationTest.java b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/test/java/co/elastic/apm/agent/jul/error/JulLoggerErrorCapturingInstrumentationTest.java index 4bd53ca2e1..94388d5ee2 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/test/java/co/elastic/apm/agent/jul/error/JulLoggerErrorCapturingInstrumentationTest.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/test/java/co/elastic/apm/agent/jul/error/JulLoggerErrorCapturingInstrumentationTest.java @@ -19,21 +19,47 @@ package co.elastic.apm.agent.jul.error; import co.elastic.apm.agent.loginstr.error.AbstractErrorLoggingInstrumentationTest; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.util.logging.Level; +import java.util.logging.LogRecord; import java.util.logging.Logger; -// todo: add support and enable -@Disabled public class JulLoggerErrorCapturingInstrumentationTest extends AbstractErrorLoggingInstrumentationTest { private static final Logger logger = Logger.getLogger(JulLoggerErrorCapturingInstrumentationTest.class.getName()); @Test void captureErrorExceptionWithStringMessage() { - logger.log(Level.SEVERE, "exception captured", new RuntimeException("some business exception")); - verifyThatExceptionCaptured(1, "some business exception", RuntimeException.class); + logWithMessage(Level.SEVERE); + verifyExceptionCaptured("some business exception", RuntimeException.class); + } + + @Test + void ignoreWarningWithStringMessage() { + logWithMessage(Level.WARNING); + verifyNoExceptionCaptured(); + } + + private static void logWithMessage(Level level) { + logger.log(level, "exception captured", new RuntimeException("some business exception")); + } + + @Test + void captureErrorExceptionWithLogRecord() { + logWithLogRecord(Level.SEVERE); + verifyExceptionCaptured("some business exception", RuntimeException.class); + } + + @Test + void ignoreWarningWithLogRecord() { + logWithLogRecord(Level.WARNING); + verifyNoExceptionCaptured(); + } + + private static void logWithLogRecord(Level warning) { + LogRecord lr = new LogRecord(warning, "exception captured"); + lr.setThrown(new RuntimeException("some business exception")); + logger.log(lr); } } diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j1-plugin/src/test/java/co/elastic/apm/agent/log4j1/error/Log4j1LoggerErrorCapturingInstrumentationTest.java b/apm-agent-plugins/apm-logging-plugin/apm-log4j1-plugin/src/test/java/co/elastic/apm/agent/log4j1/error/Log4j1LoggerErrorCapturingInstrumentationTest.java index be7d8f67b7..d798f53178 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j1-plugin/src/test/java/co/elastic/apm/agent/log4j1/error/Log4j1LoggerErrorCapturingInstrumentationTest.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-log4j1-plugin/src/test/java/co/elastic/apm/agent/log4j1/error/Log4j1LoggerErrorCapturingInstrumentationTest.java @@ -30,12 +30,12 @@ class Log4j1LoggerErrorCapturingInstrumentationTest extends AbstractErrorLogging @Test void captureErrorExceptionWithStringMessage() { logger.error("exception captured", new RuntimeException("some business exception")); - verifyThatExceptionCaptured(1, "some business exception", RuntimeException.class); + verifyExceptionCaptured("some business exception", RuntimeException.class); } @Test void captureFatalException() { logger.fatal("exception captured", new RuntimeException("some business exception")); - verifyThatExceptionCaptured(1, "some business exception", RuntimeException.class); + verifyExceptionCaptured("some business exception", RuntimeException.class); } } diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/correlation/Log4j2_7PlusLogCorrelationHelper.java b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/correlation/Log4j2_7PlusLogCorrelationHelper.java index 1d5547c2a0..21adbf2723 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/correlation/Log4j2_7PlusLogCorrelationHelper.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/correlation/Log4j2_7PlusLogCorrelationHelper.java @@ -22,6 +22,7 @@ import co.elastic.apm.agent.impl.Tracer; import co.elastic.apm.agent.impl.error.ErrorCapture; import co.elastic.apm.agent.loginstr.correlation.AbstractLogCorrelationHelper; +import co.elastic.apm.agent.loginstr.correlation.CorrelationIdMapAdapter; import org.apache.logging.log4j.ThreadContext; import java.util.Map; diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/error/Log4j2LoggerErrorCapturingInstrumentation.java b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/error/Log4j2LoggerErrorCapturingInstrumentation.java index 7fa79c15db..195249c6a0 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/error/Log4j2LoggerErrorCapturingInstrumentation.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/error/Log4j2LoggerErrorCapturingInstrumentation.java @@ -43,7 +43,8 @@ public ElementMatcher getTypeMatcher() { @Override public ElementMatcher getMethodMatcher() { - return named("fatal").and(takesArgument(1, named("java.lang.Throwable"))).or(super.getMethodMatcher()); + return named("fatal").and(takesArgument(1, named("java.lang.Throwable"))) + .or(super.getMethodMatcher()); } @Override diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/error/Log4j2LoggerErrorCapturingInstrumentationTest.java b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/error/Log4j2LoggerErrorCapturingInstrumentationTest.java index c50bce8aed..08fd54465a 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/error/Log4j2LoggerErrorCapturingInstrumentationTest.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/error/Log4j2LoggerErrorCapturingInstrumentationTest.java @@ -37,18 +37,18 @@ public class Log4j2LoggerErrorCapturingInstrumentationTest extends AbstractError @Test void captureErrorExceptionWithStringMessage() { logger.error("exception captured", new RuntimeException("some business exception")); - verifyThatExceptionCaptured(1, "some business exception", RuntimeException.class); + verifyExceptionCaptured("some business exception", RuntimeException.class); } @Test void captureErrorExceptionWithMessageMessage() { logger.error(ParameterizedMessageFactory.INSTANCE.newMessage("exception captured with parameter {}", "foo"), new RuntimeException("some business exception")); - verifyThatExceptionCaptured(1, "some business exception", RuntimeException.class); + verifyExceptionCaptured("some business exception", RuntimeException.class); } @Test void captureFatalException() { logger.fatal("exception captured", new RuntimeException("some business exception")); - verifyThatExceptionCaptured(1, "some business exception", RuntimeException.class); + verifyExceptionCaptured("some business exception", RuntimeException.class); } } diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/correlation/CorrelationIdMapAdapter.java b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/main/java/co/elastic/apm/agent/loginstr/correlation/CorrelationIdMapAdapter.java similarity index 97% rename from apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/correlation/CorrelationIdMapAdapter.java rename to apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/main/java/co/elastic/apm/agent/loginstr/correlation/CorrelationIdMapAdapter.java index 6ef8abaeb9..c94d8ca2cc 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/correlation/CorrelationIdMapAdapter.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/main/java/co/elastic/apm/agent/loginstr/correlation/CorrelationIdMapAdapter.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.agent.log4j2.correlation; +package co.elastic.apm.agent.loginstr.correlation; import co.elastic.apm.agent.impl.GlobalTracer; import co.elastic.apm.agent.impl.Tracer; @@ -38,7 +38,7 @@ import static co.elastic.apm.agent.loginstr.correlation.AbstractLogCorrelationHelper.TRACE_ID_MDC_KEY; import static co.elastic.apm.agent.loginstr.correlation.AbstractLogCorrelationHelper.TRANSACTION_ID_MDC_KEY; -class CorrelationIdMapAdapter extends AbstractMap { +public class CorrelationIdMapAdapter extends AbstractMap { private static final CorrelationIdMapAdapter INSTANCE = new CorrelationIdMapAdapter(); private static final Set> ENTRY_SET = new TraceIdentifierEntrySet(); diff --git a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/test/java/co/elastic/apm/agent/jul/package-info.java b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/main/java/co/elastic/apm/agent/loginstr/correlation/package-info.java similarity index 94% rename from apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/test/java/co/elastic/apm/agent/jul/package-info.java rename to apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/main/java/co/elastic/apm/agent/loginstr/correlation/package-info.java index 3ef4e5109c..5e792ddb0e 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/src/test/java/co/elastic/apm/agent/jul/package-info.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/main/java/co/elastic/apm/agent/loginstr/correlation/package-info.java @@ -17,6 +17,6 @@ * under the License. */ @NonnullApi -package co.elastic.apm.agent.jul; +package co.elastic.apm.agent.loginstr.correlation; import co.elastic.apm.agent.sdk.NonnullApi; diff --git a/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/main/java/co/elastic/apm/agent/loginstr/error/AbstractLoggerErrorCapturingInstrumentation.java b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/main/java/co/elastic/apm/agent/loginstr/error/AbstractLoggerErrorCapturingInstrumentation.java index 96647d05ab..03e61b0007 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/main/java/co/elastic/apm/agent/loginstr/error/AbstractLoggerErrorCapturingInstrumentation.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/main/java/co/elastic/apm/agent/loginstr/error/AbstractLoggerErrorCapturingInstrumentation.java @@ -46,28 +46,17 @@ public String getAdviceClassName() { public static class LoggingAdvice { - private static final CallDepth callDepth = CallDepth.get(LoggingAdvice.class); + private static final LoggerErrorHelper helper = new LoggerErrorHelper(LoggingAdvice.class, tracer); @Nullable @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) public static Object logEnter(@Advice.Argument(1) Throwable exception, @Advice.Origin Class clazz) { - if (!callDepth.isNestedCallAndIncrement()) { - ErrorCapture error = tracer.captureException(exception, tracer.getActive(), clazz.getClassLoader()); - if (error != null) { - error.activate(); - } - return error; - } - return null; + return helper.enter(exception, clazz); } @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false) public static void logExit(@Advice.Enter @Nullable Object errorCaptureObj) { - callDepth.decrement(); - if (errorCaptureObj instanceof ErrorCapture) { - ErrorCapture error = (ErrorCapture) errorCaptureObj; - error.deactivate().end(); - } + helper.exit(errorCaptureObj); } } diff --git a/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/main/java/co/elastic/apm/agent/loginstr/error/LoggerErrorHelper.java b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/main/java/co/elastic/apm/agent/loginstr/error/LoggerErrorHelper.java new file mode 100644 index 0000000000..75b3507358 --- /dev/null +++ b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/main/java/co/elastic/apm/agent/loginstr/error/LoggerErrorHelper.java @@ -0,0 +1,70 @@ +/* + * 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. + */ +package co.elastic.apm.agent.loginstr.error; + +import co.elastic.apm.agent.impl.Tracer; +import co.elastic.apm.agent.impl.error.ErrorCapture; +import co.elastic.apm.agent.sdk.state.CallDepth; + +import javax.annotation.Nullable; + +public class LoggerErrorHelper { + + private final CallDepth callDepth; + private final Tracer tracer; + + public LoggerErrorHelper(Class adviceClass, Tracer tracer) { + this.callDepth = CallDepth.get(adviceClass); + this.tracer = tracer; + } + + /** + * Start error capture and make error active. Must be called even if `exception` is null for proper nested calls detection. + * + * @param exception exception to capture + * @param originClass origin class + * @return error capture, if any, {@literal null} for nested calls and when no exception provided. + */ + @Nullable + public Object enter(@Nullable Throwable exception, Class originClass) { + if (!callDepth.isNestedCallAndIncrement()) { + if (exception != null) { + ErrorCapture error = tracer.captureException(exception, tracer.getActive(), originClass.getClassLoader()); + if (error != null) { + error.activate(); + } + return error; + } + } + return null; + } + + /** + * End error capture and de-activate error. Must be called even if `exception` is null for proper nested calls detection + * + * @param errorCapture value returned by {@link #enter(Throwable, Class)} + */ + public void exit(@Nullable Object errorCapture) { + callDepth.decrement(); + if (errorCapture instanceof ErrorCapture) { + ErrorCapture error = (ErrorCapture) errorCapture; + error.deactivate().end(); + } + } +} diff --git a/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/test/java/co/elastic/apm/agent/loginstr/LoggingInstrumentationTest.java b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/test/java/co/elastic/apm/agent/loginstr/LoggingInstrumentationTest.java index 3bb978ba80..d996800a45 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/test/java/co/elastic/apm/agent/loginstr/LoggingInstrumentationTest.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/test/java/co/elastic/apm/agent/loginstr/LoggingInstrumentationTest.java @@ -323,7 +323,7 @@ private void verifyTracingMetadata(JsonNode ecsLogLineTree) { private void verifyErrorCaptureAndCorrelation(boolean isErrorLine, JsonNode ecsLogLineTree) { final JsonNode errorJsonNode = ecsLogLineTree.get(AbstractLogCorrelationHelper.ERROR_ID_MDC_KEY); if (isErrorLine) { - assertThat(errorJsonNode).isNotNull(); + assertThat(errorJsonNode).describedAs("missing error ID").isNotNull(); List errors = reporter.getErrors().stream() .filter(error -> errorJsonNode.textValue().equals(error.getTraceContext().getId().toString())) .collect(Collectors.toList()); @@ -385,10 +385,10 @@ private void verifyEcsFormat(String[] splitRawLogLine, JsonNode ecsLogLineTree) private void verifyLogCorrelation(JsonNode ecsLogLineTree, boolean isErrorLine) { if (isLogCorrelationSupported()) { JsonNode traceId = ecsLogLineTree.get(AbstractLogCorrelationHelper.TRACE_ID_MDC_KEY); - assertThat(traceId).withFailMessage("Logging correlation does not work as expected").isNotNull(); + assertThat(traceId).describedAs("Logging correlation does not work as expected: missing trace ID").isNotNull(); assertThat(traceId.textValue()).isEqualTo(transaction.getTraceContext().getTraceId().toString()); JsonNode transactionId = ecsLogLineTree.get(AbstractLogCorrelationHelper.TRANSACTION_ID_MDC_KEY); - assertThat(transactionId).withFailMessage("Logging correlation does not work as expected").isNotNull(); + assertThat(transactionId).describedAs("Logging correlation does not work as expected: missing transaction ID").isNotNull(); assertThat(transactionId.textValue()).isEqualTo(transaction.getTraceContext().getTransactionId().toString()); verifyErrorCaptureAndCorrelation(isErrorLine, ecsLogLineTree); } else { diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/correlation/CorrelationIdMapAdapterTest.java b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/test/java/co/elastic/apm/agent/loginstr/correlation/CorrelationIdMapAdapterTest.java similarity index 96% rename from apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/correlation/CorrelationIdMapAdapterTest.java rename to apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/test/java/co/elastic/apm/agent/loginstr/correlation/CorrelationIdMapAdapterTest.java index a9b2427162..638315755c 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/correlation/CorrelationIdMapAdapterTest.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/test/java/co/elastic/apm/agent/loginstr/correlation/CorrelationIdMapAdapterTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.agent.log4j2.correlation; +package co.elastic.apm.agent.loginstr.correlation; import co.elastic.apm.agent.MockTracer; import co.elastic.apm.agent.impl.ElasticApmTracer; @@ -31,7 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat; -class CorrelationIdMapAdapterTest { +public class CorrelationIdMapAdapterTest { private final ElasticApmTracer tracer = MockTracer.createRealTracer(); diff --git a/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/test/java/co/elastic/apm/agent/loginstr/error/AbstractErrorLoggingInstrumentationTest.java b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/test/java/co/elastic/apm/agent/loginstr/error/AbstractErrorLoggingInstrumentationTest.java index 24581c44ed..bfe0fbce37 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/test/java/co/elastic/apm/agent/loginstr/error/AbstractErrorLoggingInstrumentationTest.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/src/test/java/co/elastic/apm/agent/loginstr/error/AbstractErrorLoggingInstrumentationTest.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; public abstract class AbstractErrorLoggingInstrumentationTest extends AbstractInstrumentationTest { @@ -40,10 +41,15 @@ void endTransaction() { transaction.deactivate().end(); } - protected void verifyThatExceptionCaptured(int errorCount, String exceptionMessage, Class exceptionClass) { + protected void verifyExceptionCaptured(String exceptionMessage, Class exceptionClass) { reporter.awaitErrorCount(1); Throwable exception = reporter.getErrors().get(0).getException(); - assertEquals(exceptionMessage, exception.getMessage()); - assertEquals(exceptionClass, exception.getClass()); + assertThat(exception).isNotNull(); + assertThat(exceptionMessage).isEqualTo(exception.getMessage()); + assertThat(exceptionClass).isEqualTo(exception.getClass()); + } + + protected void verifyNoExceptionCaptured() { + assertThat(reporter.getErrors()).isEmpty(); } } diff --git a/apm-agent-plugins/apm-logging-plugin/apm-slf4j-plugin/src/test/java/co/elastic/apm/agent/slf4j/error/Slf4jLoggerErrorCapturingInstrumentationTest.java b/apm-agent-plugins/apm-logging-plugin/apm-slf4j-plugin/src/test/java/co/elastic/apm/agent/slf4j/error/Slf4jLoggerErrorCapturingInstrumentationTest.java index 5676b71b94..7225b4b5c0 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-slf4j-plugin/src/test/java/co/elastic/apm/agent/slf4j/error/Slf4jLoggerErrorCapturingInstrumentationTest.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-slf4j-plugin/src/test/java/co/elastic/apm/agent/slf4j/error/Slf4jLoggerErrorCapturingInstrumentationTest.java @@ -30,7 +30,7 @@ class Slf4jLoggerErrorCapturingInstrumentationTest extends AbstractErrorLoggingI @Test void captureException() { logger.error("exception captured", new RuntimeException("some business exception")); - verifyThatExceptionCaptured(1, "some business exception", RuntimeException.class); + verifyExceptionCaptured("some business exception", RuntimeException.class); } } diff --git a/apm-agent-plugins/apm-logging-plugin/pom.xml b/apm-agent-plugins/apm-logging-plugin/pom.xml index f38ecba0c6..8df94692e9 100644 --- a/apm-agent-plugins/apm-logging-plugin/pom.xml +++ b/apm-agent-plugins/apm-logging-plugin/pom.xml @@ -14,7 +14,6 @@ ${project.basedir}/../.. - 1.4.0 diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index 8f0cce5297..2169240ff4 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -762,7 +762,7 @@ you should add an additional entry to this list (make sure to also include the d ==== `enable_instrumentations` (added[1.28.0]) A list of instrumentations which should be selectively enabled. -Valid options are `annotations`, `annotations-capture-span`, `annotations-capture-transaction`, `annotations-traced`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `aws-sdk`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jboss-logging-correlation`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `jul-ecs`, `kafka`, `lettuce`, `log4j1-correlation`, `log4j1-ecs`, `log4j1-error`, `log4j2-correlation`, `log4j2-ecs`, `log4j2-error`, `logback-correlation`, `logback-ecs`, `logging`, `micrometer`, `mongodb`, `mongodb-client`, `okhttp`, `opentelemetry`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `servlet-service-name`, `servlet-version`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webclient`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`. +Valid options are `annotations`, `annotations-capture-span`, `annotations-capture-transaction`, `annotations-traced`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `aws-sdk`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jboss-logging-correlation`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `jul-ecs`, `jul-error`, `kafka`, `lettuce`, `log4j1-correlation`, `log4j1-ecs`, `log4j1-error`, `log4j2-correlation`, `log4j2-ecs`, `log4j2-error`, `logback-correlation`, `logback-ecs`, `logging`, `micrometer`, `mongodb`, `mongodb-client`, `okhttp`, `opentelemetry`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `servlet-service-name`, `servlet-version`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webclient`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`. When set to non-empty value, only listed instrumentations will be enabled if they are not disabled through <> or <>. When not set or empty (default), all instrumentations enabled by default will be enabled unless they are disabled through <> or <>. @@ -790,7 +790,7 @@ NOTE: Changing this value at runtime can slow down the application temporarily. ==== `disable_instrumentations` (added[1.0.0,Changing this value at runtime is possible since version 1.15.0]) A list of instrumentations which should be disabled. -Valid options are `annotations`, `annotations-capture-span`, `annotations-capture-transaction`, `annotations-traced`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `aws-sdk`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jboss-logging-correlation`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `jul-ecs`, `kafka`, `lettuce`, `log4j1-correlation`, `log4j1-ecs`, `log4j1-error`, `log4j2-correlation`, `log4j2-ecs`, `log4j2-error`, `logback-correlation`, `logback-ecs`, `logging`, `micrometer`, `mongodb`, `mongodb-client`, `okhttp`, `opentelemetry`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `servlet-service-name`, `servlet-version`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webclient`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`. +Valid options are `annotations`, `annotations-capture-span`, `annotations-capture-transaction`, `annotations-traced`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `aws-sdk`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jboss-logging-correlation`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `jul-ecs`, `jul-error`, `kafka`, `lettuce`, `log4j1-correlation`, `log4j1-ecs`, `log4j1-error`, `log4j2-correlation`, `log4j2-ecs`, `log4j2-error`, `logback-correlation`, `logback-ecs`, `logging`, `micrometer`, `mongodb`, `mongodb-client`, `okhttp`, `opentelemetry`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `servlet-service-name`, `servlet-version`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webclient`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`. For version `1.25.0` and later, use <> to enable experimental instrumentations. NOTE: Changing this value at runtime can slow down the application temporarily. @@ -3248,7 +3248,7 @@ Example: `5ms`. # sanitize_field_names=password,passwd,pwd,secret,*key,*token*,*session*,*credit*,*card*,*auth*,*principal*,set-cookie # A list of instrumentations which should be selectively enabled. -# Valid options are `annotations`, `annotations-capture-span`, `annotations-capture-transaction`, `annotations-traced`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `aws-sdk`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jboss-logging-correlation`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `jul-ecs`, `kafka`, `lettuce`, `log4j1-correlation`, `log4j1-ecs`, `log4j1-error`, `log4j2-correlation`, `log4j2-ecs`, `log4j2-error`, `logback-correlation`, `logback-ecs`, `logging`, `micrometer`, `mongodb`, `mongodb-client`, `okhttp`, `opentelemetry`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `servlet-service-name`, `servlet-version`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webclient`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`. +# Valid options are `annotations`, `annotations-capture-span`, `annotations-capture-transaction`, `annotations-traced`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `aws-sdk`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jboss-logging-correlation`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `jul-ecs`, `jul-error`, `kafka`, `lettuce`, `log4j1-correlation`, `log4j1-ecs`, `log4j1-error`, `log4j2-correlation`, `log4j2-ecs`, `log4j2-error`, `logback-correlation`, `logback-ecs`, `logging`, `micrometer`, `mongodb`, `mongodb-client`, `okhttp`, `opentelemetry`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `servlet-service-name`, `servlet-version`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webclient`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`. # When set to non-empty value, only listed instrumentations will be enabled if they are not disabled through <> or <>. # When not set or empty (default), all instrumentations enabled by default will be enabled unless they are disabled through <> or <>. # @@ -3261,7 +3261,7 @@ Example: `5ms`. # enable_instrumentations= # A list of instrumentations which should be disabled. -# Valid options are `annotations`, `annotations-capture-span`, `annotations-capture-transaction`, `annotations-traced`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `aws-sdk`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jboss-logging-correlation`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `jul-ecs`, `kafka`, `lettuce`, `log4j1-correlation`, `log4j1-ecs`, `log4j1-error`, `log4j2-correlation`, `log4j2-ecs`, `log4j2-error`, `logback-correlation`, `logback-ecs`, `logging`, `micrometer`, `mongodb`, `mongodb-client`, `okhttp`, `opentelemetry`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `servlet-service-name`, `servlet-version`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webclient`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`. +# Valid options are `annotations`, `annotations-capture-span`, `annotations-capture-transaction`, `annotations-traced`, `apache-commons-exec`, `apache-httpclient`, `asynchttpclient`, `aws-lambda`, `aws-sdk`, `cassandra`, `concurrent`, `dubbo`, `elasticsearch-restclient`, `exception-handler`, `executor`, `executor-collection`, `experimental`, `fork-join`, `grails`, `grpc`, `hibernate-search`, `http-client`, `jakarta-websocket`, `javalin`, `javax-websocket`, `jax-rs`, `jax-ws`, `jboss-logging-correlation`, `jdbc`, `jdk-httpclient`, `jdk-httpserver`, `jedis`, `jms`, `jsf`, `jul-ecs`, `jul-error`, `kafka`, `lettuce`, `log4j1-correlation`, `log4j1-ecs`, `log4j1-error`, `log4j2-correlation`, `log4j2-ecs`, `log4j2-error`, `logback-correlation`, `logback-ecs`, `logging`, `micrometer`, `mongodb`, `mongodb-client`, `okhttp`, `opentelemetry`, `opentracing`, `process`, `public-api`, `quartz`, `rabbitmq`, `reactor`, `redis`, `redisson`, `render`, `scala-future`, `scheduled`, `servlet-api`, `servlet-api-async`, `servlet-api-dispatch`, `servlet-input-stream`, `servlet-service-name`, `servlet-version`, `slf4j-error`, `sparkjava`, `spring-amqp`, `spring-mvc`, `spring-resttemplate`, `spring-service-name`, `spring-view-render`, `spring-webclient`, `spring-webflux`, `ssl-context`, `struts`, `timer-task`, `urlconnection`, `vertx`, `vertx-web`, `vertx-webclient`, `websocket`. # For version `1.25.0` and later, use <> to enable experimental instrumentations. # # NOTE: Changing this value at runtime can slow down the application temporarily. diff --git a/docs/supported-technologies.asciidoc b/docs/supported-technologies.asciidoc index 025d98316f..b58cb90b5d 100644 --- a/docs/supported-technologies.asciidoc +++ b/docs/supported-technologies.asciidoc @@ -606,8 +606,15 @@ JBoss LogManager - 1.30.0 |JUL - `java.util.logging` |All supported Java versions |When <> is enabled, logs will be automatically reformatted into -ECS-compatible format -|1.31.0 +ECS-compatible format. + +Trace correlation is only supported when used ECS logging library or with <> as JUL does +not provide any MDC implementation. +|Trace correlation - 1.35.0 + +ECS Reformatting - 1.31.0 + +Error capturing - 1.31.0 |=== diff --git a/pom.xml b/pom.xml index fb74d4ff9c..c8282f51a1 100644 --- a/pom.xml +++ b/pom.xml @@ -114,7 +114,7 @@ (https://nvd.nist.gov/vuln/detail/CVE-2020-9488#vulnCurrentDescriptionTitle), the SMTP appender is excluded from the build and not packaged into the agent artifacts --> 2.12.4 - 1.4.0 + 1.5.0 5.0.15.RELEASE 2.2.2.RELEASE 9.4.11.v20180605