diff --git a/instrumentation/logback/logback-1.0.0/library/NOTICE.txt b/instrumentation/logback/logback-1.0.0/library/NOTICE.txt
new file mode 100644
index 0000000000..ad41c3d753
--- /dev/null
+++ b/instrumentation/logback/logback-1.0.0/library/NOTICE.txt
@@ -0,0 +1,19 @@
+This product contains a modified part of Armeria, distributed by LINE:
+
+ * License:
+
+ Copyright 2015 LINE Corporation
+
+ LINE Corporation 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:
+
+ https://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.
+
+ * Homepage: https://armeria.dev
\ No newline at end of file
diff --git a/instrumentation/logback/logback-1.0.0/library/README.md b/instrumentation/logback/logback-1.0.0/library/README.md
new file mode 100644
index 0000000000..be8eef5ad8
--- /dev/null
+++ b/instrumentation/logback/logback-1.0.0/library/README.md
@@ -0,0 +1,54 @@
+# Logback Integration
+
+This module integrates instrumentation with Logback by injecting the trace ID and span ID from a
+mounted span using a custom Logback appender.
+
+To use it, add the module to your application's runtime classpath and add the appender to your
+`logback.xml`.
+
+**Maven**
+
+```xml
+
+
+ io.opentelemetry.instrumentation
+ opentelemetry-logback-1.0.0
+ 0.8.0-SNAPSHOT
+ runtime
+
+
+```
+
+**Gradle**
+
+```kotlin
+dependencies {
+ runtimeOnly("io.opentelemetry.instrumentation:opentelemetry-logback-1.0.0:0.8.0-SNAPSHOT")
+}
+```
+
+**logback.xml**
+
+```xml
+
+
+
+
+ %d{HH:mm:ss.SSS} %X{traceId} %X{spanId} %msg%n
+
+
+
+
+
+
+
+ ...
+
+```
+
+Logging events will automatically have context information from the span context injected. The
+following attributes are available for use:
+
+- `traceId`
+- `spanId`
+- `traceFlags`
diff --git a/instrumentation/logback/logback-1.0.0/library/logback-1.0.0-library.gradle b/instrumentation/logback/logback-1.0.0/library/logback-1.0.0-library.gradle
new file mode 100644
index 0000000000..2bb17868c7
--- /dev/null
+++ b/instrumentation/logback/logback-1.0.0/library/logback-1.0.0-library.gradle
@@ -0,0 +1,7 @@
+apply from: "$rootDir/gradle/instrumentation-library.gradle"
+
+dependencies {
+ library group: 'ch.qos.logback', name: 'logback-classic', version: '1.0.0'
+
+ testImplementation project(':instrumentation:logback:logback-1.0.0:testing')
+}
diff --git a/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/LoggingEventWrapper.java b/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/LoggingEventWrapper.java
new file mode 100644
index 0000000000..2e06d31c23
--- /dev/null
+++ b/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/LoggingEventWrapper.java
@@ -0,0 +1,128 @@
+/*
+ * 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.logback.v1_0_0;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.IThrowableProxy;
+import ch.qos.logback.classic.spi.LoggerContextVO;
+import java.util.Map;
+import org.slf4j.Marker;
+
+final class LoggingEventWrapper implements ILoggingEvent {
+ private final ILoggingEvent event;
+ private final Map mdcPropertyMap;
+ private final LoggerContextVO vo;
+
+ LoggingEventWrapper(ILoggingEvent event, Map mdcPropertyMap) {
+ this.event = event;
+ this.mdcPropertyMap = mdcPropertyMap;
+
+ final LoggerContextVO oldVo = event.getLoggerContextVO();
+ if (oldVo != null) {
+ vo = new LoggerContextVO(oldVo.getName(), mdcPropertyMap, oldVo.getBirthTime());
+ } else {
+ vo = null;
+ }
+ }
+
+ @Override
+ public Object[] getArgumentArray() {
+ return event.getArgumentArray();
+ }
+
+ @Override
+ public Level getLevel() {
+ return event.getLevel();
+ }
+
+ @Override
+ public String getLoggerName() {
+ return event.getLoggerName();
+ }
+
+ @Override
+ public String getThreadName() {
+ return event.getThreadName();
+ }
+
+ @Override
+ public IThrowableProxy getThrowableProxy() {
+ return event.getThrowableProxy();
+ }
+
+ @Override
+ public void prepareForDeferredProcessing() {
+ event.prepareForDeferredProcessing();
+ }
+
+ @Override
+ public LoggerContextVO getLoggerContextVO() {
+ return vo;
+ }
+
+ @Override
+ public String getMessage() {
+ return event.getMessage();
+ }
+
+ @Override
+ public long getTimeStamp() {
+ return event.getTimeStamp();
+ }
+
+ @Override
+ public StackTraceElement[] getCallerData() {
+ return event.getCallerData();
+ }
+
+ @Override
+ public boolean hasCallerData() {
+ return event.hasCallerData();
+ }
+
+ @Override
+ public Marker getMarker() {
+ return event.getMarker();
+ }
+
+ @Override
+ public String getFormattedMessage() {
+ return event.getFormattedMessage();
+ }
+
+ @Override
+ public Map getMDCPropertyMap() {
+ return mdcPropertyMap;
+ }
+
+ /**
+ * A synonym for {@link #getMDCPropertyMap}.
+ *
+ * @deprecated Use {@link #getMDCPropertyMap()}.
+ */
+ @Override
+ @Deprecated
+ public Map getMdc() {
+ return event.getMDCPropertyMap();
+ }
+
+ @Override
+ public String toString() {
+ return event.toString();
+ }
+}
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
new file mode 100644
index 0000000000..2ece7fd09d
--- /dev/null
+++ b/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/OpenTelemetryAppender.java
@@ -0,0 +1,95 @@
+/*
+ * 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.logback.v1_0_0;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.UnsynchronizedAppenderBase;
+import ch.qos.logback.core.spi.AppenderAttachable;
+import ch.qos.logback.core.spi.AppenderAttachableImpl;
+import io.opentelemetry.trace.Span;
+import io.opentelemetry.trace.SpanContext;
+import io.opentelemetry.trace.TracingContextUtils;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class OpenTelemetryAppender extends UnsynchronizedAppenderBase
+ implements AppenderAttachable {
+
+ private final AppenderAttachableImpl aai = new AppenderAttachableImpl<>();
+
+ @Override
+ protected void append(ILoggingEvent event) {
+ Span currentSpan = TracingContextUtils.getCurrentSpan();
+ if (!currentSpan.getContext().isValid()) {
+ aai.appendLoopOnAppenders(event);
+ return;
+ }
+
+ Map contextData = new HashMap<>();
+ SpanContext spanContext = currentSpan.getContext();
+ contextData.put("traceId", spanContext.getTraceId().toLowerBase16());
+ 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);
+ }
+
+ @Override
+ public void addAppender(Appender appender) {
+ aai.addAppender(appender);
+ }
+
+ @Override
+ public Iterator> iteratorForAppenders() {
+ return aai.iteratorForAppenders();
+ }
+
+ @Override
+ public Appender getAppender(String name) {
+ return aai.getAppender(name);
+ }
+
+ @Override
+ public boolean isAttached(Appender appender) {
+ return aai.isAttached(appender);
+ }
+
+ @Override
+ public void detachAndStopAllAppenders() {
+ aai.detachAndStopAllAppenders();
+ }
+
+ @Override
+ public boolean detachAppender(Appender appender) {
+ return aai.detachAppender(appender);
+ }
+
+ @Override
+ public boolean detachAppender(String name) {
+ return aai.detachAppender(name);
+ }
+}
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
new file mode 100644
index 0000000000..19a32c0856
--- /dev/null
+++ b/instrumentation/logback/logback-1.0.0/library/src/main/java/io/opentelemetry/instrumentation/logback/v1_0_0/UnionMap.java
@@ -0,0 +1,211 @@
+/*
+ * 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.logback.v1_0_0;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An immutable view over two maps, with keys resolving from the first map first, or otherwise the
+ * second if not present in the first.
+ */
+final class UnionMap extends AbstractMap {
+
+ private final Map first;
+ private final Map second;
+ private int size = -1;
+ private Set> entrySet;
+
+ UnionMap(Map first, Map second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ @Override
+ public int size() {
+ if (size >= 0) {
+ return size;
+ }
+
+ final Map a;
+ final Map b;
+ if (first.size() >= second.size()) {
+ a = first;
+ b = second;
+ } else {
+ a = second;
+ b = first;
+ }
+
+ int size = a.size();
+ if (!b.isEmpty()) {
+ for (K k : b.keySet()) {
+ if (!a.containsKey(k)) {
+ size++;
+ }
+ }
+ }
+
+ return this.size = size;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return first.isEmpty() && second.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return first.containsKey(key) || second.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return first.containsValue(value) || second.containsValue(value);
+ }
+
+ @Override
+ public V get(Object key) {
+ final V value = first.get(key);
+ return value != null ? value : second.get(key);
+ }
+
+ @Override
+ public V put(K key, V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V remove(Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Set> entrySet() {
+ if (entrySet != null) {
+ return entrySet;
+ }
+
+ // Check for dupes first to reduce allocations on the vastly more common case where there aren't
+ // any.
+ boolean secondHasDupes = false;
+ for (Entry entry : second.entrySet()) {
+ if (first.containsKey(entry.getKey())) {
+ secondHasDupes = true;
+ break;
+ }
+ }
+
+ final Set> filteredSecond;
+ if (!secondHasDupes) {
+ filteredSecond = second.entrySet();
+ } else {
+ filteredSecond = new LinkedHashSet<>();
+ for (Entry entry : second.entrySet()) {
+ if (!first.containsKey(entry.getKey())) {
+ filteredSecond.add(entry);
+ }
+ }
+ }
+ return entrySet =
+ Collections.unmodifiableSet(new ConcatenatedSet<>(first.entrySet(), filteredSecond));
+ }
+
+ // Member sets must be deduped by caller.
+ private static final class ConcatenatedSet extends AbstractSet {
+
+ private final Set first;
+ private final Set second;
+
+ private final int size;
+
+ ConcatenatedSet(Set first, Set second) {
+ this.first = first;
+ this.second = second;
+
+ size = first.size() + second.size();
+ }
+
+ @Override
+ public int size() {
+ return size;
+ }
+
+ @Override
+ public boolean add(T t) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean addAll(Collection extends T> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean retainAll(Collection> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+
+ final Iterator firstItr = first.iterator();
+ final Iterator secondItr = second.iterator();
+
+ @Override
+ public boolean hasNext() {
+ return firstItr.hasNext() || secondItr.hasNext();
+ }
+
+ @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
new file mode 100644
index 0000000000..dfdbd2522b
--- /dev/null
+++ b/instrumentation/logback/logback-1.0.0/library/src/test/groovy/io/opentelemetry/instrumentation/logback/v1_0_0/LogbackTest.groovy
@@ -0,0 +1,20 @@
+/*
+ * 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.logback.v1_0_0
+
+class LogbackTest extends AbstractLogbackTest {
+}
diff --git a/instrumentation/logback/logback-1.0.0/library/src/test/groovy/io/opentelemetry/instrumentation/logback/v1_0_0/UnionMapTest.groovy b/instrumentation/logback/logback-1.0.0/library/src/test/groovy/io/opentelemetry/instrumentation/logback/v1_0_0/UnionMapTest.groovy
new file mode 100644
index 0000000000..62a26cb3e0
--- /dev/null
+++ b/instrumentation/logback/logback-1.0.0/library/src/test/groovy/io/opentelemetry/instrumentation/logback/v1_0_0/UnionMapTest.groovy
@@ -0,0 +1,92 @@
+/*
+ * 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.logback.v1_0_0
+
+import spock.lang.Specification
+
+class UnionMapTest extends Specification {
+
+ def "maps"() {
+ when:
+ def union = new UnionMap(first, second)
+
+ then:
+ union['cat'] == 'meow'
+ union['dog'] == 'bark'
+ union['foo'] == 'bar'
+ union['hello'] == 'world'
+ union['giraffe'] == null
+
+ !union.isEmpty()
+ union.size() == 4
+ union.containsKey('cat')
+ union.containsKey('dog')
+ union.containsKey('foo')
+ union.containsKey('hello')
+ !union.containsKey('giraffe')
+
+ def set = union.entrySet()
+ !set.isEmpty()
+ set.size() == 4
+ def copy = new ArrayList(set)
+ copy.size() == 4
+
+ where:
+ first | second
+ [cat: 'meow', dog: 'bark'] | [foo: 'bar', hello: 'world']
+ // Overlapping entries in second does not affect the union.
+ [cat: 'meow', dog: 'bark'] | [foo: 'bar', hello: 'world', cat: 'moo']
+ }
+
+ def "both empty"() {
+ when:
+ def union = new UnionMap(Collections.emptyMap(), Collections.emptyMap())
+
+ then:
+ union.isEmpty()
+ union.size() == 0
+ union['cat'] == null
+
+ def set = union.entrySet()
+ set.isEmpty()
+ set.size() == 0
+ def copy = new ArrayList(set)
+ copy.size() == 0
+ }
+
+ def "one empty"() {
+ when:
+ def union = new UnionMap(first, second)
+
+ then:
+ !union.isEmpty()
+ union.size() == 1
+ union['cat'] == 'meow'
+ union['dog'] == null
+
+ def set = union.entrySet()
+ !set.isEmpty()
+ set.size() == 1
+ def copy = new ArrayList(set)
+ copy.size() == 1
+
+ where:
+ first | second
+ [cat: 'meow'] | Collections.emptyMap()
+ Collections.emptyMap() | [cat: 'meow']
+ }
+}
diff --git a/instrumentation/logback/logback-1.0.0/library/src/test/resources/logback.xml b/instrumentation/logback/logback-1.0.0/library/src/test/resources/logback.xml
new file mode 100644
index 0000000000..db4f7ca70b
--- /dev/null
+++ b/instrumentation/logback/logback-1.0.0/library/src/test/resources/logback.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/instrumentation/logback/logback-1.0.0/testing/logback-1.0.0-testing.gradle b/instrumentation/logback/logback-1.0.0/testing/logback-1.0.0-testing.gradle
new file mode 100644
index 0000000000..268e0409f7
--- /dev/null
+++ b/instrumentation/logback/logback-1.0.0/testing/logback-1.0.0-testing.gradle
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+apply from: "$rootDir/gradle/java.gradle"
+
+dependencies {
+ compileOnly project(":instrumentation:logback:logback-1.0.0:library")
+
+ api project(':testing-common')
+
+ api group: 'ch.qos.logback', name: 'logback-classic', version: '1.0.0'
+
+ implementation deps.guava
+
+ implementation deps.groovy
+ implementation deps.opentelemetryApi
+ implementation deps.spock
+}
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
new file mode 100644
index 0000000000..5a37875a96
--- /dev/null
+++ b/instrumentation/logback/logback-1.0.0/testing/src/main/groovy/io/opentelemetry/instrumentation/logback/v1_0_0/AbstractLogbackTest.groovy
@@ -0,0 +1,101 @@
+/*
+ * 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.logback.v1_0_0
+
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.core.read.ListAppender
+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 {
+
+ private static final Logger logger = LoggerFactory.getLogger("test")
+
+ @Shared
+ ListAppender listAppender
+
+ 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 setup() {
+ listAppender.list.clear()
+ }
+
+ def "no ids when no span"() {
+ when:
+ logger.info("log message 1")
+ logger.info("log message 2")
+
+ def events = listAppender.list
+
+ then:
+ events.size() == 2
+ events[0].message == "log message 1"
+ events[0].getMDCPropertyMap().get("traceId") == null
+ events[0].getMDCPropertyMap().get("spanId") == null
+ events[0].getMDCPropertyMap().get("traceFlags") == null
+
+ events[1].message == "log message 2"
+ events[1].getMDCPropertyMap().get("traceId") == null
+ events[1].getMDCPropertyMap().get("spanId") == null
+ events[1].getMDCPropertyMap().get("traceFlags") == null
+ }
+
+ def "ids when span"() {
+ when:
+ Span span1
+ TraceUtils.runUnderTrace("test") {
+ span1 = TracingContextUtils.currentSpan
+ logger.info("log message 1")
+ }
+
+ logger.info("log message 2")
+
+ Span span2
+ TraceUtils.runUnderTrace("test 2") {
+ span2 = TracingContextUtils.currentSpan
+ logger.info("log message 3")
+ }
+
+ def events = listAppender.list
+
+ then:
+ events.size() == 3
+ events[0].message == "log message 1"
+ events[0].getMDCPropertyMap().get("traceId") == span1.context.traceId.toLowerBase16()
+ events[0].getMDCPropertyMap().get("spanId") == span1.context.spanId.toLowerBase16()
+ events[0].getMDCPropertyMap().get("traceFlags") == span1.context.traceFlags.toLowerBase16()
+
+ events[1].message == "log message 2"
+ events[1].getMDCPropertyMap().get("traceId") == null
+ events[1].getMDCPropertyMap().get("spanId") == null
+ events[1].getMDCPropertyMap().get("traceFlags") == null
+
+ events[2].message == "log message 3"
+ events[2].getMDCPropertyMap().get("traceId") == span2.context.traceId.toLowerBase16()
+ events[2].getMDCPropertyMap().get("spanId") == span2.context.spanId.toLowerBase16()
+ events[2].getMDCPropertyMap().get("traceFlags") == span2.context.traceFlags.toLowerBase16()
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 06b5d87d01..9f47b8ce92 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -121,6 +121,8 @@ 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:library'
+include ':instrumentation:logback:logback-1.0.0:testing'
include ':instrumentation:mongo:mongo-3.1'
include ':instrumentation:mongo:mongo-3.7'
include ':instrumentation:mongo:mongo-async-3.3'