diff --git a/build.sbt b/build.sbt index d864de08d..74209d479 100644 --- a/build.sbt +++ b/build.sbt @@ -301,6 +301,7 @@ lazy val oteljava = project name := "otel4s-oteljava", libraryDependencies ++= Seq( "io.opentelemetry" % "opentelemetry-sdk" % OpenTelemetryVersion, + "io.opentelemetry" % "opentelemetry-sdk-extension-autoconfigure" % OpenTelemetryVersion, "io.opentelemetry" % "opentelemetry-sdk-testing" % OpenTelemetryVersion % Test ) ) diff --git a/docs/customization/histogram-custom-buckets/README.md b/docs/customization/histogram-custom-buckets/README.md index a0ac79d5a..0ce40f8a1 100644 --- a/docs/customization/histogram-custom-buckets/README.md +++ b/docs/customization/histogram-custom-buckets/README.md @@ -117,8 +117,6 @@ import cats.effect.std.Random import cats.syntax.flatMap._ import cats.syntax.functor._ import cats.syntax.parallel._ -import io.opentelemetry.sdk.OpenTelemetrySdk -import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk import io.opentelemetry.sdk.metrics.Aggregation import io.opentelemetry.sdk.metrics.InstrumentSelector import io.opentelemetry.sdk.metrics.InstrumentType @@ -146,9 +144,7 @@ object HistogramBucketsExample extends IOApp.Simple { } yield () def program[F[_] : Async : LiftIO : Parallel : Console]: F[Unit] = - Resource - .eval(configureSdk[F]) - .evalMap(OtelJava.forAsync[F]) + configureSdk[F] .evalMap(_.meterProvider.get("histogram-example")) .use { meter => for { @@ -161,32 +157,30 @@ object HistogramBucketsExample extends IOApp.Simple { def run: IO[Unit] = program[IO] - private def configureSdk[F[_] : Sync]: F[OpenTelemetrySdk] = Sync[F].delay { - AutoConfiguredOpenTelemetrySdk - .builder() - .addMeterProviderCustomizer { (meterProviderBuilder, _) => - meterProviderBuilder - .registerView( - InstrumentSelector - .builder() - .setName("service.work.duration") - .setType(InstrumentType.HISTOGRAM) - .build(), - View - .builder() - .setName("service.work.duration") - .setAggregation( - Aggregation.explicitBucketHistogram( - java.util.Arrays.asList(.005, .01, .025, .05, .075, .1, .25, .5) + private def configureSdk[F[_] : Async : LiftIO]: Resource[F, OtelJava[F]] = + OtelJava.autoConfigured { sdkBuilder => + sdkBuilder + .addMeterProviderCustomizer { (meterProviderBuilder, _) => + meterProviderBuilder + .registerView( + InstrumentSelector + .builder() + .setName("service.work.duration") + .setType(InstrumentType.HISTOGRAM) + .build(), + View + .builder() + .setName("service.work.duration") + .setAggregation( + Aggregation.explicitBucketHistogram( + java.util.Arrays.asList(.005, .01, .025, .05, .075, .1, .25, .5) + ) ) - ) - .build() - ) - } - .setResultAsGlobal - .build() - .getOpenTelemetrySdk - } + .build() + ) + } + .setResultAsGlobal + } } ``` diff --git a/docs/examples/grafana/README.md b/docs/examples/grafana/README.md index 9ec361c5e..5e74f5ed2 100644 --- a/docs/examples/grafana/README.md +++ b/docs/examples/grafana/README.md @@ -239,26 +239,28 @@ object ApiService { object ExampleService extends IOApp.Simple { def run: IO[Unit] = - OtelJava.global.flatMap { otel4s => - ( - otel4s.tracerProvider.get("com.service.runtime"), - otel4s.meterProvider.get("com.service.runtime"), - Random.scalaUtilRandom[IO] - ).flatMapN { case components => - implicit val (tracer: Tracer[IO], meter: Meter[IO], random: Random[IO]) = - components - - for { - service <- ApiService[IO]( - minLatency = 40, - maxLatency = 80, - bananaPercentage = 70 - ) - data <- service.getDataFromSomeAPI - _ <- IO.println(s"Service data: $data") - } yield () + OtelJava.autoConfigured() + .evalMap { otel4s => + ( + otel4s.tracerProvider.get("com.service.runtime"), + otel4s.meterProvider.get("com.service.runtime"), + Random.scalaUtilRandom[IO] + ).flatMapN { case components => + implicit val (tracer: Tracer[IO], meter: Meter[IO], random: Random[IO]) = + components + + for { + service <- ApiService[IO]( + minLatency = 40, + maxLatency = 80, + bananaPercentage = 70 + ) + data <- service.getDataFromSomeAPI + _ <- IO.println(s"Service data: $data") + } yield () + } } - } + .use_ } ``` diff --git a/docs/examples/honeycomb/README.md b/docs/examples/honeycomb/README.md index 706d5f067..61aec1eed 100644 --- a/docs/examples/honeycomb/README.md +++ b/docs/examples/honeycomb/README.md @@ -135,16 +135,18 @@ object Work { object TracingExample extends IOApp.Simple { def run: IO[Unit] = { - OtelJava.global.flatMap { otel4s => - otel4s.tracerProvider.get("com.service.runtime") - .flatMap { implicit tracer: Tracer[IO] => - for { - meter <- otel4s.meterProvider.get("com.service.runtime") - histogram <- meter.histogram("work.execution.duration").create - _ <- Work[IO](histogram).doWork - } yield () - } - } + OtelJava.autoConfigured() + .evalMap { otel4s => + otel4s.tracerProvider.get("com.service.runtime") + .flatMap { implicit tracer: Tracer[IO] => + for { + meter <- otel4s.meterProvider.get("com.service.runtime") + histogram <- meter.histogram("work.execution.duration").create + _ <- Work[IO](histogram).doWork + } yield () + } + } + .use_ } } ``` diff --git a/docs/examples/jaeger-docker/README.md b/docs/examples/jaeger-docker/README.md index cfd02a7b4..21e894e96 100644 --- a/docs/examples/jaeger-docker/README.md +++ b/docs/examples/jaeger-docker/README.md @@ -76,7 +76,7 @@ $ docker run --name jaeger \ ### Application example ```scala mdoc:silent -import cats.effect.{Async, IO, IOApp} +import cats.effect.{Async, IO, IOApp, Resource} import cats.effect.std.Console import cats.effect.std.Random import cats.syntax.all._ @@ -118,14 +118,15 @@ object Work { } object TracingExample extends IOApp.Simple { - def tracer: IO[Tracer[IO]] = - OtelJava.global.flatMap(_.tracerProvider.get("Example")) + def tracer: Resource[IO, Tracer[IO]] = + OtelJava.autoConfigured().evalMap(_.tracerProvider.get("Example")) - def run: IO[Unit] = { - tracer.flatMap { implicit tracer: Tracer[IO] => - Work[IO].doWork - } - } + def run: IO[Unit] = + tracer + .evalMap { implicit tracer: Tracer[IO] => + Work[IO].doWork + } + .use_ } ``` diff --git a/docs/instrumentation/tracing.md b/docs/instrumentation/tracing.md index 0748c50bf..582e4d39e 100644 --- a/docs/instrumentation/tracing.md +++ b/docs/instrumentation/tracing.md @@ -49,12 +49,13 @@ import cats.effect.IO import org.typelevel.otel4s.trace.Tracer import org.typelevel.otel4s.oteljava.OtelJava -OtelJava.global.flatMap { otel4s => - otel4s.tracerProvider.get("com.service").flatMap { implicit tracer: Tracer[IO] => - val _ = tracer // use tracer here - ??? +OtelJava.autoConfigured[IO]() + .evalMap { otel4s => + otel4s.tracerProvider.get("com.service").flatMap { implicit tracer: Tracer[IO] => + val _ = tracer // use tracer here + ??? + } } -} ``` ### Creating a span diff --git a/examples/src/main/scala/ObservableExample.scala b/examples/src/main/scala/ObservableExample.scala index 177c8d73d..7df1ac8f6 100644 --- a/examples/src/main/scala/ObservableExample.scala +++ b/examples/src/main/scala/ObservableExample.scala @@ -30,8 +30,8 @@ object ObservableExample extends IOApp.Simple { val mbeanName = new ObjectName("cats.effect.metrics:type=CpuStarvation") def meterResource: Resource[IO, ObservableCounter] = - Resource - .eval(OtelJava.global) + OtelJava + .autoConfigured() .evalMap(_.meterProvider.get("observable-example")) .flatMap( _.observableCounter("cats-effect-runtime-cpu-starvation-count") diff --git a/examples/src/main/scala/TraceExample.scala b/examples/src/main/scala/TraceExample.scala index c23d86d7a..ad6e788ec 100644 --- a/examples/src/main/scala/TraceExample.scala +++ b/examples/src/main/scala/TraceExample.scala @@ -124,31 +124,33 @@ object TraceExample extends IOApp.Simple { * to acquire and then 100 to shutdown, but in the middle are the child spans * from our UserIdsAlg. */ - def run: IO[Unit] = { - OtelJava.global.flatMap { (otel4s: Otel4s[IO]) => - otel4s.tracerProvider.tracer("TraceExample").get.flatMap { - implicit tracer: Tracer[IO] => - val userIdAlg = UserIdsAlg.apply[IO]( - InstitutionServiceClient.apply[IO], - UserDatabase.apply[IO] - ) - tracer - .span("Start up") - .use { span => - for { - _ <- tracer.span("acquire").surround(IO.sleep(50.millis)) - _ <- span.addEvent("event") - _ <- tracer.span("use").surround { - userIdAlg - .getAllUsersForInstitution( - "9902181e-1d8d-4e00-913d-51532b493f1b" - ) - .flatMap(IO.println) - } - _ <- tracer.span("release").surround(IO.sleep(100.millis)) - } yield () - } + def run: IO[Unit] = + OtelJava + .autoConfigured() + .evalMap { (otel4s: Otel4s[IO]) => + otel4s.tracerProvider.tracer("TraceExample").get.flatMap { + implicit tracer: Tracer[IO] => + val userIdAlg = UserIdsAlg.apply[IO]( + InstitutionServiceClient.apply[IO], + UserDatabase.apply[IO] + ) + tracer + .span("Start up") + .use { span => + for { + _ <- tracer.span("acquire").surround(IO.sleep(50.millis)) + _ <- span.addEvent("event") + _ <- tracer.span("use").surround { + userIdAlg + .getAllUsersForInstitution( + "9902181e-1d8d-4e00-913d-51532b493f1b" + ) + .flatMap(IO.println) + } + _ <- tracer.span("release").surround(IO.sleep(100.millis)) + } yield () + } + } } - } - } + .use_ } diff --git a/examples/src/main/scala/TracingExample.scala b/examples/src/main/scala/TracingExample.scala index 49655e1fd..abf305653 100644 --- a/examples/src/main/scala/TracingExample.scala +++ b/examples/src/main/scala/TracingExample.scala @@ -57,33 +57,35 @@ object Work { } object TracingExample extends IOApp.Simple { - def run: IO[Unit] = { - OtelJava.global.flatMap { (otel4s: Otel4s[IO]) => - otel4s.tracerProvider.tracer("example").get.flatMap { - implicit tracer: Tracer[IO] => - tracer - .span("resource") - .resource - .use { res => - res.trace { - for { - _ <- tracer.span("acquire").surround(IO.sleep(50.millis)) - _ <- tracer.span("use").surround { - Work[IO].request( - Map( - "X-B3-TraceId" -> "80f198ee56343ba864fe8b2a57d3eff7", - "X-B3-ParentSpanId" -> "05e3ac9a4f6e3b90", - "X-B3-SpanId" -> "e457b5a2e4d86bd1", - "X-B3-Sampled" -> "1" + def run: IO[Unit] = + OtelJava + .autoConfigured() + .evalMap { (otel4s: Otel4s[IO]) => + otel4s.tracerProvider.tracer("example").get.flatMap { + implicit tracer: Tracer[IO] => + tracer + .span("resource") + .resource + .use { res => + res.trace { + for { + _ <- tracer.span("acquire").surround(IO.sleep(50.millis)) + _ <- tracer.span("use").surround { + Work[IO].request( + Map( + "X-B3-TraceId" -> "80f198ee56343ba864fe8b2a57d3eff7", + "X-B3-ParentSpanId" -> "05e3ac9a4f6e3b90", + "X-B3-SpanId" -> "e457b5a2e4d86bd1", + "X-B3-Sampled" -> "1" + ) ) - ) - } - _ <- res.span.addEvent("event") - _ <- tracer.span("release").surround(IO.sleep(100.millis)) - } yield () + } + _ <- res.span.addEvent("event") + _ <- tracer.span("release").surround(IO.sleep(100.millis)) + } yield () + } } - } + } } - } - } + .use_ } diff --git a/oteljava/all/src/main/scala/org/typelevel/otel4s/oteljava/OtelJava.scala b/oteljava/all/src/main/scala/org/typelevel/otel4s/oteljava/OtelJava.scala index 60b2f1b16..39b57d98c 100644 --- a/oteljava/all/src/main/scala/org/typelevel/otel4s/oteljava/OtelJava.scala +++ b/oteljava/all/src/main/scala/org/typelevel/otel4s/oteljava/OtelJava.scala @@ -25,6 +25,12 @@ import cats.syntax.all._ import io.opentelemetry.api.{OpenTelemetry => JOpenTelemetry} import io.opentelemetry.api.GlobalOpenTelemetry import io.opentelemetry.sdk.{OpenTelemetrySdk => JOpenTelemetrySdk} +import io.opentelemetry.sdk.autoconfigure.{ + AutoConfiguredOpenTelemetrySdk => AutoConfigOtelSdk +} +import io.opentelemetry.sdk.autoconfigure.{ + AutoConfiguredOpenTelemetrySdkBuilder => AutoConfigOtelSdkBuilder +} import io.opentelemetry.sdk.common.CompletableResultCode import org.typelevel.otel4s.Otel4s import org.typelevel.otel4s.context.propagation.ContextPropagators @@ -97,8 +103,38 @@ object OtelJava { ) .evalMap(forAsync[F]) + /** Creates a [[cats.effect.Resource `Resource`]] of the automatic + * configuration of a Java `OpenTelemetrySdk` instance. + * + * If you rely on + * [[https://opentelemetry.io/docs/instrumentation/java/automatic/ automatic instrumentation via Java agent]], + * you MUST NOT use this method and MUST use [[global]] instead. + * + * @param customize + * A function for customizing the auto-configured SDK builder. This + * function MUST NOT call `setResultAsGlobal`. + * @return + * An [[org.typelevel.otel4s.Otel4s]] resource. + * @see + * [[global]] + */ + def autoConfigured[F[_]: LiftIO: Async]( + customize: AutoConfigOtelSdkBuilder => AutoConfigOtelSdkBuilder = identity + ): Resource[F, OtelJava[F]] = + resource { + Sync[F].delay { + customize(AutoConfigOtelSdk.builder()) + .disableShutdownHook() + .build() + .getOpenTelemetrySdk + } + } + /** Creates an [[org.typelevel.otel4s.Otel4s]] from the global Java * OpenTelemetry instance. + * + * @see + * [[autoConfigured]] */ def global[F[_]: LiftIO: Async]: F[OtelJava[F]] = Sync[F].delay(GlobalOpenTelemetry.get).flatMap(forAsync[F])