diff --git a/.scalafmt.conf b/.scalafmt.conf index e43a06b9..bca80319 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -75,7 +75,7 @@ project.includePaths."+" = ["glob:**.md"] fileOverride { "glob:**/project/Dependencies.scala" { - maxColumn = 150 + maxColumn = 160 align.preset = more } } diff --git a/build.sbt b/build.sbt index 1afae6cf..734cea64 100644 --- a/build.sbt +++ b/build.sbt @@ -13,7 +13,8 @@ lazy val rawAllAggregates = chimneyZioPrelude.projectRefs ++ circeReactivemongoB reactivemongoBase.projectRefs ++ reactivemongoBson.projectRefs ++ reactivemongoBsonAny.projectRefs ++ reactivemongoBsonJodaTime.projectRefs ++ reactivemongoBsonRefined.projectRefs ++ reactivemongoBsonZioPrelude.projectRefs ++ - reactivemongoKamonInstrumentation.projectRefs ++ redissonCore.projectRefs ++ + reactivemongoKamonInstrumentation.projectRefs ++ + reactivemongoOpentelemetryJavaagentExtension.projectRefs ++ redissonCore.projectRefs ++ redissonCodecBase.projectRefs ++ redissonCodecCirce.projectRefs ++ redissonCodecPlayJson.projectRefs ++ redissonCodecPlay2Json.projectRefs ++ scalaLogging.projectRefs ++ tapirZioPrelude.projectRefs ++ implicitsAny.projectRefs ++ @@ -242,6 +243,11 @@ lazy val reactivemongoKamonInstrumentation = projectMatrix .jvmPlatform(ProjectSettings.scala2Versions) .configure(ModulesCommon.reactivemongoKamonInstrumentationProfile) +lazy val reactivemongoOpentelemetryJavaagentExtension = projectMatrix + .in(file("common/reactivemongo/opentelemetry-javaagent-extension")) + .jvmPlatform(ProjectSettings.scala2_13Versions) + .configure(ModulesCommon.reactivemongoOpentelemetryJavaagentExtensionProfile) + lazy val redissonCore = projectMatrix .in(file("common/redisson/core")) .jvmPlatform(ProjectSettings.scala2Versions) diff --git a/common/reactivemongo/opentelemetry-javaagent-extension/src/main/java/io/kinoplan/utils/reactivemongo/opentelemetry/javaagent/extension/ReactiveMongoInstrumentationModule.java b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/java/io/kinoplan/utils/reactivemongo/opentelemetry/javaagent/extension/ReactiveMongoInstrumentationModule.java new file mode 100644 index 00000000..e0d1df4c --- /dev/null +++ b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/java/io/kinoplan/utils/reactivemongo/opentelemetry/javaagent/extension/ReactiveMongoInstrumentationModule.java @@ -0,0 +1,47 @@ +package io.kinoplan.utils.reactivemongo.opentelemetry.javaagent.extension; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers; +import net.bytebuddy.matcher.ElementMatcher; +import reactivemongo.api.ReactiveMongoInstrumentation; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@AutoService(InstrumentationModule.class) +public final class ReactiveMongoInstrumentationModule extends InstrumentationModule { + public ReactiveMongoInstrumentationModule() { + super("reactivemongo"); + } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + return AgentElementMatchers.hasClassesNamed("reactivemongo.api.MongoConnection"); + } + + @Override + public boolean isIndyModule() { + return true; + } + + public List getAdditionalHelperClassNames() { + return Arrays.asList( + "io.kinoplan.utils.reactivemongo.opentelemetry.javaagent.extension.ReactiveMongoSingletons", + "reactivemongo.api.ResponseFutureWrapper", + "reactivemongo.api.ResponseFutureWrapper$1", + "reactivemongo.api.ResponseFutureWrapper$2", + "reactivemongo.api.ReactiveMongoDbClientAttributesGetter", + "reactivemongo.api.ReactiveMongoAttributesExtractor", + "reactivemongo.api.ReactiveMongoSpanNameExtractor" + ); + } + + + @Override + public List typeInstrumentations() { + return Collections.singletonList(new ReactiveMongoInstrumentation()); + } +} diff --git a/common/reactivemongo/opentelemetry-javaagent-extension/src/main/java/io/kinoplan/utils/reactivemongo/opentelemetry/javaagent/extension/ReactiveMongoSingletons.java b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/java/io/kinoplan/utils/reactivemongo/opentelemetry/javaagent/extension/ReactiveMongoSingletons.java new file mode 100644 index 00000000..30526ca7 --- /dev/null +++ b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/java/io/kinoplan/utils/reactivemongo/opentelemetry/javaagent/extension/ReactiveMongoSingletons.java @@ -0,0 +1,38 @@ +package io.kinoplan.utils.reactivemongo.opentelemetry.javaagent.extension; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; +import reactivemongo.api.ExpectingResponseWrapper; +import reactivemongo.api.ReactiveMongoAttributesExtractor; +import reactivemongo.api.ReactiveMongoDbClientAttributesGetter; +import reactivemongo.api.ReactiveMongoSpanNameExtractor; + +public final class ReactiveMongoSingletons { + private static final String INSTRUMENTATION_NAME = "io.kinoplan.utils.reactivemongo"; + + private static final Instrumenter INSTRUMENTER; + + static { + ReactiveMongoDbClientAttributesGetter dbAttributesGetter = new ReactiveMongoDbClientAttributesGetter(); + ReactiveMongoAttributesExtractor attributesExtractor = new ReactiveMongoAttributesExtractor(); + ReactiveMongoSpanNameExtractor spanNameExtractor = new ReactiveMongoSpanNameExtractor(); + + INSTRUMENTER = + Instrumenter.builder( + GlobalOpenTelemetry.get(), + INSTRUMENTATION_NAME, + spanNameExtractor) + .addAttributesExtractor(DbClientAttributesExtractor.create(dbAttributesGetter)) + .addAttributesExtractor(attributesExtractor) + .buildInstrumenter(SpanKindExtractor.alwaysClient()); + } + + public static Instrumenter instrumenter() { + return INSTRUMENTER; + } + + private ReactiveMongoSingletons() { + } +} diff --git a/common/reactivemongo/opentelemetry-javaagent-extension/src/main/java/reactivemongo/api/ReactiveMongoInstrumentation.java b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/java/reactivemongo/api/ReactiveMongoInstrumentation.java new file mode 100644 index 00000000..283293dd --- /dev/null +++ b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/java/reactivemongo/api/ReactiveMongoInstrumentation.java @@ -0,0 +1,85 @@ +package reactivemongo.api; + + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import reactivemongo.core.actors.ExpectingResponse; +import reactivemongo.core.protocol.Response; +import scala.concurrent.Future; + +import static io.kinoplan.utils.reactivemongo.opentelemetry.javaagent.extension.ReactiveMongoSingletons.instrumenter; +import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; +import static net.bytebuddy.matcher.ElementMatchers.*; + +public class ReactiveMongoInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("reactivemongo.api.MongoConnection"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod() + .and(named("sendExpectingResponse")) + .and(takesArguments(1)) + .and(takesArgument(0, named("reactivemongo.core.actors.ExpectingResponse"))) + , + this.getClass().getName() + "$SendExpectingResponseAdvice" + ); + } + + @SuppressWarnings("unused") + public static class SendExpectingResponseAdvice { + + @Advice.AssignReturned.ToArguments(@Advice.AssignReturned.ToArguments.ToArgument(value = 0, index = 0)) + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + public static Object[] onEnter( + @Advice.Argument(0) ExpectingResponse expectingResponse + ) { + Context parentContext = currentContext(); + ExpectingResponseWrapper wrapper = new ExpectingResponseWrapper(expectingResponse); + if (!instrumenter().shouldStart(parentContext, wrapper)) { + return new Object[]{null, null}; + } + Context context = instrumenter().start(parentContext, wrapper); + Scope scope = context.makeCurrent(); + + return new Object[]{context, scope}; + } + + @Advice.AssignReturned.ToReturned + @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class) + public static Future onExit( + @Advice.Argument(0) ExpectingResponse expectingResponse, + @Advice.This MongoConnection thiz, + @Advice.Return Future responseFuture, + @Advice.Enter Object[] enter, + @Advice.Thrown Throwable throwable + ) { + + if (!(enter[0] instanceof Context context) || !(enter[1] instanceof Scope scope)) { + return null; + } + scope.close(); + + ExpectingResponseWrapper wrapper = new ExpectingResponseWrapper(expectingResponse); + if (throwable != null) { + instrumenter().end(context, wrapper, null, throwable); + return responseFuture; + } + + if (responseFuture == null) { + return null; + } + return ResponseFutureWrapper.wrap(responseFuture, context, thiz.actorSystem().dispatcher()); + } + } + +} + diff --git a/common/reactivemongo/opentelemetry-javaagent-extension/src/main/java/reactivemongo/api/ResponseFutureWrapper.java b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/java/reactivemongo/api/ResponseFutureWrapper.java new file mode 100644 index 00000000..f78612c4 --- /dev/null +++ b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/java/reactivemongo/api/ResponseFutureWrapper.java @@ -0,0 +1,41 @@ +package reactivemongo.api; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.context.Context; +import reactivemongo.core.protocol.Response; +import scala.concurrent.ExecutionContext; +import scala.concurrent.Future; +import scala.runtime.AbstractFunction1; + +import static io.kinoplan.utils.reactivemongo.opentelemetry.javaagent.extension.ReactiveMongoSingletons.instrumenter; + +public final class ResponseFutureWrapper { + + public static Future wrap( + Future future, Context context, ExecutionContext executionContext) { + + return future.transform( + new AbstractFunction1<>() { + @Override + @CanIgnoreReturnValue + public Response apply(Response result) { + if (result.error().isEmpty()) { + instrumenter().end(context, null, null, null); + } else { + instrumenter().end(context, null, null, (Throwable) result.error().get()); + } + return result; + } + }, + new AbstractFunction1<>() { + @Override + @CanIgnoreReturnValue + public Throwable apply(Throwable throwable) { + instrumenter().end(context, null, null, throwable); + return throwable; + } + }, + executionContext); + } +} + diff --git a/common/reactivemongo/opentelemetry-javaagent-extension/src/main/scala/reactivemongo/api/ExpectingResponseWrapper.scala b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/scala/reactivemongo/api/ExpectingResponseWrapper.scala new file mode 100644 index 00000000..2d09b376 --- /dev/null +++ b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/scala/reactivemongo/api/ExpectingResponseWrapper.scala @@ -0,0 +1,43 @@ +package reactivemongo.api + +import scala.util.{Failure, Success, Try} + +import reactivemongo.api.bson.BSONDocument +import reactivemongo.api.bson.buffer.ReadableBuffer +import reactivemongo.api.bson.collection.BSONSerializationPack.readFromBuffer +import reactivemongo.core.actors.ExpectingResponse +import reactivemongo.core.protocol.CollectionAwareRequestOp + +case class ExpectingResponseWrapper(expectingResponse: ExpectingResponse) { + + lazy val statement: Option[BSONDocument] = { + val buf = expectingResponse.requestMaker.payload.duplicate() + Try[BSONDocument] { + val sz = buf.getIntLE(buf.readerIndex) + val bytes = Array.ofDim[Byte](sz) + buf.readBytes(bytes) + readFromBuffer(ReadableBuffer(bytes)) + } match { + case Failure(_) => None + case Success(value) => + buf.resetReaderIndex() + Some(value) + } + } + + lazy val collectionName: Option[String] = statement.flatMap { document => + document + .headOption + .flatMap( + _.value.asOpt[String].orElse(document.getAsOpt[String]("collection")) // in case of getMore + ) + } + + lazy val dbName: Option[String] = expectingResponse.requestMaker.op match { + case op: CollectionAwareRequestOp => Some(op.db) + case _ => None + } + + lazy val operationName: Option[String] = statement.flatMap(_.headOption.map(_.name)) + +} diff --git a/common/reactivemongo/opentelemetry-javaagent-extension/src/main/scala/reactivemongo/api/ReactiveMongoAttributesExtractor.scala b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/scala/reactivemongo/api/ReactiveMongoAttributesExtractor.scala new file mode 100644 index 00000000..11ba7550 --- /dev/null +++ b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/scala/reactivemongo/api/ReactiveMongoAttributesExtractor.scala @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package reactivemongo.api + +import io.opentelemetry.api.common.{AttributeKey, AttributesBuilder} +import io.opentelemetry.context.Context +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor +import io.opentelemetry.instrumentation.api.internal.SemconvStability.{ + emitOldDatabaseSemconv, + emitStableDatabaseSemconv +}; + +class ReactiveMongoAttributesExtractor extends AttributesExtractor[ExpectingResponseWrapper, Void] { + + // copied from DbIncubatingAttributes + private val DB_COLLECTION_NAME = AttributeKey.stringKey("db.collection.name"); + private val DB_MONGODB_COLLECTION = AttributeKey.stringKey("db.mongodb.collection"); + + override def onStart( + attributes: AttributesBuilder, + parentContext: Context, + request: ExpectingResponseWrapper + ): Unit = request + .collectionName + .foreach { collectionName => + if (emitStableDatabaseSemconv()) attributes.put(DB_COLLECTION_NAME, collectionName) + if (emitOldDatabaseSemconv()) attributes.put(DB_MONGODB_COLLECTION, collectionName) + } + + override def onEnd( + attributes: AttributesBuilder, + context: Context, + request: ExpectingResponseWrapper, + response: Void, + error: Throwable + ): Unit = {} + +} diff --git a/common/reactivemongo/opentelemetry-javaagent-extension/src/main/scala/reactivemongo/api/ReactiveMongoDbClientAttributesGetter.scala b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/scala/reactivemongo/api/ReactiveMongoDbClientAttributesGetter.scala new file mode 100644 index 00000000..d7299c4d --- /dev/null +++ b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/scala/reactivemongo/api/ReactiveMongoDbClientAttributesGetter.scala @@ -0,0 +1,27 @@ +package reactivemongo.api + +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientAttributesGetter +import reactivemongo.api.bson.BSONDocument.pretty + +class ReactiveMongoDbClientAttributesGetter + extends DbClientAttributesGetter[ExpectingResponseWrapper] { + + override def getUser(request: ExpectingResponseWrapper): String = null + + override def getConnectionString(request: ExpectingResponseWrapper): String = null + + override def getDbSystem(request: ExpectingResponseWrapper): String = "mongodb" + + override def getDbNamespace(request: ExpectingResponseWrapper): String = request + .dbName + .getOrElse("undefined") + + override def getDbQueryText(request: ExpectingResponseWrapper): String = request + .statement + .map(pretty) + .orNull + + override def getDbOperationName(request: ExpectingResponseWrapper): String = + request.operationName.orNull + +} diff --git a/common/reactivemongo/opentelemetry-javaagent-extension/src/main/scala/reactivemongo/api/ReactiveMongoSpanNameExtractor.scala b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/scala/reactivemongo/api/ReactiveMongoSpanNameExtractor.scala new file mode 100644 index 00000000..a48257f9 --- /dev/null +++ b/common/reactivemongo/opentelemetry-javaagent-extension/src/main/scala/reactivemongo/api/ReactiveMongoSpanNameExtractor.scala @@ -0,0 +1,28 @@ +package reactivemongo.api + +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor + +class ReactiveMongoSpanNameExtractor extends SpanNameExtractor[ExpectingResponseWrapper] { + + private val DEFAULT_SPAN_NAME = "DB Query" + + override def extract(wrapper: ExpectingResponseWrapper): String = { + val operation = wrapper.operationName.orNull + val dbName = wrapper.dbName.orNull + + if (operation == null) return if (dbName == null) DEFAULT_SPAN_NAME + else dbName + + val table = wrapper.collectionName.orNull + val name = new StringBuilder(operation) + if (dbName != null || table != null) name.append(' ') + // skip db name if table already has a db name prefixed to it// skip db name if table already has a db name prefixed to it + if (dbName != null && (table == null || table.indexOf('.') == -1)) { + name.append(dbName) + if (table != null) name.append('.') + } + if (table != null) name.append(table) + name.toString + } + +} diff --git a/common/reactivemongo/opentelemetry-javaagent-extension/src/test/resources/logback.xml b/common/reactivemongo/opentelemetry-javaagent-extension/src/test/resources/logback.xml new file mode 100644 index 00000000..ab0aab6b --- /dev/null +++ b/common/reactivemongo/opentelemetry-javaagent-extension/src/test/resources/logback.xml @@ -0,0 +1,17 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + \ No newline at end of file diff --git a/common/reactivemongo/opentelemetry-javaagent-extension/src/test/scala/io/kinoplan/utils/reactivemongo/opentelemetry/javaagent/extension/ReactivemongoInstrumentationModuleSpec.scala b/common/reactivemongo/opentelemetry-javaagent-extension/src/test/scala/io/kinoplan/utils/reactivemongo/opentelemetry/javaagent/extension/ReactivemongoInstrumentationModuleSpec.scala new file mode 100644 index 00000000..7ed85c5c --- /dev/null +++ b/common/reactivemongo/opentelemetry-javaagent-extension/src/test/scala/io/kinoplan/utils/reactivemongo/opentelemetry/javaagent/extension/ReactivemongoInstrumentationModuleSpec.scala @@ -0,0 +1,220 @@ +package io.kinoplan.utils.reactivemongo.opentelemetry.javaagent.extension + +import scala.concurrent.Future + +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.trace.SpanKind +import io.opentelemetry.javaagent.testing.common.AgentTestingExporterAccess +import io.opentelemetry.sdk.trace.data.SpanData +import org.scalatest.{Assertion, BeforeAndAfterAll, BeforeAndAfterEach} +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AsyncWordSpec +import org.testcontainers.containers.GenericContainer +import org.testcontainers.utility.DockerImageName +import reactivemongo.api.AsyncDriver +import reactivemongo.api.bson.collection.BSONCollection +import reactivemongo.api.bson.document + +class ReactivemongoInstrumentationModuleSpec + extends AsyncWordSpec + with Matchers + with BeforeAndAfterAll + with BeforeAndAfterEach { + + var container: GenericContainer[Nothing] = _ + val driver: AsyncDriver = AsyncDriver() + + def tools: Future[BSONCollection] = driver + .connect(s"mongodb://${container.getHost}:${container.getFirstMappedPort}") + .flatMap(_.database("test")) + .map(_.collection[BSONCollection]("tools")) + + override def beforeAll(): Unit = { + super.beforeAll() + + val MONGODB_IMAGE = DockerImageName.parse("mongo:5.0") + container = new GenericContainer(MONGODB_IMAGE).withExposedPorts(27017) + container.start() + } + + override def afterAll(): Unit = { + container.stop() + super.afterAll() + } + + override protected def beforeEach(): Unit = { + AgentTestingExporterAccess.reset() + super.beforeEach() + } + + private def checkHasException(span: SpanData): Assertion = span + .getEvents + .get(0) + .getAttributes + .get(AttributeKey.stringKey("exception.message")) should not be null + + private def checkSpan(span: SpanData)( + dbOperationName: String, + dbNamespace: String, + extraChecks: SpanData => Assertion* + ): Assertion = { + span.getKind shouldBe SpanKind.CLIENT + span.getAttributes.get(AttributeKey.stringKey("db.namespace")) shouldBe dbNamespace + span.getAttributes.get(AttributeKey.stringKey("db.operation.name")) shouldBe dbOperationName + span.getAttributes.get(AttributeKey.stringKey("db.system")) shouldBe "mongodb" + extraChecks.map(_.apply(span)) + span.getAttributes.get(AttributeKey.stringKey("db.query.text")) should not be null + } + + private def checkCollectionSpan( + span: SpanData + )(dbOperationName: String, extraChecks: SpanData => Assertion*): Assertion = checkSpan(span)( + dbOperationName, + "test", + Seq[SpanData => Assertion]( + _.getName shouldBe s"$dbOperationName test.tools", + _.getAttributes.get(AttributeKey.stringKey("db.collection.name")) shouldBe "tools" + ) ++ extraChecks: _* + ) + + private def testOp(dbOperationName: String, extraChecks: SpanData => Assertion*)( + op: => Future[Unit] + ): Future[Assertion] = op.map { _ => + val spans = AgentTestingExporterAccess.getExportedSpans + // we need only the last created span in common case + val span = spans.get(spans.size() - 1) + checkCollectionSpan(span)(dbOperationName, extraChecks: _*) + } + + "Reactivemongo Instrumentation Module" should { + + "create spans for drop" in + testOp("drop", checkHasException) { + for { + t <- tools + _ <- t.drop() + } yield () + } + "create spans for findAndUpdate" in + testOp("findAndModify") { + for { + t <- tools + _ <- t.insert.one(document("name" -> "kamon", "findAndUpdate" -> true)) + _ <- t.findAndUpdate( + document("name" -> "kamon", "findAndUpdate" -> true), + document("$set" -> document("name" -> "zipkin")) + ) + } yield () + } + "create spans for findAndRemove" in + testOp("findAndModify") { + for { + t <- tools + _ <- t.insert.one(document("name" -> "kamon", "findAndRemove" -> true)) + _ <- t.findAndRemove(document("name" -> "kamon", "findAndRemove" -> true)) + } yield () + } + "create spans for findAndModify" in + testOp("findAndModify") { + for { + t <- tools + _ <- t.insert.one(document("name" -> "kamon", "findAndModify" -> true)) + updateOp = t.updateModifier(document(f"$$set" -> document("name" -> "zipkin"))) + _ <- t.findAndModify(document("name" -> "kamon", "findAndModify" -> true), updateOp) + } yield () + } + + "create spans for updateOne" in + testOp("update") { + for { + t <- tools + _ <- t.insert.one(document("name" -> "kamon", "updateOne" -> true)) + _ <- t + .update + .one( + document("name" -> "kamon", "updateOne" -> true), + document("$set" -> document("name" -> "zipkin")) + ) + } yield () + } + + "create spans for updateMany" in + testOp("update") { + for { + t <- tools + _ <- t + .insert + .many( + Seq( + document("name" -> "kamon", "updateMany" -> "one"), + document("name" -> "zipkin", "updateMany" -> "two") + ) + ) + update = t.update(ordered = false) + elements <- Future.sequence( + List( + update.element( + document("updateMany" -> "one"), + document("$set" -> document("name" -> "kamon updated")) + ), + update.element( + document("updateMany" -> "two"), + document("$set" -> document("name" -> "zipkin updated")) + ) + ) + ) + _ <- update.many(elements) + } yield () + } + + "create spans for insertOne" in + testOp("insert") { + for { + t <- tools + _ <- t.insert.one(document("name" -> "kamon", "insertOne" -> true)) + } yield () + } + + "create spans for insertMany" in + testOp("insert") { + for { + t <- tools + _ <- t + .insert + .many( + Seq( + document("name" -> "kamon", "insertMany" -> "one"), + document("name" -> "kamon", "insertMany" -> "two"), + document("name" -> "kamon", "insertMany" -> "three") + ) + ) + } yield () + } + + "create spans for countDocuments" in + testOp("count") { + for { + t <- tools + _ <- t + .insert + .many( + Seq( + document("name" -> "kamon", "count" -> true), + document("name" -> "kamon", "count" -> true), + document("name" -> "kamon", "count" -> true) + ) + ) + _ <- t.count(Some(document("count" -> true))) + } yield () + } + + // TODO find out why it throws error + /* "create spans for distinct" in { + for { + t <- tools + _ <- t.distinct[BSONDocument, List]("name", None) + } yield true shouldBe true + }*/ + } + +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index e97fdbed..b4156418 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -5,17 +5,19 @@ import sbt.{Def, *} object Dependencies { object Versions { - val circeV = "0.14.10" - val kamonV = "2.7.5" - val logbackV = "1.5.15" - val openTelemetryV = "1.45.0" - val openTelemetrySemconvV = "1.29.0-alpha" - val reactivemongoV = "1.1.0-RC13" - val scalaJavaTimeV = "2.6.0" - val sttpV = "3.10.2" - val tapirV = "1.11.11" - val zioV = "2.1.14" - val zioConfigV = "4.0.3" + val circeV = "0.14.10" + val kamonV = "2.7.5" + val logbackV = "1.5.15" + val openTelemetryV = "1.45.0" + val openTelemetryJavaagentV = "2.11.0" + val openTelemetryJavaagentAlphaV = "2.11.0-alpha" + val openTelemetrySemconvV = "1.29.0-alpha" + val reactivemongoV = "1.1.0-RC13" + val scalaJavaTimeV = "2.6.0" + val sttpV = "3.10.2" + val tapirV = "1.11.11" + val zioV = "2.1.14" + val zioConfigV = "4.0.3" } import Versions.* @@ -40,55 +42,60 @@ object Dependencies { val zioTestSbt = Def.setting("dev.zio" %%% "zio-test-sbt" % zioV) // A -> Z - val http4sBlazeServer = "org.http4s" %% "http4s-blaze-server" % "0.23.17" - val http4sDsl = "org.http4s" %% "http4s-dsl" % "0.23.30" - val http4sServer = "org.http4s" %% "http4s-server" % "0.23.30" - val jacksonModule = "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.18.2" - val jodaTime = "joda-time" % "joda-time" % "2.13.0" - val kamonCore = "io.kamon" %% "kamon-core" % kamonV - val kamonInstrumentationCommon = "io.kamon" %% "kamon-instrumentation-common" % kamonV - val kamonTestkit = "io.kamon" %% "kamon-testkit" % kamonV % Test - val kanelaAgent = "io.kamon" % "kanela-agent" % "1.0.18" - val kindProjector = "org.typelevel" %% "kind-projector" % "0.13.3" - val logbackClassic = "ch.qos.logback" % "logback-classic" % logbackV - val logbackCore = "ch.qos.logback" % "logback-core" % logbackV - val micrometerRegistryPrometheus = "io.micrometer" % "micrometer-registry-prometheus" % "1.14.2" - val mockitoScala = "org.scalatestplus" %% "mockito-3-4" % "3.2.10.0" % Test - val openTelemetryApi = "io.opentelemetry" % "opentelemetry-api" % openTelemetryV - val openTelemetryExporterOtlp = "io.opentelemetry" % "opentelemetry-exporter-otlp" % openTelemetryV - val openTelemetryExporterLoggingOtlp = "io.opentelemetry" % "opentelemetry-exporter-logging-otlp" % openTelemetryV - val openTelemetrySdk = "io.opentelemetry" % "opentelemetry-sdk" % openTelemetryV - val openTelemetrySemconv = "io.opentelemetry.semconv" % "opentelemetry-semconv" % openTelemetrySemconvV - val openTelemetrySemconvIncubating = "io.opentelemetry.semconv" % "opentelemetry-semconv-incubating" % openTelemetrySemconvV - val play = "org.playframework" %% "play" % "3.0.6" - val play2 = "com.typesafe.play" %% "play" % "2.8.22" - val playJson = "org.playframework" %% "play-json" % "3.0.4" - val play2Json = "com.typesafe.play" %% "play-json" % "2.10.6" - val playReactiveMongo = "org.reactivemongo" %% "play2-reactivemongo" % "1.1.0-play30.RC13" - val play2ReactiveMongo = "org.reactivemongo" %% "play2-reactivemongo" % "1.1.0-play28.RC13" - val prometheusExporterHttpServer = "io.prometheus" % "prometheus-metrics-exporter-httpserver" % "1.3.5" - val reactiveMongo = "org.reactivemongo" %% "reactivemongo" % "1.1.0-pekko.RC13" - val reactiveMongoBsonApi = "org.reactivemongo" %% "reactivemongo-bson-api" % reactivemongoV - val redisson = "org.redisson" % "redisson" % "3.24.2" - val refined = "eu.timepit" %% "refined" % "0.11.3" - val scalaCollectionCompat = "org.scala-lang.modules" %% "scala-collection-compat" % "2.12.0" - val scalaJavaCompat = "org.scala-lang.modules" %% "scala-java8-compat" % "1.0.2" - val scalaLogging = "com.typesafe.scala-logging" %% "scala-logging" % "3.9.5" - val scalatestPlay = "org.scalatestplus.play" %% "scalatestplus-play" % "7.0.1" % Test - val scalatestPlay2 = "org.scalatestplus.play" %% "scalatestplus-play" % "5.1.0" % Test - val sourcecode = "com.lihaoyi" %% "sourcecode" % "0.4.2" - val sttpCore = "com.softwaremill.sttp.client3" %% "core" % sttpV - val sttpSlf4jBackend = "com.softwaremill.sttp.client3" %% "slf4j-backend" % sttpV - val sttpZio = "com.softwaremill.sttp.client3" %% "zio" % sttpV - val tapirServer = "com.softwaremill.sttp.tapir" %% "tapir-server" % tapirV - val testContainersMongodb = "org.testcontainers" % "mongodb" % "1.20.0" % Test - val typesafeConfig = "com.typesafe" % "config" % "1.4.3" - val zioConfig = "dev.zio" %% "zio-config" % zioConfigV - val zioConfigMagnolia = "dev.zio" %% "zio-config-magnolia" % zioConfigV - val zioConfigEnumeratum = "dev.zio" %% "zio-config-enumeratum" % zioConfigV - val zioInteropCats = "dev.zio" %% "zio-interop-cats" % "23.1.0.3" - val zioMetricsConnectorsMicrometer = "dev.zio" %% "zio-metrics-connectors-micrometer" % "2.3.1" - val zioOpenTelemetry = "dev.zio" %% "zio-opentelemetry" % "3.1.0" + val googleAutoService = "com.google.auto.service" % "auto-service" % "1.1.1" + val http4sBlazeServer = "org.http4s" %% "http4s-blaze-server" % "0.23.17" + val http4sDsl = "org.http4s" %% "http4s-dsl" % "0.23.30" + val http4sServer = "org.http4s" %% "http4s-server" % "0.23.30" + val jacksonModule = "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.18.2" + val jodaTime = "joda-time" % "joda-time" % "2.13.0" + val kamonCore = "io.kamon" %% "kamon-core" % kamonV + val kamonInstrumentationCommon = "io.kamon" %% "kamon-instrumentation-common" % kamonV + val kamonTestkit = "io.kamon" %% "kamon-testkit" % kamonV % Test + val kanelaAgent = "io.kamon" % "kanela-agent" % "1.0.18" + val kindProjector = "org.typelevel" %% "kind-projector" % "0.13.3" + val logbackClassic = "ch.qos.logback" % "logback-classic" % logbackV + val logbackCore = "ch.qos.logback" % "logback-core" % logbackV + val micrometerRegistryPrometheus = "io.micrometer" % "micrometer-registry-prometheus" % "1.14.2" + val mockitoScala = "org.scalatestplus" %% "mockito-3-4" % "3.2.10.0" % Test + val openTelemetryApi = "io.opentelemetry" % "opentelemetry-api" % openTelemetryV + val openTelemetryExporterOtlp = "io.opentelemetry" % "opentelemetry-exporter-otlp" % openTelemetryV + val openTelemetryExporterLoggingOtlp = "io.opentelemetry" % "opentelemetry-exporter-logging-otlp" % openTelemetryV + val openTelemetryJavaagent = "io.opentelemetry.javaagent" % "opentelemetry-javaagent" % openTelemetryJavaagentV + val openTelemetryJavaagentExtensionApi = "io.opentelemetry.javaagent" % "opentelemetry-javaagent-extension-api" % openTelemetryJavaagentAlphaV + val openTelemetryJavaagentTesting = "io.opentelemetry.javaagent" % "opentelemetry-testing-common" % openTelemetryJavaagentAlphaV % Test + val openTelemetryJavaagentTestingAgent = "io.opentelemetry.javaagent" % "opentelemetry-agent-for-testing" % openTelemetryJavaagentAlphaV % Test + val openTelemetrySdk = "io.opentelemetry" % "opentelemetry-sdk" % openTelemetryV + val openTelemetrySemconv = "io.opentelemetry.semconv" % "opentelemetry-semconv" % openTelemetrySemconvV + val openTelemetrySemconvIncubating = "io.opentelemetry.semconv" % "opentelemetry-semconv-incubating" % openTelemetrySemconvV + val play = "org.playframework" %% "play" % "3.0.6" + val play2 = "com.typesafe.play" %% "play" % "2.8.22" + val playJson = "org.playframework" %% "play-json" % "3.0.4" + val play2Json = "com.typesafe.play" %% "play-json" % "2.10.6" + val playReactiveMongo = "org.reactivemongo" %% "play2-reactivemongo" % "1.1.0-play30.RC13" + val play2ReactiveMongo = "org.reactivemongo" %% "play2-reactivemongo" % "1.1.0-play28.RC13" + val prometheusExporterHttpServer = "io.prometheus" % "prometheus-metrics-exporter-httpserver" % "1.3.5" + val reactiveMongo = "org.reactivemongo" %% "reactivemongo" % "1.1.0-pekko.RC13" + val reactiveMongoBsonApi = "org.reactivemongo" %% "reactivemongo-bson-api" % reactivemongoV + val redisson = "org.redisson" % "redisson" % "3.24.2" + val refined = "eu.timepit" %% "refined" % "0.11.3" + val scalaCollectionCompat = "org.scala-lang.modules" %% "scala-collection-compat" % "2.12.0" + val scalaJavaCompat = "org.scala-lang.modules" %% "scala-java8-compat" % "1.0.2" + val scalaLogging = "com.typesafe.scala-logging" %% "scala-logging" % "3.9.5" + val scalatestPlay = "org.scalatestplus.play" %% "scalatestplus-play" % "7.0.1" % Test + val scalatestPlay2 = "org.scalatestplus.play" %% "scalatestplus-play" % "5.1.0" % Test + val sourcecode = "com.lihaoyi" %% "sourcecode" % "0.4.2" + val sttpCore = "com.softwaremill.sttp.client3" %% "core" % sttpV + val sttpSlf4jBackend = "com.softwaremill.sttp.client3" %% "slf4j-backend" % sttpV + val sttpZio = "com.softwaremill.sttp.client3" %% "zio" % sttpV + val tapirServer = "com.softwaremill.sttp.tapir" %% "tapir-server" % tapirV + val testContainersMongodb = "org.testcontainers" % "mongodb" % "1.20.0" % Test + val typesafeConfig = "com.typesafe" % "config" % "1.4.3" + val zioConfig = "dev.zio" %% "zio-config" % zioConfigV + val zioConfigMagnolia = "dev.zio" %% "zio-config-magnolia" % zioConfigV + val zioConfigEnumeratum = "dev.zio" %% "zio-config-enumeratum" % zioConfigV + val zioInteropCats = "dev.zio" %% "zio-interop-cats" % "23.1.0.3" + val zioMetricsConnectorsMicrometer = "dev.zio" %% "zio-metrics-connectors-micrometer" % "2.3.1" + val zioOpenTelemetry = "dev.zio" %% "zio-opentelemetry" % "3.1.0" } object Batches { diff --git a/project/ModulesCommon.scala b/project/ModulesCommon.scala index 7153982b..13d4ad49 100644 --- a/project/ModulesCommon.scala +++ b/project/ModulesCommon.scala @@ -1,10 +1,10 @@ import Dependencies.Libraries import com.lightbend.sbt.javaagent.JavaAgent import com.lightbend.sbt.javaagent.JavaAgent.JavaAgentKeys.javaAgents -import sbt.Keys.* +import locales.LocalesPlugin.autoImport.* import locales.{CLDRVersion, LocalesFilter, LocalesPlugin} import sbt.* -import locales.LocalesPlugin.autoImport.* +import sbt.Keys.* object ModulesCommon { @@ -153,6 +153,33 @@ object ModulesCommon { ) ) + lazy val reactivemongoOpentelemetryJavaagentExtensionProfile: Project => Project = _ + .configure(ProjectSettings.commonProfile) + .enablePlugins(JavaAgent) + .settings(name := "utils-reactivemongo-opentelemetry-javaagent-extension") + .settings(javaAgents := Seq(Libraries.openTelemetryJavaagentTestingAgent)) + .settings( + libraryDependencies ++= + Seq( + Libraries.googleAutoService % Provided, + Libraries.openTelemetryJavaagentExtensionApi % Provided, + Libraries.openTelemetryJavaagentTesting, + Libraries.openTelemetryJavaagentTestingAgent, + Libraries.openTelemetrySdk % Provided, + Libraries.logbackClassic % Test, + Libraries.reactiveMongo % Provided, + Libraries.testContainersMongodb + ) + ) + .settings( + Test / javaOptions ++= + Seq( + s"-Dotel.javaagent.extensions=${(Compile / packageBin).value.getAbsolutePath}", + "-Dio.opentelemetry.javaagent.shaded.io.opentelemetry.context.enableStrictContext=false", + "-Dotel.semconv-stability.opt-in=database" + ) + ) + lazy val redissonCoreProfile: Project => Project = _ .configure(ProjectSettings.commonProfile) .settings(name := "utils-redisson-core")