Remove logging instrumentation (#803)

This commit is contained in:
Trask Stalnaker 2020-07-27 08:56:25 -07:00 committed by GitHub
parent 91f8e8c199
commit 6508ffc889
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 3 additions and 1395 deletions

View File

@ -171,7 +171,6 @@ provide the path to a JAR file including an SPI implementation using the system
| [Hibernate](https://github.com/hibernate/hibernate-orm) | 3.3+ |
| [HttpURLConnection](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpURLConnection.html) | Java 7+ |
| [Hystrix](https://github.com/Netflix/Hystrix) | 1.4+ |
| [java.util.logging](https://docs.oracle.com/en/java/javase/11/docs/api/java.logging/java/util/logging/package-summary.html) | Java 7+ |
| [JAX-RS](https://javaee.github.io/javaee-spec/javadocs/javax/ws/rs/package-summary.html) | 0.5+ |
| [JAX-RS Client](https://javaee.github.io/javaee-spec/javadocs/javax/ws/rs/client/package-summary.html) | 2.0+ |
| [JDBC](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/package-summary.html) | Java 7+ |
@ -183,8 +182,6 @@ provide the path to a JAR file including an SPI implementation using the system
| [khttp](https://khttp.readthedocs.io) | 0.1+ |
| [Kubernetes Client](https://github.com/kubernetes-client/java) | 7.0+ |
| [Lettuce](https://github.com/lettuce-io/lettuce-core) | 4.0+ |
| [Log4j](https://logging.apache.org/log4j/2.x/) | 1.1+ |
| [Logback](https://github.com/qos-ch/logback) | 1.0+ |
| [MongoDB Drivers](https://mongodb.github.io/mongo-java-driver/) | 3.3+ |
| [Netty](https://github.com/netty/netty) | 3.8+ |
| [OkHttp](https://github.com/square/okhttp/) | 3.0+ |

View File

@ -46,34 +46,8 @@ configurations {
implementation.exclude group: 'io.opentelemetry', module: 'opentelemetry-api'
}
// need to perform shading in two steps in order to avoid shading java.util.logging.Logger
// in the java-util-logging instrumentation since that instrumentation needs to
// reference unshaded java.util.logging.Logger
// (java.util.logging.Logger shading is not needed in any of the instrumentation modules,
// but it is needed for the dependencies, e.g. guava, which use java.util.logging.Logger)
task shadowJarStep1(type: com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
archiveClassifier = 'step1'
destinationDirectory = file("${project.buildDir}/step1")
configurations = [project.configurations.runtimeClasspath]
dependencies {
exclude(project(':instrumentation:java-util-logging'))
}
// rewrite dependencies calling Logger.getLogger
relocate 'java.util.logging.Logger', 'io.opentelemetry.auto.bootstrap.PatchLogger'
}
shadowJar {
dependsOn shadowJarStep1
from {
zipTree(shadowJarStep1.archiveFile)
}
mergeServiceFiles()
exclude '**/module-info.class'
@ -87,6 +61,9 @@ shadowJar {
exclude(project(':auto-bootstrap'))
}
// rewrite dependencies calling Logger.getLogger
relocate 'java.util.logging.Logger', 'io.opentelemetry.auto.bootstrap.PatchLogger'
// relocate OpenTelemetry API usage
relocate "io.opentelemetry.OpenTelemetry", "io.opentelemetry.auto.shaded.io.opentelemetry.OpenTelemetry"
relocate "io.opentelemetry.common", "io.opentelemetry.auto.shaded.io.opentelemetry.common"

View File

@ -1,22 +0,0 @@
apply from: "$rootDir/gradle/instrumentation.gradle"
apply plugin: 'org.unbroken-dome.test-sets'
muzzle {
pass {
group = 'org.jboss.logmanager'
module = 'jboss-logmanager'
versions = '(,)'
}
}
testSets {
latestDepTest {
dirName = 'test'
}
}
dependencies {
testImplementation 'org.jboss.logmanager:jboss-logmanager:1.0.0.GA'
latestDepTestImplementation 'org.jboss.logmanager:jboss-logmanager:+'
}

View File

@ -1,114 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.auto.instrumentation.jul;
import io.opentelemetry.OpenTelemetry;
import io.opentelemetry.auto.config.Config;
import io.opentelemetry.trace.Span;
import io.opentelemetry.trace.Tracer;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.slf4j.LoggerFactory;
public class JavaUtilLoggingSpans {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(JavaUtilLoggingSpans.class);
private static final Tracer TRACER =
OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.java-util-logging");
private static final Formatter FORMATTER = new AccessibleFormatter();
public static void capture(final Logger logger, final LogRecord logRecord) {
Level level = logRecord.getLevel();
if (!logger.isLoggable(level)) {
// this is already checked in most cases, except if Logger.log(LogRecord) was called directly
return;
}
if (level.intValue() < getThreshold().intValue()) {
return;
}
Throwable t = logRecord.getThrown();
Span span =
TRACER
.spanBuilder("log.message")
.setAttribute("message", FORMATTER.formatMessage(logRecord))
.setAttribute("level", level.getName())
.setAttribute("loggerName", logger.getName())
.startSpan();
if (t != null) {
span.setAttribute("error.stack", toString(t));
}
span.end();
}
private static String toString(final Throwable t) {
StringWriter out = new StringWriter();
t.printStackTrace(new PrintWriter(out));
return out.toString();
}
private static Level getThreshold() {
String level = Config.get().getExperimentalLogCaptureThreshold();
if (level == null) {
return Level.OFF;
}
switch (level) {
case "OFF":
return Level.OFF;
case "FATAL":
case "ERROR":
case "SEVERE":
return Level.SEVERE;
case "WARN":
case "WARNING":
return Level.WARNING;
case "INFO":
return Level.INFO;
case "CONFIG":
return Level.CONFIG;
case "DEBUG":
case "FINE":
return Level.FINE;
case "FINER":
return Level.FINER;
case "TRACE":
case "FINEST":
return Level.FINEST;
case "ALL":
return Level.ALL;
default:
log.error("unexpected value for {}: {}", Config.EXPERIMENTAL_LOG_CAPTURE_THRESHOLD, level);
return Level.OFF;
}
}
// this is just needed for calling formatMessage in abstract super class
public static class AccessibleFormatter extends Formatter {
@Override
public String format(final LogRecord record) {
throw new UnsupportedOperationException();
}
}
}

View File

@ -1,99 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.auto.instrumentation.jul;
import static io.opentelemetry.auto.tooling.bytebuddy.matcher.AgentElementMatchers.extendsClass;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import com.google.auto.service.AutoService;
import io.opentelemetry.auto.bootstrap.CallDepthThreadLocalMap;
import io.opentelemetry.auto.bootstrap.instrumentation.logging.LoggerDepth;
import io.opentelemetry.auto.tooling.Instrumenter;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(Instrumenter.class)
public class JavaUtilLoggingSpansInstrumentation extends Instrumenter.Default {
public JavaUtilLoggingSpansInstrumentation() {
super("java-util-logging");
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return extendsClass(named("java.util.logging.Logger"));
}
@Override
public String[] helperClassNames() {
return new String[] {
packageName + ".JavaUtilLoggingSpans",
packageName + ".JavaUtilLoggingSpans$AccessibleFormatter"
};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
transformers.put(
isMethod()
.and(isPublic())
.and(named("log"))
.and(takesArguments(1))
.and(takesArgument(0, named("java.util.logging.LogRecord"))),
JavaUtilLoggingSpansInstrumentation.class.getName() + "$LogAdvice");
transformers.put(
isMethod()
.and(isPublic())
.and(named("logRaw"))
.and(takesArguments(1))
.and(takesArgument(0, named("org.jboss.logmanager.ExtLogRecord"))),
JavaUtilLoggingSpansInstrumentation.class.getName() + "$LogAdvice");
return transformers;
}
public static class LogAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static boolean methodEnter(
@Advice.This final Logger logger, @Advice.Argument(0) final LogRecord logRecord) {
// need to track call depth across all loggers in order to avoid double capture when one
// logging framework delegates to another
boolean topLevel = CallDepthThreadLocalMap.incrementCallDepth(LoggerDepth.class) == 0;
if (topLevel) {
JavaUtilLoggingSpans.capture(logger, logRecord);
}
return topLevel;
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit(@Advice.Enter final boolean topLevel) {
if (topLevel) {
CallDepthThreadLocalMap.reset(LoggerDepth.class);
}
}
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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.
*/
import io.opentelemetry.auto.test.log.events.LogEventsTestBase
import org.jboss.logmanager.LogContext
class JBossJavaUtilLoggingEventTest extends LogEventsTestBase {
@Override
Object createLogger(String name) {
LogContext.create().getLogger(name)
}
String warn() {
return "warning"
}
String error() {
return "severe"
}
}

View File

@ -1,35 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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.
*/
import io.opentelemetry.auto.test.log.events.LogEventsTestBase
import java.util.logging.Logger
class JavaUtilLoggingSpansTest extends LogEventsTestBase {
@Override
Object createLogger(String name) {
Logger.getLogger(name)
}
String warn() {
return "warning"
}
String error() {
return "severe"
}
}

View File

@ -1,34 +0,0 @@
apply from: "$rootDir/gradle/instrumentation.gradle"
apply plugin: 'org.unbroken-dome.test-sets'
muzzle {
pass {
group = 'log4j'
module = 'log4j'
versions = '(,)'
}
}
testSets {
latestDepTest {
dirName = 'test'
}
}
configurations {
// In order to test the real log4j library we need to remove the log4j transitive
// dependency 'log4j-over-slf4j' brought in by :testing-common which would shadow
// the log4j module under test using a proxy to slf4j instead.
testImplementation.exclude group: 'org.slf4j', module: 'log4j-over-slf4j'
// See: https://stackoverflow.com/a/9047963/2749853
testImplementation.exclude group: 'javax.jms', module: 'jms'
}
dependencies {
compileOnly group: 'log4j', name: 'log4j', version: '1.2.16'
testImplementation group: 'log4j', name: 'log4j', version: '1.2.16'
latestDepTestImplementation group: 'log4j', name: 'log4j', version: '+'
}

View File

@ -1,80 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.auto.instrumentation.log4j.v1_1;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.named;
import io.opentelemetry.auto.config.Config;
import io.opentelemetry.auto.tooling.Instrumenter;
import java.lang.reflect.Method;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
// FIXME this instrumentation relied on scope listener
// @AutoService(Instrumenter.class)
public class Log4jMDCInstrumentation extends Instrumenter.Default {
public Log4jMDCInstrumentation() {
super("log4j");
}
@Override
protected boolean defaultEnabled() {
return Config.get().isLogInjectionEnabled();
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return named("org.apache.log4j.MDC");
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
isConstructor(), Log4jMDCInstrumentation.class.getName() + "$MDCContextAdvice");
}
@Override
public String[] helperClassNames() {
return new String[] {"io.opentelemetry.auto.tooling.log.LogContextScopeListener"};
}
public static class MDCContextAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void mdcClassInitialized(@Advice.This final Object instance) {
if (instance == null) {
return;
}
try {
Class<?> mdcClass = instance.getClass();
Method putMethod = mdcClass.getMethod("put", String.class, Object.class);
Method removeMethod = mdcClass.getMethod("remove", String.class);
// FIXME this instrumentation relied on scope listener
// GlobalTracer.get().addScopeListener(new LogContextScopeListener(putMethod,
// removeMethod));
} catch (final NoSuchMethodException e) {
org.slf4j.LoggerFactory.getLogger(instance.getClass())
.debug("Failed to add log4j ThreadContext span listener", e);
}
}
}
}

View File

@ -1,104 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.auto.instrumentation.log4j.v1_1;
import io.opentelemetry.OpenTelemetry;
import io.opentelemetry.auto.config.Config;
import io.opentelemetry.trace.Span;
import io.opentelemetry.trace.Tracer;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.apache.log4j.Category;
import org.apache.log4j.Priority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Log4jSpans {
private static final Logger log = LoggerFactory.getLogger(Log4jSpans.class);
private static final Tracer TRACER =
OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.log4j-1.1");
// these constants are copied from org.apache.log4j.Priority and org.apache.log4j.Level because
// Level was only introduced in 1.2, and then Level.TRACE was only introduced in 1.2.12
private static final int OFF_INT = Integer.MAX_VALUE;
private static final int FATAL_INT = 50000;
private static final int ERROR_INT = 40000;
private static final int WARN_INT = 30000;
private static final int INFO_INT = 20000;
private static final int DEBUG_INT = 10000;
private static final int TRACE_INT = 5000;
private static final int ALL_INT = Integer.MIN_VALUE;
public static void capture(
final Category logger, final Priority level, final Object message, final Throwable t) {
if (level.toInt() < getThreshold()) {
return;
}
Span span = TRACER.spanBuilder("log.message").startSpan();
span.setAttribute("message", String.valueOf(message));
span.setAttribute("level", level.toString());
span.setAttribute("loggerName", logger.getName());
if (t != null) {
span.setAttribute("error.stack", toString(t));
}
span.end();
}
private static String toString(final Throwable t) {
StringWriter out = new StringWriter();
t.printStackTrace(new PrintWriter(out));
return out.toString();
}
private static int getThreshold() {
String level = Config.get().getExperimentalLogCaptureThreshold();
if (level == null) {
return OFF_INT;
}
switch (level) {
case "OFF":
return OFF_INT;
case "FATAL":
return FATAL_INT;
case "ERROR":
case "SEVERE":
return ERROR_INT;
case "WARN":
case "WARNING":
return WARN_INT;
case "INFO":
return INFO_INT;
case "CONFIG":
case "DEBUG":
case "FINE":
case "FINER":
return DEBUG_INT;
case "TRACE":
case "FINEST":
return TRACE_INT;
case "ALL":
return ALL_INT;
default:
log.error("unexpected value for {}: {}", Config.EXPERIMENTAL_LOG_CAPTURE_THRESHOLD, level);
return OFF_INT;
}
}
}

View File

@ -1,94 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.auto.instrumentation.log4j.v1_1;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isProtected;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import com.google.auto.service.AutoService;
import io.opentelemetry.auto.bootstrap.CallDepthThreadLocalMap;
import io.opentelemetry.auto.bootstrap.instrumentation.logging.LoggerDepth;
import io.opentelemetry.auto.tooling.Instrumenter;
import java.util.HashMap;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.log4j.Category;
import org.apache.log4j.Priority;
@AutoService(Instrumenter.class)
public class Log4jSpansInstrumentation extends Instrumenter.Default {
public Log4jSpansInstrumentation() {
super("log4j");
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return named("org.apache.log4j.Category");
}
@Override
public String[] helperClassNames() {
return new String[] {packageName + ".Log4jSpans"};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
transformers.put(
isMethod()
.and(isProtected())
.and(named("forcedLog"))
.and(takesArguments(4))
.and(takesArgument(0, named("java.lang.String")))
.and(takesArgument(1, named("org.apache.log4j.Priority")))
.and(takesArgument(2, named("java.lang.Object")))
.and(takesArgument(3, named("java.lang.Throwable"))),
Log4jSpansInstrumentation.class.getName() + "$ForcedLogAdvice");
return transformers;
}
public static class ForcedLogAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static boolean methodEnter(
@Advice.This final Category logger,
@Advice.Argument(1) final Priority level,
@Advice.Argument(2) final Object message,
@Advice.Argument(3) final Throwable t) {
// need to track call depth across all loggers to avoid double capture when one logging
// framework delegates to another
boolean topLevel = CallDepthThreadLocalMap.incrementCallDepth(LoggerDepth.class) == 0;
if (topLevel) {
Log4jSpans.capture(logger, level, message, t);
}
return topLevel;
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit(@Advice.Enter final boolean topLevel) {
if (topLevel) {
CallDepthThreadLocalMap.reset(LoggerDepth.class);
}
}
}
}

View File

@ -1,52 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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.
*/
import io.opentelemetry.auto.test.log.injection.LogContextInjectionTestBase
import org.apache.log4j.MDC
import org.junit.Ignore
import spock.lang.Requires
/**
It looks like log4j1 is broken for any java version that doesn't have '.' in version number
- it thinks it runs on ancient version. For example this happens for java13.
See {@link org.apache.log4j.helpers.Loader}.
*/
// FIXME this instrumentation relied on scope listener
@Ignore
@Requires({ System.getProperty("java.version").contains(".") })
class Log4jMDCTest extends LogContextInjectionTestBase {
@Override
def put(String key, Object value) {
return MDC.put(key, value)
}
@Override
def get(String key) {
return MDC.get(key)
}
@Override
def remove(String key) {
MDC.context
return MDC.remove(key)
}
@Override
def clear() {
return MDC.clear()
}
}

View File

@ -1,26 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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.
*/
import io.opentelemetry.auto.test.log.events.LogEventsTestBase
import org.apache.log4j.Logger
class Log4jSpansTest extends LogEventsTestBase {
@Override
Object createLogger(String name) {
Logger.getLogger(name)
}
}

View File

@ -1,3 +0,0 @@
log4j.rootLogger=WARN, CONSOLE
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.SimpleLayout

View File

@ -1,40 +0,0 @@
apply from: "$rootDir/gradle/instrumentation.gradle"
apply plugin: 'org.unbroken-dome.test-sets'
muzzle {
pass {
group = 'org.apache.logging.log4j'
module = 'log4j-core'
versions = '(,)'
}
pass {
group = 'org.apache.logging.log4j'
module = 'log4j-api'
versions = '(,)'
}
}
testSets {
latestDepTest {
dirName = 'test'
}
}
configurations {
// In order to test the real log4j library we need to remove the log4j transitive
// dependency 'log4j-over-slf4j' brought in by :testing-common which would shadow
// the log4j module under test using a proxy to slf4j instead.
testImplementation.exclude group: 'org.slf4j', module: 'log4j-over-slf4j'
}
dependencies {
compileOnly group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.0'
compileOnly group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.0'
testImplementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.0'
testImplementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.0'
latestDepTestImplementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '+'
latestDepTestImplementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '+'
}

View File

@ -1,96 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.auto.instrumentation.log4j.v2_0;
import io.opentelemetry.OpenTelemetry;
import io.opentelemetry.auto.config.Config;
import io.opentelemetry.trace.Span;
import io.opentelemetry.trace.Tracer;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.Message;
import org.slf4j.LoggerFactory;
public class Log4jSpans {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(Log4jSpans.class);
private static final Tracer TRACER =
OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.log4j-2.0");
public static void capture(
final Logger logger, final Level level, final Message message, final Throwable t) {
if (level.intLevel() > getThreshold().intLevel()) {
return;
}
Span span =
TRACER
.spanBuilder("log.message")
.setAttribute("message", message.getFormattedMessage())
.setAttribute("level", level.toString())
.setAttribute("loggerName", logger.getName())
.startSpan();
if (t != null) {
span.setAttribute("error.stack", toString(t));
}
span.end();
}
private static String toString(final Throwable t) {
StringWriter out = new StringWriter();
t.printStackTrace(new PrintWriter(out));
return out.toString();
}
private static Level getThreshold() {
String level = Config.get().getExperimentalLogCaptureThreshold();
if (level == null) {
return Level.OFF;
}
switch (level) {
case "OFF":
return Level.OFF;
case "FATAL":
return Level.FATAL;
case "ERROR":
case "SEVERE":
return Level.ERROR;
case "WARN":
case "WARNING":
return Level.WARN;
case "INFO":
return Level.INFO;
case "CONFIG":
case "DEBUG":
case "FINE":
case "FINER":
return Level.DEBUG;
case "TRACE":
case "FINEST":
return Level.TRACE;
case "ALL":
return Level.ALL;
default:
log.error("unexpected value for {}: {}", Config.EXPERIMENTAL_LOG_CAPTURE_THRESHOLD, level);
return Level.OFF;
}
}
}

View File

@ -1,136 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.auto.instrumentation.log4j.v2_0;
import static io.opentelemetry.auto.tooling.bytebuddy.matcher.AgentElementMatchers.extendsClass;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isProtected;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import com.google.auto.service.AutoService;
import io.opentelemetry.auto.bootstrap.CallDepthThreadLocalMap;
import io.opentelemetry.auto.bootstrap.instrumentation.logging.LoggerDepth;
import io.opentelemetry.auto.tooling.Instrumenter;
import java.util.HashMap;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.Message;
@AutoService(Instrumenter.class)
public class Log4jSpansInstrumentation extends Instrumenter.Default {
public Log4jSpansInstrumentation() {
super("log4j");
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return extendsClass(named("org.apache.logging.log4j.spi.AbstractLogger"));
}
@Override
public String[] helperClassNames() {
return new String[] {packageName + ".Log4jSpans"};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
transformers.put(
isMethod()
.and(isPublic())
.and(named("logMessage"))
.and(takesArguments(5))
.and(takesArgument(0, named("java.lang.String")))
.and(takesArgument(1, named("org.apache.logging.log4j.Level")))
.and(takesArgument(2, named("org.apache.logging.log4j.Marker")))
.and(takesArgument(3, named("org.apache.logging.log4j.message.Message")))
.and(takesArgument(4, named("java.lang.Throwable"))),
Log4jSpansInstrumentation.class.getName() + "$LogMessageAdvice");
// log4j 2.12.1 introduced and started using this new log() method
transformers.put(
isMethod()
.and(isProtected())
.and(named("log"))
.and(takesArguments(6))
.and(takesArgument(0, named("org.apache.logging.log4j.Level")))
.and(takesArgument(1, named("org.apache.logging.log4j.Marker")))
.and(takesArgument(2, named("java.lang.String")))
.and(takesArgument(3, named("java.lang.StackTraceElement")))
.and(takesArgument(4, named("org.apache.logging.log4j.message.Message")))
.and(takesArgument(5, named("java.lang.Throwable"))),
Log4jSpansInstrumentation.class.getName() + "$LogAdvice");
return transformers;
}
public static class LogMessageAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static boolean methodEnter(
@Advice.This final Logger logger,
@Advice.Argument(1) final Level level,
@Advice.Argument(3) final Message message,
@Advice.Argument(4) final Throwable t) {
// need to track call depth across all loggers in order to avoid double capture when one
// logging framework delegates to another
boolean topLevel = CallDepthThreadLocalMap.incrementCallDepth(LoggerDepth.class) == 0;
if (topLevel) {
Log4jSpans.capture(logger, level, message, t);
}
return topLevel;
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit(@Advice.Enter final boolean topLevel) {
if (topLevel) {
CallDepthThreadLocalMap.reset(LoggerDepth.class);
}
}
}
public static class LogAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static boolean methodEnter(
@Advice.This final Logger logger,
@Advice.Argument(0) final Level level,
@Advice.Argument(4) final Message message,
@Advice.Argument(5) final Throwable t) {
// need to track call depth across all loggers in order to avoid double capture when one
// logging framework delegates to another
boolean topLevel = CallDepthThreadLocalMap.incrementCallDepth(LoggerDepth.class) == 0;
if (topLevel) {
Log4jSpans.capture(logger, level, message, t);
}
return topLevel;
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit(@Advice.Enter final boolean topLevel) {
if (topLevel) {
CallDepthThreadLocalMap.reset(LoggerDepth.class);
}
}
}
}

View File

@ -1,78 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.auto.instrumentation.log4j.v2_0;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isTypeInitializer;
import static net.bytebuddy.matcher.ElementMatchers.named;
import io.opentelemetry.auto.config.Config;
import io.opentelemetry.auto.tooling.Instrumenter;
import io.opentelemetry.auto.tooling.log.LogContextScopeListener;
import java.lang.reflect.Method;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
// FIXME this instrumentation relied on scope listener
// @AutoService(Instrumenter.class)
public class ThreadContextInstrumentation extends Instrumenter.Default {
public static final String MDC_INSTRUMENTATION_NAME = "log4j";
public ThreadContextInstrumentation() {
super(MDC_INSTRUMENTATION_NAME);
}
@Override
protected boolean defaultEnabled() {
return Config.get().isLogInjectionEnabled();
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return named("org.apache.logging.log4j.ThreadContext");
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
isTypeInitializer(), ThreadContextInstrumentation.class.getName() + "$ThreadContextAdvice");
}
@Override
public String[] helperClassNames() {
return new String[] {LogContextScopeListener.class.getName()};
}
public static class ThreadContextAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void mdcClassInitialized(@Advice.Origin final Class threadClass) {
try {
Method putMethod = threadClass.getMethod("put", String.class, String.class);
Method removeMethod = threadClass.getMethod("remove", String.class);
// FIXME this instrumentation relied on scope listener
// GlobalTracer.get().addScopeListener(new LogContextScopeListener(putMethod,
// removeMethod));
} catch (final NoSuchMethodException e) {
org.slf4j.LoggerFactory.getLogger(threadClass)
.debug("Failed to add log4j ThreadContext span listener", e);
}
}
}
}

View File

@ -1,32 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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.
*/
import io.opentelemetry.auto.test.log.events.LogEventsTestBase
import org.apache.logging.log4j.LogManager
class Log4jSpansTest extends LogEventsTestBase {
static {
// need to initialize logger before tests to flush out init warning message:
// "Unable to instantiate org.fusesource.jansi.WindowsAnsiOutputStream"
LogManager.getLogger(Log4jSpansTest)
}
@Override
Object createLogger(String name) {
LogManager.getLogger(name)
}
}

View File

@ -1,44 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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.
*/
import io.opentelemetry.auto.test.log.injection.LogContextInjectionTestBase
import org.apache.logging.log4j.ThreadContext
import org.junit.Ignore
// FIXME this instrumentation relied on scope listener
@Ignore
class Log4jThreadContextTest extends LogContextInjectionTestBase {
@Override
def put(String key, Object value) {
return ThreadContext.put(key, value as String)
}
@Override
def get(String key) {
return ThreadContext.get(key)
}
@Override
def remove(String key) {
return ThreadContext.remove(key)
}
@Override
def clear() {
return ThreadContext.clearAll()
}
}

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configuration>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="warn">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

View File

@ -1,7 +0,0 @@
apply from: "$rootDir/gradle/instrumentation.gradle"
dependencies {
compileOnly group: 'ch.qos.logback', name: 'logback-classic', version: '0.9.16'
testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '0.9.16'
}

View File

@ -1,105 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.auto.instrumentation.logback;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.ThrowableProxy;
import io.opentelemetry.OpenTelemetry;
import io.opentelemetry.auto.config.Config;
import io.opentelemetry.trace.Span;
import io.opentelemetry.trace.Tracer;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogbackSpans {
private static final Logger log = LoggerFactory.getLogger(LogbackSpans.class);
private static final Tracer TRACER =
OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.logback-1.0");
public static void capture(final ILoggingEvent event) {
Level level = event.getLevel();
if (level.toInt() < getThreshold().toInt()) {
// this needs to be configurable
return;
}
Object throwableProxy = event.getThrowableProxy();
Throwable t = null;
if (throwableProxy instanceof ThrowableProxy) {
// there is only one other subclass of ch.qos.logback.classic.spi.IThrowableProxy
// and it is only used for logging exceptions over the wire
t = ((ThrowableProxy) throwableProxy).getThrowable();
}
Span span =
TRACER
.spanBuilder("log.message")
.setAttribute("message", event.getFormattedMessage())
.setAttribute("level", level.toString())
.setAttribute("loggerName", event.getLoggerName())
.startSpan();
if (t != null) {
span.setAttribute("error.stack", toString(t));
}
span.end();
}
private static String toString(final Throwable t) {
StringWriter out = new StringWriter();
t.printStackTrace(new PrintWriter(out));
return out.toString();
}
private static Level getThreshold() {
String level = Config.get().getExperimentalLogCaptureThreshold();
if (level == null) {
return Level.OFF;
}
switch (level) {
case "OFF":
return Level.OFF;
case "FATAL":
case "ERROR":
case "SEVERE":
return Level.ERROR;
case "WARN":
case "WARNING":
return Level.WARN;
case "INFO":
return Level.INFO;
case "CONFIG":
case "DEBUG":
case "FINE":
case "FINER":
return Level.DEBUG;
case "TRACE":
case "FINEST":
return Level.TRACE;
case "ALL":
return Level.ALL;
default:
log.error("unexpected value for {}: {}", Config.EXPERIMENTAL_LOG_CAPTURE_THRESHOLD, level);
return Level.OFF;
}
}
}

View File

@ -1,86 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.auto.instrumentation.logback;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import ch.qos.logback.classic.spi.ILoggingEvent;
import com.google.auto.service.AutoService;
import io.opentelemetry.auto.bootstrap.CallDepthThreadLocalMap;
import io.opentelemetry.auto.bootstrap.instrumentation.logging.LoggerDepth;
import io.opentelemetry.auto.tooling.Instrumenter;
import java.util.HashMap;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(Instrumenter.class)
public class LogbackSpansInstrumentation extends Instrumenter.Default {
public LogbackSpansInstrumentation() {
super("logback");
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return named("ch.qos.logback.classic.Logger");
}
@Override
public String[] helperClassNames() {
return new String[] {packageName + ".LogbackSpans"};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
transformers.put(
isMethod()
.and(isPublic())
.and(named("callAppenders"))
.and(takesArguments(1))
.and(takesArgument(0, named("ch.qos.logback.classic.spi.ILoggingEvent"))),
LogbackSpansInstrumentation.class.getName() + "$CallAppendersAdvice");
return transformers;
}
public static class CallAppendersAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static boolean methodEnter(@Advice.Argument(0) final ILoggingEvent event) {
// need to track call depth across all loggers in order to avoid double capture when one
// logging framework delegates to another
boolean topLevel = CallDepthThreadLocalMap.incrementCallDepth(LoggerDepth.class) == 0;
if (topLevel) {
LogbackSpans.capture(event);
}
return topLevel;
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit(@Advice.Enter final boolean topLevel) {
if (topLevel) {
CallDepthThreadLocalMap.reset(LoggerDepth.class);
}
}
}
}

View File

@ -1,26 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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.
*/
import ch.qos.logback.classic.LoggerContext
import io.opentelemetry.auto.test.log.events.LogEventsTestBase
class LogbackSpansTest extends LogEventsTestBase {
@Override
Object createLogger(String name) {
new LoggerContext().getLogger(name)
}
}

View File

@ -22,7 +22,6 @@ rootProject.name = 'opentelemetry-java-instrumentation'
include ':opentelemetry-javaagent'
include ':opentelemetry-sdk-shaded-for-testing'
include ':opentelemetry-api-beta-shaded-for-instrumenting'
include ':logback-shaded-for-instrumenting'
include ':agent-bootstrap'
include ':agent-tooling'
include ':load-generator'
@ -90,7 +89,6 @@ include ':instrumentation:java-concurrent:kotlin-testing'
include ':instrumentation:java-concurrent:scala-testing'
include ':instrumentation:java-concurrent:akka-testing'
include ':instrumentation:java-concurrent:akka-2.5-testing'
include ':instrumentation:java-util-logging'
include ':instrumentation:jaxrs:jaxrs-1.0'
include ':instrumentation:jaxrs:jaxrs-2.0'
include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-jersey-2.0'
@ -113,10 +111,7 @@ include ':instrumentation:kubernetes-client-7.0'
include ':instrumentation:lettuce:lettuce-4.0'
include ':instrumentation:lettuce:lettuce-5.0'
include ':instrumentation:lettuce:lettuce-5.1'
include ':instrumentation:log4j:log4j-1.1'
include ':instrumentation:log4j:log4j-2.0'
include ':instrumentation:log4j:log4j-2.13.2:library'
include ':instrumentation:logback-1.0'
include ':instrumentation:mongo:mongo-3.1'
include ':instrumentation:mongo:mongo-3.7'
include ':instrumentation:mongo:mongo-async-3.3'