diff --git a/README.md b/README.md
index 3decd6610169..afd57f5b907a 100644
--- a/README.md
+++ b/README.md
@@ -203,6 +203,7 @@ 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+ |
+| [Logback](http://logback.qos.ch/) | 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+ |
diff --git a/instrumentation/logback/logback-1.0.0/auto/logback-1.0.0-auto.gradle b/instrumentation/logback/logback-1.0.0/auto/logback-1.0.0-auto.gradle
new file mode 100644
index 000000000000..395579f25944
--- /dev/null
+++ b/instrumentation/logback/logback-1.0.0/auto/logback-1.0.0-auto.gradle
@@ -0,0 +1,20 @@
+apply from: "$rootDir/gradle/instrumentation.gradle"
+
+muzzle {
+ pass {
+ group = "ch.qos.logback"
+ module = "logback-classic"
+ versions = "[1.0.0,1.2.3]"
+ }
+}
+
+dependencies {
+ implementation project(':instrumentation:logback:logback-1.0.0:library')
+
+ library group: 'ch.qos.logback', name: 'logback-classic', version: '1.0.0'
+
+ testImplementation project(':instrumentation:logback:logback-1.0.0:testing')
+
+ // 1.3+ contains breaking changes, check back after it stabilizes.
+ latestDepTestLibrary group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.+'
+}
diff --git a/instrumentation/logback/logback-1.0.0/auto/src/main/java/io/opentelemetry/instrumentation/auto/logback/v1_0_0/LogbackInstrumentation.java b/instrumentation/logback/logback-1.0.0/auto/src/main/java/io/opentelemetry/instrumentation/auto/logback/v1_0_0/LogbackInstrumentation.java
new file mode 100644
index 000000000000..e403cd5196ba
--- /dev/null
+++ b/instrumentation/logback/logback-1.0.0/auto/src/main/java/io/opentelemetry/instrumentation/auto/logback/v1_0_0/LogbackInstrumentation.java
@@ -0,0 +1,76 @@
+/*
+ * 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.instrumentation.auto.logback.v1_0_0;
+
+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.instrumentation.logback.v1_0_0.OpenTelemetryAppender;
+import io.opentelemetry.javaagent.tooling.Instrumenter;
+import java.util.Collections;
+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 LogbackInstrumentation extends Instrumenter.Default {
+
+ public LogbackInstrumentation() {
+ super("logback");
+ }
+
+ @Override
+ public String[] helperClassNames() {
+ return new String[] {
+ "io.opentelemetry.instrumentation.logback.v1_0_0.OpenTelemetryAppender",
+ "io.opentelemetry.instrumentation.logback.v1_0_0.LoggingEventWrapper",
+ "io.opentelemetry.instrumentation.logback.v1_0_0.UnionMap",
+ "io.opentelemetry.instrumentation.logback.v1_0_0.UnionMap$ConcatenatedSet",
+ "io.opentelemetry.instrumentation.logback.v1_0_0.UnionMap$ConcatenatedSet$ConcatenatedSetIterator",
+ };
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> typeMatcher() {
+ return named("ch.qos.logback.classic.Logger");
+ }
+
+ @Override
+ public Map extends ElementMatcher super MethodDescription>, String> transformers() {
+ return Collections.singletonMap(
+ isMethod()
+ .and(isPublic())
+ .and(named("callAppenders"))
+ .and(takesArguments(1))
+ .and(takesArgument(0, named("ch.qos.logback.classic.spi.ILoggingEvent"))),
+ LogbackInstrumentation.class.getName() + "$CallAppendersAdvice");
+ }
+
+ public static class CallAppendersAdvice {
+ @Advice.OnMethodEnter
+ public static void onEnter(@Advice.Argument(value = 0, readOnly = false) ILoggingEvent event) {
+ event = OpenTelemetryAppender.wrapEvent(event);
+ }
+ }
+}
diff --git a/instrumentation/logback/logback-1.0.0/auto/src/test/groovy/io/opentelemetry/instrumentation/auto/logback/v1_0_0/LogbackTest.groovy b/instrumentation/logback/logback-1.0.0/auto/src/test/groovy/io/opentelemetry/instrumentation/auto/logback/v1_0_0/LogbackTest.groovy
new file mode 100644
index 000000000000..9e2ddf60b3b5
--- /dev/null
+++ b/instrumentation/logback/logback-1.0.0/auto/src/test/groovy/io/opentelemetry/instrumentation/auto/logback/v1_0_0/LogbackTest.groovy
@@ -0,0 +1,23 @@
+/*
+ * 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.instrumentation.auto.logback.v1_0_0
+
+import io.opentelemetry.auto.test.AgentTestTrait
+import io.opentelemetry.instrumentation.logback.v1_0_0.AbstractLogbackTest
+
+class LogbackTest extends AbstractLogbackTest implements AgentTestTrait {
+}
diff --git a/instrumentation/logback/logback-1.0.0/auto/src/test/resources/logback.xml b/instrumentation/logback/logback-1.0.0/auto/src/test/resources/logback.xml
new file mode 100644
index 000000000000..3434fbaaab59
--- /dev/null
+++ b/instrumentation/logback/logback-1.0.0/auto/src/test/resources/logback.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ %coloredLevel %logger{15} - %message%n%xException{10}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/OpenTelemetryAppender.java b/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/OpenTelemetryAppender.java
index 2ece7fd09dbf..b2b302dc591a 100644
--- a/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/OpenTelemetryAppender.java
+++ b/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/OpenTelemetryAppender.java
@@ -33,12 +33,16 @@ public class OpenTelemetryAppender extends UnsynchronizedAppenderBase aai = new AppenderAttachableImpl<>();
- @Override
- protected void append(ILoggingEvent event) {
+ public static ILoggingEvent wrapEvent(ILoggingEvent event) {
Span currentSpan = TracingContextUtils.getCurrentSpan();
if (!currentSpan.getContext().isValid()) {
- aai.appendLoopOnAppenders(event);
- return;
+ return event;
+ }
+
+ Map eventContext = event.getMDCPropertyMap();
+ if (eventContext != null && eventContext.containsKey("traceId")) {
+ // Assume already instrumented event if traceId is present.
+ return event;
}
Map contextData = new HashMap<>();
@@ -47,15 +51,18 @@ protected void append(ILoggingEvent event) {
contextData.put("spanId", spanContext.getSpanId().toLowerBase16());
contextData.put("traceFlags", spanContext.getTraceFlags().toLowerBase16());
- Map eventContext = event.getMDCPropertyMap();
if (eventContext == null) {
eventContext = contextData;
} else {
eventContext = new UnionMap<>(eventContext, contextData);
}
- ILoggingEvent wrapped = new LoggingEventWrapper(event, eventContext);
- aai.appendLoopOnAppenders(wrapped);
+ return new LoggingEventWrapper(event, eventContext);
+ }
+
+ @Override
+ protected void append(ILoggingEvent event) {
+ aai.appendLoopOnAppenders(wrapEvent(event));
}
@Override
diff --git a/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/UnionMap.java b/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/UnionMap.java
index 19a32c085673..c3e301fed6ad 100644
--- a/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/UnionMap.java
+++ b/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/UnionMap.java
@@ -137,7 +137,7 @@ public Set> entrySet() {
}
// Member sets must be deduped by caller.
- private static final class ConcatenatedSet extends AbstractSet {
+ static final class ConcatenatedSet extends AbstractSet {
private final Set first;
private final Set second;
@@ -183,29 +183,32 @@ public void clear() {
@Override
public Iterator iterator() {
- return new Iterator() {
+ return new ConcatenatedSetIterator();
+ }
- final Iterator firstItr = first.iterator();
- final Iterator secondItr = second.iterator();
+ class ConcatenatedSetIterator implements Iterator {
+ final Iterator firstItr = first.iterator();
+ final Iterator secondItr = second.iterator();
- @Override
- public boolean hasNext() {
- return firstItr.hasNext() || secondItr.hasNext();
- }
+ ConcatenatedSetIterator() {}
- @Override
- public T next() {
- if (firstItr.hasNext()) {
- return firstItr.next();
- }
- return secondItr.next();
- }
+ @Override
+ public boolean hasNext() {
+ return firstItr.hasNext() || secondItr.hasNext();
+ }
- @Override
- public void remove() {
- throw new UnsupportedOperationException();
+ @Override
+ public T next() {
+ if (firstItr.hasNext()) {
+ return firstItr.next();
}
- };
+ return secondItr.next();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
}
}
}
diff --git a/instrumentation/logback/logback-1.0.0/library/src/test/groovy/io/opentelemetry/instrumentation/logback/v1_0_0/LogbackTest.groovy b/instrumentation/logback/logback-1.0.0/library/src/test/groovy/io/opentelemetry/instrumentation/logback/v1_0_0/LogbackTest.groovy
index dfdbd2522b40..23b85ea7fdf3 100644
--- a/instrumentation/logback/logback-1.0.0/library/src/test/groovy/io/opentelemetry/instrumentation/logback/v1_0_0/LogbackTest.groovy
+++ b/instrumentation/logback/logback-1.0.0/library/src/test/groovy/io/opentelemetry/instrumentation/logback/v1_0_0/LogbackTest.groovy
@@ -16,5 +16,7 @@
package io.opentelemetry.instrumentation.logback.v1_0_0
-class LogbackTest extends AbstractLogbackTest {
+import io.opentelemetry.auto.test.InstrumentationTestTrait
+
+class LogbackTest extends AbstractLogbackTest implements InstrumentationTestTrait {
}
diff --git a/instrumentation/logback/logback-1.0.0/testing/src/main/groovy/io/opentelemetry/instrumentation/logback/v1_0_0/AbstractLogbackTest.groovy b/instrumentation/logback/logback-1.0.0/testing/src/main/groovy/io/opentelemetry/instrumentation/logback/v1_0_0/AbstractLogbackTest.groovy
index 5a37875a968e..14523f19692a 100644
--- a/instrumentation/logback/logback-1.0.0/testing/src/main/groovy/io/opentelemetry/instrumentation/logback/v1_0_0/AbstractLogbackTest.groovy
+++ b/instrumentation/logback/logback-1.0.0/testing/src/main/groovy/io/opentelemetry/instrumentation/logback/v1_0_0/AbstractLogbackTest.groovy
@@ -18,15 +18,15 @@ package io.opentelemetry.instrumentation.logback.v1_0_0
import ch.qos.logback.classic.spi.ILoggingEvent
import ch.qos.logback.core.read.ListAppender
+import io.opentelemetry.auto.test.InstrumentationSpecification
import io.opentelemetry.auto.test.utils.TraceUtils
import io.opentelemetry.trace.Span
import io.opentelemetry.trace.TracingContextUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import spock.lang.Shared
-import spock.lang.Specification
-abstract class AbstractLogbackTest extends Specification {
+abstract class AbstractLogbackTest extends InstrumentationSpecification {
private static final Logger logger = LoggerFactory.getLogger("test")
@@ -35,8 +35,15 @@ abstract class AbstractLogbackTest extends Specification {
def setupSpec() {
ch.qos.logback.classic.Logger logbackLogger = (ch.qos.logback.classic.Logger) logger
- listAppender = (logbackLogger.getAppender("OTEL") as OpenTelemetryAppender)
- .getAppender("LIST") as ListAppender
+ def topLevelListAppender = logbackLogger.getAppender("LIST")
+ if (topLevelListAppender != null) {
+ // Auto instrumentation test.
+ listAppender = topLevelListAppender as ListAppender
+ } else {
+ // Library instrumentation test.
+ listAppender = (logbackLogger.getAppender("OTEL") as OpenTelemetryAppender)
+ .getAppender("LIST") as ListAppender
+ }
}
def setup() {
diff --git a/settings.gradle b/settings.gradle
index 9f47b8ce923c..a132eabed6a4 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -121,6 +121,7 @@ include ':instrumentation:lettuce:lettuce-4.0'
include ':instrumentation:lettuce:lettuce-5.0'
include ':instrumentation:lettuce:lettuce-5.1'
include ':instrumentation:log4j:log4j-2.13.2:library'
+include ':instrumentation:logback:logback-1.0.0:auto'
include ':instrumentation:logback:logback-1.0.0:library'
include ':instrumentation:logback:logback-1.0.0:testing'
include ':instrumentation:mongo:mongo-3.1'