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 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'