### sbt Project Setup Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/grafana/README.md Add the following settings to your build.sbt file to configure the project for the Grafana example. ```scala libraryDependencies ++= Seq( "org.typelevel" %% "otel4s-oteljava" % "@VERSION@", // <1> "io.opentelemetry" % "opentelemetry-exporter-otlp" % "@OPEN_TELEMETRY_VERSION@" % Runtime, // <2> "io.opentelemetry" % "opentelemetry-sdk-extension-autoconfigure" % "@OPEN_TELEMETRY_VERSION@" % Runtime // <3> ) run / fork := true javaOptions += "-Dotel.java.global-autoconfigure.enabled=true" // <4> javaOptions += "-Dotel.service.name=grafana-example" // <5> javaOptions += "-Dotel.exporter.otlp.endpoint=http://localhost:4317" // <6> ``` -------------------------------- ### sbt Project Setup Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/honeycomb/README.md Dependencies and JVM options for sbt to integrate with Honeycomb via otel4s. ```scala libraryDependencies ++= Seq( "org.typelevel" %% "otel4s-oteljava" % "@VERSION@", // <1> "io.opentelemetry" % "opentelemetry-exporter-otlp" % "@OPEN_TELEMETRY_VERSION@" % Runtime, // <2> "io.opentelemetry" % "opentelemetry-sdk-extension-autoconfigure" % "@OPEN_TELEMETRY_VERSION@" % Runtime // <3> ) run / fork := true javaOptions += "-Dotel.java.global-autoconfigure.enabled=true" // <4> javaOptions += "-Dotel.service.name=honeycomb-example" // <5> javaOptions += "-Dotel.exporter.otlp.endpoint=https://api.honeycomb.io/" // <6> ``` -------------------------------- ### Scala CLI Project Setup Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/honeycomb/README.md Directives for Scala CLI to integrate with Honeycomb via otel4s. ```scala //> using dep "org.typelevel::otel4s-oteljava:@VERSION@" // <1> //> using dep "io.opentelemetry:opentelemetry-exporter-otlp:@OPEN_TELEMETRY_VERSION@" // <2> //> using dep "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:@OPEN_TELEMETRY_VERSION@" // <3> //> using javaOpt "-Dotel.java.global-autoconfigure.enabled=true" // <4> //> using javaOpt "-Dotel.service.name=honeycomb-example" // <5> //> using javaOpt "-Dotel.exporter.otlp.endpoint=https://api.honeycomb.io/" // <6> ``` -------------------------------- ### Scala CLI Project Setup Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/grafana/README.md Add the following directives to your grafana.scala file for Scala CLI project setup. ```scala //> using scala 3.3.0 //> using dep "org.typelevel::otel4s-oteljava:@VERSION@" // <1> //> using dep "io.opentelemetry:opentelemetry-exporter-otlp:@OPEN_TELEMETRY_VERSION@" // <2> //> using dep "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:@OPEN_TELEMETRY_VERSION@" // <3> //> using javaOpt "-Dotel.java.global-autoconfigure.enabled=true" // <4> //> using javaOpt "-Dotel.service.name=grafana-example" // <5> //> using javaOpt "-Dotel.exporter.otlp.endpoint=http://localhost:4317" // <6> ``` -------------------------------- ### Starting a root span using rootScope Source: https://github.com/typelevel/otel4s/blob/main/docs/instrumentation/tracing.md Example of starting a root span using Tracer[F].rootScope to wrap an existing effect. This creates an independent root span. ```scala import cats.Monad import cats.syntax.flatMap._ import cats.syntax.functor._ class UserRequestHandler[F[_]: Tracer: Monad](repo: UserRepository[F]) { private val SystemUserId = -1L def handleUser(userId: Long): F[Unit] = Tracer[F].rootScope(activateUser(userId)) def handleUserInternal(userId: Long): F[Unit] = Tracer[F].rootSpan("handle-user").surround(activateUser(userId)) private def activateUser(userId: Long): F[Unit] = for { systemUser <- repo.findUser(SystemUserId) user <- repo.findUser(userId) _ <- activate(systemUser, user) } yield () private def activate(systemUser: Option[User], target: Option[User]): F[Unit] = { val _ = (systemUser, target) // some processing logic Monad[F].unit } } ``` -------------------------------- ### Application Example Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/honeycomb/README.md This Scala code demonstrates setting up and using otel4s with Honeycomb for tracing and metrics. It defines a `Work` trait and an example application `TracingExample` that utilizes `Tracer` and `Histogram`. ```scala import java.util.concurrent.TimeUnit import cats.effect.{Async, IO, IOApp} import cats.effect.std.Console import cats.effect.std.Random import cats.syntax.all._ import org.typelevel.otel4s.{Attribute, AttributeKey} import org.typelevel.otel4s.oteljava.OtelJava import org.typelevel.otel4s.metrics.Histogram import org.typelevel.otel4s.trace.Tracer import scala.concurrent.duration._ trait Work[F[_]] { def doWork: F[Unit] } object Work { def apply[F[_]: Async: Tracer: Console](histogram: Histogram[F, Double]): Work[F] = new Work[F] { def doWork: F[Unit] = Tracer[F].span("Work.DoWork").use { span => span.addEvent("Starting the work.") *> doWorkInternal(steps = 10) *> span.addEvent("Finished working.") } def doWorkInternal(steps: Int): F[Unit] = { val step = Tracer[F] .span("internal", Attribute(AttributeKey.long("steps"), steps.toLong)) .surround { for { random <- Random.scalaUtilRandom delay <- random.nextIntBounded(1000) _ <- Async[F].sleep(delay.millis) _ <- Console[F].println("Doin' work") } yield () } val metered = histogram.recordDuration(TimeUnit.MILLISECONDS).surround(step) if (steps > 0) metered *> doWorkInternal(steps - 1) else metered } } } object TracingExample extends IOApp.Simple { def run: IO[Unit] = { OtelJava .autoConfigured[IO]() .evalMap { otel4s => otel4s.tracerProvider.get("com.service.runtime") .flatMap { implicit tracer: Tracer[IO] => for { meter <- otel4s.meterProvider.get("com.service.runtime") histogram <- meter.histogram[Double]("work.execution.duration").create _ <- Work[IO](histogram).doWork } yield () } } .use_ } } ``` -------------------------------- ### Constructing Local[F, Context] for manual context sharing Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/tracing-java-interop.md This example shows how to obtain direct access to `Local[F, Context]` which is necessary for manually sharing context between otel4s and the Java SDK. It demonstrates the setup required before implementing interoperability solutions. ```scala import cats.effect._ import cats.mtl.Local import org.typelevel.otel4s.oteljava.context.Context import org.typelevel.otel4s.oteljava.OtelJava def program[F[_]: Async](otel4s: OtelJava[F])(implicit L: Local[F, Context]): F[Unit] = { val _ = (otel4s, L) // both OtelJava and Local[F, Context] are available here Async[F].unit } val run: IO[Unit] = OtelJava.global[IO].flatMap { import otel4s.localContext otel4s => program(otel4s) } ``` -------------------------------- ### Application Example Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/dash0/README.md A Scala code example demonstrating how to instrument an application with OpenTelemetry for tracing and metrics using otel4s. ```scala import java.util.concurrent.TimeUnit import cats.effect.{Async, IO, IOApp} import cats.effect.std.Console import cats.effect.std.Random import cats.syntax.all._ import org.typelevel.otel4s.{Attribute, AttributeKey} import org.typelevel.otel4s.oteljava.OtelJava import org.typelevel.otel4s.metrics.Histogram import org.typelevel.otel4s.trace.Tracer import scala.concurrent.duration._ trait Work[F[_]] { def doWork: F[Unit] } object Work { def apply[F[_]: Async: Tracer: Console](histogram: Histogram[F, Double]): Work[F] = new Work[F] { def doWork: F[Unit] = Tracer[F].span("Work.DoWork").use { span => span.addEvent("Starting the work.") *> doWorkInternal(steps = 10) *> span.addEvent("Finished working.") } def doWorkInternal(steps: Int): F[Unit] = { val step = Tracer[F] .span("internal", Attribute(AttributeKey.long("steps"), steps.toLong)) .surround { for { random <- Random.scalaUtilRandom delay <- random.nextIntBounded(1000) _ <- Async[F].sleep(delay.millis) _ <- Console[F].println("Doin' work") } yield () } val metered = histogram.recordDuration(TimeUnit.MILLISECONDS).surround(step) if (steps > 0) metered *> doWorkInternal(steps - 1) else metered } } } object TracingExample extends IOApp.Simple { def run: IO[Unit] = { OtelJava .autoConfigured[IO]() .evalMap { otel4s => otel4s.tracerProvider.get("com.service.runtime") .flatMap { implicit tracer: Tracer[IO] => for { meter <- otel4s.meterProvider.get("com.service.runtime") histogram <- meter.histogram[Double]("work.execution.duration").create _ <- Work[IO](histogram).doWork } yield () } } .use_ } } ``` -------------------------------- ### Basic flow example Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-metrics.md Example demonstrating the basic flow of using the metrics testkit. ```scala import cats.effect.IO import io.opentelemetry.sdk.metrics.data.MetricData import org.typelevel.otel4s.metrics.MeterProvider import org.typelevel.otel4s.oteljava.testkit.metrics._ def program(meterProvider: MeterProvider[IO]): IO[Unit] = for { meter <- meterProvider.get("service") counter <- meter.counter[Long]("service.counter").create _ <- counter.inc() gauge <- meter.gauge[Long]("service.gauge").create _ <- gauge.record(42L) } yield () def assertExpected(metrics: List[MetricData], expected: MetricExpectation*): Unit = MetricExpectations.checkAllDistinct(metrics, expected: _*) match { case Right(_) => () case Left(mismatches) => // or use an assert function from the testing framework here sys.error(MetricExpectations.format(mismatches)) } def test: IO[Unit] = MetricsTestkit.inMemory[IO]().use { testkit => for { _ <- program(testkit.meterProvider) metrics <- testkit.collectMetrics } yield assertExpected( metrics, MetricExpectation.sum[Long]("service.counter").value(1L), MetricExpectation.gauge[Long]("service.gauge").value(42L) ) } ``` -------------------------------- ### Scribe Integration Example Source: https://github.com/typelevel/otel4s/blob/main/docs/instrumentation/logs.md An example of how to use otel4s logging with the Scribe logging library. ```scala import org.typelevel.otel4s.Otel4s def program[F[_]: Monad](otel4s: Otel4s[F]): F[Unit] = { val logger = new ScriberLoggerSupport(otel4s.loggerProvider) otel4s.tracerProvider.get("tracer").flatMap { tracer => tracer.spanBuilder("test-span").build.surround { logger.error( "something went wrong", new RuntimeException("Oops, something went wrong") ) } } } ``` -------------------------------- ### IOLocal Global Shortcut Example Source: https://github.com/typelevel/otel4s/blob/main/docs/tracing-context-propagation.md The shortest way to initialize OtelJava using OtelJava.global. ```Scala import cats.effect._ import org.typelevel.otel4s.oteljava.OtelJava def program[F[_]: Async](otel4s: OtelJava[F]): F[Unit] = { val _ = otel4s Async[F].unit } val run: IO[Unit] = OtelJava.global[IO].flatMap(otel4s => program(otel4s)) ``` -------------------------------- ### Application Example Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/grafana/README.md Example service mocking a call to a remote API that returns apples or bananas. It uses metrics to measure the apple/banana ratio and traces to measure API latency. ```scala import cats.effect.{Async, IO, IOApp} import cats.effect.std.Random import cats.syntax.apply._ import cats.syntax.flatMap._ import cats.syntax.functor._ import org.typelevel.otel4s.Attribute import org.typelevel.otel4s.oteljava.OtelJava import org.typelevel.otel4s.metrics.Meter import org.typelevel.otel4s.trace.Tracer import java.util.concurrent.TimeUnit import scala.concurrent.duration.FiniteDuration case class ApiData(result: String) trait ApiService[F[_]] { def getDataFromSomeAPI: F[ApiData] } object ApiService { def apply[F[_]: Async: Tracer: Meter: Random]( minLatency: Int, maxLatency: Int, bananaPercentage: Int ): F[ApiService[F]] = Meter[F] .counter[Long]("RemoteApi.fruit.count") .withDescription("Number of fruits returned by the API.") .create .map { remoteApiFruitCount => new ApiService[F] { override def getDataFromSomeAPI: F[ApiData] = for { latency <- Random[F].betweenInt(minLatency, maxLatency) isBanana <- Random[F].betweenInt(0, 100).map(_ <= bananaPercentage) duration = FiniteDuration(latency, TimeUnit.MILLISECONDS) fruit <- Tracer[F].span("remoteAPI.com/fruit").surround( Async[F].sleep(duration) *> Async[F].pure(if (isBanana) "banana" else "apple") ) _ <- remoteApiFruitCount.inc(Attribute("fruit", fruit)) } yield ApiData(s"Api returned a $fruit !") } } } object ExampleService extends IOApp.Simple { def run: IO[Unit] = OtelJava.autoConfigured[IO]() .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_ } ``` -------------------------------- ### Application example Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/jaeger-docker/README.md A Scala example demonstrating how to instrument an application with otel4s to send traces to Jaeger. ```scala import cats.effect.{Async, IO, IOApp, Resource} import cats.effect.std.Console import cats.effect.std.Random import cats.syntax.all._ import org.typelevel.otel4s.Attribute import org.typelevel.otel4s.oteljava.OtelJava import org.typelevel.otel4s.trace.Tracer import scala.concurrent.duration._ trait Work[F[_]] { def doWork: F[Unit] } object Work { def apply[F[_]: Async: Tracer: Console]: Work[F] = new Work[F] { def doWork: F[Unit] = Tracer[F].span("Work.DoWork").use { span => span.addEvent("Starting the work.") *> doWorkInternal(steps = 10) *> span.addEvent("Finished working.") } def doWorkInternal(steps: Int): F[Unit] = { val step = Tracer[F] .span("internal", Attribute("steps", steps.toLong)) .surround { for { random <- Random.scalaUtilRandom delay <- random.nextIntBounded(1000) _ <- Async[F].sleep(delay.millis) _ <- Console[F].println("Doin' work") } yield () } if (steps > 0) step *> doWorkInternal(steps - 1) else step } } } object TracingExample extends IOApp.Simple { def tracer: Resource[IO, Tracer[IO]] = OtelJava.autoConfigured[IO]().evalMap(_.tracerProvider.get("Example")) def run: IO[Unit] = tracer .evalMap { implicit tracer: Tracer[IO] => Work[IO].doWork } .use_ } ``` -------------------------------- ### Message and body example 2 Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-logs.md Example using `body(...)` for structured log bodies. ```scala LogRecordExpectation.body( AnyValue.map( Map( "status" -> AnyValue.string("failed"), "count" -> AnyValue.long(2L) ) ) ) ``` -------------------------------- ### Run the application (scala-cli) Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/grafana/README.md Command to run the application using scala-cli. ```shell $ scala-cli run grafana.scala ``` -------------------------------- ### Synchronous instruments example Source: https://github.com/typelevel/otel4s/blob/main/docs/instrumentation/metrics.md Example demonstrating the use of Counter and Histogram for tracking missing users and search duration. ```scala import java.util.concurrent.TimeUnit import cats.Monad import cats.effect.{Concurrent, MonadCancelThrow, Ref} import cats.syntax.applicative._ import cats.syntax.flatMap._ import cats.syntax.functor._ import org.typelevel.otel4s.metrics.{Counter, Histogram, Meter} case class User(email: String) class UserRepository[F[_]: MonadCancelThrow]( storage: Ref[F, Map[Long, User]], missingCounter: Counter[F, Long], searchDuration: Histogram[F, Double] ) { def findUser(userId: Long): F[Option[User]] = searchDuration.recordDuration(TimeUnit.SECONDS).surround( for { current <- storage.get user <- Monad[F].pure(current.get(userId)) _ <- missingCounter.inc().whenA(user.isEmpty) } yield user ) } object UserRepository { def create[F[_]: Concurrent: Meter]: F[UserRepository[F]] = { for { storage <- Concurrent[F].ref(Map.empty[Long, User]) missing <- Meter[F].counter[Long]("user.search.missing").create duration <- Meter[F].histogram[Double]("user.search.duration").withUnit("s").create } yield new UserRepository(storage, missing, duration) } } ``` -------------------------------- ### Kleisli Example Source: https://github.com/typelevel/otel4s/blob/main/docs/tracing-context-propagation.md Demonstrates context propagation using Kleisli with OtelJava. ```Scala import cats.effect._ import cats.syntax.flatMap._ import cats.data.Kleisli import cats.mtl.Local import org.typelevel.otel4s.oteljava.context.Context import org.typelevel.otel4s.oteljava.OtelJava import io.opentelemetry.api.GlobalOpenTelemetry def createOtel4s[F[_]: Async](implicit L: Local[F, Context]): F[OtelJava[F]] = Async[F].delay(GlobalOpenTelemetry.get).flatMap(OtelJava.fromJOpenTelemetry[F]) def program[F[_]: Async](otel4s: OtelJava[F]): F[Unit] = { val _ = otel4s Async[F].unit } val kleisli: Kleisli[IO, Context, Unit] = createOtel4s[Kleisli[IO, Context, *]].flatMap(otel4s => program(otel4s)) val run: IO[Unit] = kleisli.run(Context.root) ``` -------------------------------- ### Application Example Source: https://github.com/typelevel/otel4s/blob/main/docs/customization/histogram-custom-buckets/README.md This code snippet shows a complete application example demonstrating how to configure custom bucket boundaries for a histogram using otel4s. ```scala import cats.Parallel import cats.effect._ import cats.effect.kernel.Temporal import cats.effect.std.Console import cats.effect.std.Random import cats.syntax.flatMap._ import cats.syntax.functor._ import cats.syntax.parallel._ import io.opentelemetry.sdk.metrics.Aggregation import io.opentelemetry.sdk.metrics.ExplicitBucketHistogramOptions import io.opentelemetry.sdk.metrics.InstrumentSelector import io.opentelemetry.sdk.metrics.InstrumentType import io.opentelemetry.sdk.metrics.View import org.typelevel.otel4s.oteljava.context.LocalContextProvider import org.typelevel.otel4s.oteljava.OtelJava import org.typelevel.otel4s.metrics.Histogram import java.util.concurrent.TimeUnit import scala.concurrent.duration._ object HistogramBucketsExample extends IOApp.Simple { def work[F[_]: Temporal: Console]( histogram: Histogram[F, Double], random: Random[F] ): F[Unit] = for { sleepDuration <- random.nextIntBounded(5000) _ <- histogram .recordDuration(TimeUnit.SECONDS) .surround( Temporal[F].sleep(sleepDuration.millis) >> Console[F].println(s"I'm working after [\$sleepDuration ms]") ) } yield () def program[F[_]: Async: LocalContextProvider: Parallel: Console]: F[Unit] = configureSdk[F] .evalMap(_.meterProvider.get("histogram-example")) .use { meter => for { random <- Random.scalaUtilRandom[F] histogram <- meter.histogram[Double]("service.work.duration").create _ <- work[F](histogram, random).parReplicateA_(50) } yield () } def run: IO[Unit] = program[IO] private def configureSdk[F[_]: Async: LocalContextProvider]: 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( ExplicitBucketHistogramOptions .builder() .setBucketBoundaries( java.util.Arrays.asList(.005, .01, .025, .05, .075, .1, .25, .5) ) .build() ) ) .build() ) } .setResultAsGlobal } } ``` -------------------------------- ### IOLocal Example Source: https://github.com/typelevel/otel4s/blob/main/docs/tracing-context-propagation.md Demonstrates how to use IOLocal for context propagation with OtelJava. ```Scala import cats.effect._ import cats.mtl.Local import cats.syntax.flatMap._ import org.typelevel.otel4s.oteljava.context.Context import org.typelevel.otel4s.oteljava.OtelJava import io.opentelemetry.api.GlobalOpenTelemetry def createOtel4s[F[_]: Async](implicit L: Local[F, Context]): F[OtelJava[F]] = Async[F].delay(GlobalOpenTelemetry.get).flatMap(OtelJava.fromJOpenTelemetry[F]) def program[F[_]: Async](otel4s: OtelJava[F]): F[Unit] = { val _ = otel4s Async[F].unit } val run: IO[Unit] = IOLocal(Context.root).map(_.asLocal).flatMap { implicit local: Local[IO, Context] => createOtel4s[IO].flatMap(otel4s => program(otel4s)) } ``` -------------------------------- ### Clues example Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-metrics.md An example demonstrating the use of 'clue' to add explanatory messages to metric expectations. ```scala import org.typelevel.otel4s.oteljava.testkit.metrics.{PointExpectation, PointSetExpectation} import org.typelevel.otel4s.Attribute MetricExpectation .sum[Long]("service.requests") .clue("the request counter must be emitted") .points( PointSetExpectation .exists( PointExpectation .numeric(1L) .clue("GET requests should increment the counter") .attributesExact(Attribute("http.method", "GET")) ) .clue("the GET point must be present") ) ``` -------------------------------- ### Combined testkit example Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit.md Example usage of the combined OtelJavaTestkit for collecting metrics, spans, and logs. ```scala import cats.effect.IO import org.typelevel.otel4s.oteljava.testkit.OtelJavaTestkit def test: IO[Unit] = OtelJavaTestkit.inMemory[IO]().use { testkit => for { metrics <- testkit.collectMetrics spans <- testkit.finishedSpans logs <- testkit.finishedLogs } yield { val _ = (metrics, spans, logs) } } ``` -------------------------------- ### Example of using the utility method with Pekko HTTP Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/tracing-java-interop.md This example demonstrates how to materialize an IO within a Pekko HTTP request handler using the current tracing context. ```scala import cats.effect.{Async, IO} import cats.effect.std.Random import cats.effect.syntax.temporal._ import cats.effect.unsafe.implicits.global import cats.mtl.Local import cats.syntax.all._ import org.apache.pekko.http.scaladsl.model.StatusCodes.OK import org.apache.pekko.http.scaladsl.server.Directives._ import org.apache.pekko.http.scaladsl.server.Route import org.typelevel.otel4s.Attribute import org.typelevel.otel4s.trace.Tracer import org.typelevel.otel4s.oteljava.context.Context import io.opentelemetry.instrumentation.annotations.WithSpan import io.opentelemetry.context.{Context => JContext} import scala.concurrent.duration._ def route(implicit T: Tracer[IO], L: Local[IO, Context]): Route = path("gen-random-name") { get { complete { OK -> generateRandomName(length = 10) } } } @WithSpan("generate-random-name") def generateRandomName(length: Int)(implicit T: Tracer[IO], L: Local[IO, Context]): String = withJContext(JContext.current())(generate[IO](length)).unsafeRunSync() def generate[F[_]: Async: Tracer](length: Int): F[String] = Tracer[F].span("generate", Attribute("length", length.toLong)).surround { for { random <- Random.scalaUtilRandom[F] delay <- random.betweenInt(100, 2000) chars <- random.nextAlphaNumeric.replicateA(length).delayBy(delay.millis) } yield chars.mkString } def withJContext[F[_], A](ctx: JContext)(fa: F[A])(implicit L: Local[F, Context]): F[A] = Local[F, Context].scope(fa)(Context.wrap(ctx)) ``` -------------------------------- ### Materializing an instance Source: https://github.com/typelevel/otel4s/blob/main/docs/index.md Example of how to materialize an auto-configured OpenTelemetry instance and use its MeterProvider and TracerProvider. ```scala import cats.effect.{IO, IOApp} import org.typelevel.otel4s.metrics.MeterProvider import org.typelevel.otel4s.oteljava.OtelJava import org.typelevel.otel4s.trace.TracerProvider object Main extends IOApp.Simple { def run: IO[Unit] = OtelJava.autoConfigured[IO]().use { otel4s => program(otel4s.meterProvider, otel4s.tracerProvider) } def program( meterProvider: MeterProvider[IO], tracerProvider: TracerProvider[IO] ): IO[Unit] = for { meter <- meterProvider.get("service") tracer <- tracerProvider.get("service") // create a counter and increment its value counter <- meter.counter[Long]("counter").create _ <- counter.inc() // create and materialize a span _ <- tracer.span("span").surround(IO.unit) } yield () } ``` -------------------------------- ### Creating instruments Source: https://github.com/typelevel/otel4s/blob/main/docs/instrumentation/metrics.md Example of creating a double and a long counter using the Meter. ```scala import cats.effect.IO import org.typelevel.otel4s.metrics.{Counter, Meter} @annotation.nowarn val meter: Meter[IO] = ??? val doubleCounter: IO[Counter[IO, Double]] = meter.counter[Double]("double-counter").create val longCounter: IO[Counter[IO, Long]] = meter.counter[Long]("long-counter").create ``` -------------------------------- ### EventExpectation Examples Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-traces.md Examples demonstrating the usage of EventExpectation for matching single events, including name, timestamp, and attribute matching. ```scala import scala.concurrent.duration._ EventExpectation.name("started") EventExpectation .name("exception") .timestamp(2.seconds) .attributesSubset(Attribute("exception.message", "boom")) ``` -------------------------------- ### Structured Spans Source: https://github.com/typelevel/otel4s/blob/main/docs/instrumentation/tracing.md Example demonstrating how to achieve structured spans using otel4s Tracer and Resource. ```scala class Connection[F[_]: Tracer] { def use[A](f: Connection[F] => F[A]): F[A] = Tracer[F].span("use_conn").surround(f(this)) } object Connection { def create[F[_]: Async: Tracer]: Resource[F, Connection[F]] = Resource.make( Tracer[F].span("acquire").surround(Async[F].pure(new Connection[F])) )(_ => Tracer[F].span("release").surround(Async[F].unit)) } class App[F[_]: Async: Tracer] { def withConnection[A](f: Connection[F] => F[A]): F[A] = (for { r <- Tracer[F].span("resource").resource c <- Connection.create[F].mapK(r.trace) } yield (r, c)).use { case (res, connection) => res.trace(Tracer[F].span("use").surround(connection.use(f))) } } ``` -------------------------------- ### Partial matching example 1 Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-logs.md Demonstrates a basic partial match on message. ```scala LogRecordExpectation.message("request failed") ``` -------------------------------- ### Partial matching example 2 Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-logs.md Demonstrates partial matching on message and severity. ```scala LogRecordExpectation .message("request failed") .severity(Severity.error) ``` -------------------------------- ### Severity matching example Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-logs.md Demonstrates matching on severity and severity text. ```scala LogRecordExpectation .message("request failed") .severity(Severity.error) .severityText("ERROR") ``` -------------------------------- ### Autoconfigured instance with OtelJava.autoConfigured Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/overview.md Example of creating an autoconfigured OpenTelemetry instance using OtelJava.autoConfigured. ```scala import cats.effect.{IO, IOApp} import org.typelevel.otel4s.oteljava.OtelJava import org.typelevel.otel4s.metrics.MeterProvider import org.typelevel.otel4s.trace.TracerProvider object TelemetryApp extends IOApp.Simple { def run: IO[Unit] = OtelJava.autoConfigured[IO]().use { otel4s => program(otel4s.meterProvider, otel4s.tracerProvider) } def program( meterProvider: MeterProvider[IO], tracerProvider: TracerProvider[IO] ): IO[Unit] = ??? ``` -------------------------------- ### Global instance with OtelJava.global Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/overview.md Example of accessing the global OpenTelemetry instance using OtelJava.global. ```scala import cats.effect.{IO, IOApp} import org.typelevel.otel4s.oteljava.OtelJava import org.typelevel.otel4s.metrics.MeterProvider import org.typelevel.otel4s.trace.TracerProvider object TelemetryApp extends IOApp.Simple { def run: IO[Unit] = OtelJava.global[IO].flatMap { otel4s => program(otel4s.meterProvider, otel4s.tracerProvider) } def program( meterProvider: MeterProvider[IO], tracerProvider: TracerProvider[IO] ): IO[Unit] = ??? ``` -------------------------------- ### Run the application (sbt) Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/honeycomb/README.md Instructions to run the application using sbt, including setting environment variables for Honeycomb. ```shell $ export OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=your-api-key,x-honeycomb-dataset=honeycomb-example" $ sbt run ``` -------------------------------- ### Getting the Tracer Source: https://github.com/typelevel/otel4s/blob/main/docs/instrumentation/tracing.md Create an auto-configured Tracer instance using OtelJava.autoConfigured. This instance is isolated and non-global. ```scala import cats.effect.IO import org.typelevel.otel4s.trace.Tracer import org.typelevel.otel4s.oteljava.OtelJava OtelJava.autoConfigured[IO]().evalMap { otel4s => otel4s.tracerProvider.get("com.service").flatMap { implicit tracer: Tracer[IO] => val _ = tracer // use tracer here ??? } } ``` -------------------------------- ### Run the application (scala-cli) Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/honeycomb/README.md Instructions to run the application using scala-cli, including setting environment variables for Honeycomb. ```shell $ export OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=your-api-key,x-honeycomb-dataset=honeycomb-example" $ scala-cli run tracing.scala ``` -------------------------------- ### Starting an unmanaged span - Properly ended span Source: https://github.com/typelevel/otel4s/blob/main/docs/instrumentation/tracing.md Example of an unmanaged span that is properly ended. ```scala def ok[F[_]: Monad: Tracer]: F[Unit] = Tracer[F].spanBuilder("manual-span").build.startUnmanaged.flatMap { span => span.setStatus(StatusCode.Ok, "all good") >> span.end } ``` -------------------------------- ### sbt Project Setup Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/dash0/README.md Add the otel4s library, OpenTelemetry exporter, and autoconfigure extension to your build.sbt file. Configure system properties for autoconfiguration, service name, and the Dash0 API endpoint. ```scala libraryDependencies ++= Seq( "org.typelevel" %% "otel4s-oteljava" % "@VERSION@", // <1> "io.opentelemetry" % "opentelemetry-exporter-otlp" % "@OPEN_TELEMETRY_VERSION@" % Runtime, // <2> "io.opentelemetry" % "opentelemetry-sdk-extension-autoconfigure" % "@OPEN_TELEMETRY_VERSION@" % Runtime // <3> ) run / fork := true javaOptions += "-Dotel.java.global-autoconfigure.enabled=true" // <4> javaOptions += "-Dotel.service.name=dash0-example" // <5> javaOptions += "-Dotel.exporter.otlp.endpoint=https://ingress.eu-west-1.aws.dash0.com // <6> ``` -------------------------------- ### Starting an unmanaged span - Leaked span Source: https://github.com/typelevel/otel4s/blob/main/docs/instrumentation/tracing.md Example of an unmanaged span that has never been terminated, leading to a leak. ```scala import org.typelevel.otel4s.trace.StatusCode def leaked[F[_]: Monad: Tracer]: F[Unit] = Tracer[F].spanBuilder("manual-span").build.startUnmanaged.flatMap { span => span.setStatus(StatusCode.Ok, "all good") } ``` -------------------------------- ### Scala CLI Project Setup Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/dash0/README.md Add the otel4s library, OpenTelemetry exporter, and autoconfigure extension as directives in your tracing.scala file. Configure system properties for autoconfiguration, service name, and the Dash0 API endpoint. ```scala //> using dep "org.typelevel::otel4s-oteljava:@VERSION@" //> using dep "io.opentelemetry:opentelemetry-exporter-otlp:@OPEN_TELEMETRY_VERSION@" //> using dep "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:@OPEN_TELEMETRY_VERSION@" //> using javaOpt "-Dotel.java.global-autoconfigure.enabled=true" //> using javaOpt "-Dotel.service.name=dash0-example" //> using javaOpt "-Dotel.exporter.otlp.endpoint=https://ingress.eu-west-1.aws.dash0.com" ``` -------------------------------- ### Tracing a resource - Span not propagated automatically Source: https://github.com/typelevel/otel4s/blob/main/docs/instrumentation/tracing.md Example showing that the span started by `.resource` is not propagated automatically to the resource closure. ```scala import cats.effect._ import cats.syntax.functor._ import org.typelevel.otel4s.trace.{Tracer, SpanOps} def withResource[F[_]: Async: Tracer]: F[Unit] = Tracer[F].span("my-resource-span").resource.use { case SpanOps.Res(_, _) => Tracer[F].currentSpanContext.void // returns `None` } ``` -------------------------------- ### Run the application (sbt) Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/dash0/README.md Command to run the OpenTelemetry for Scala example application using sbt. ```shell $ export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer auth_token,Dash0-Dataset=otel-metrics" $ sbt run ``` -------------------------------- ### Timing and Lifecycle Expectations Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-traces.md Examples showing how to assert the start and end timestamps of spans, as well as their lifecycle state (ended or not ended). ```Scala import scala.concurrent.duration._ SpanExpectation .name("request") .startTimestamp(1.second) .endTimestamp(1500.millis) .hasEnded SpanExpectation .name("still-open") .endTimestamp(None) .hasNotEnded ``` -------------------------------- ### IOLocal Shortcut Example Source: https://github.com/typelevel/otel4s/blob/main/docs/tracing-context-propagation.md A shorter way to initialize OtelJava using IOLocal without direct IOLocal instance access. ```Scala import cats.effect._ import cats.syntax.flatMap._ import org.typelevel.otel4s.oteljava.OtelJava import io.opentelemetry.api.GlobalOpenTelemetry def createOtel4s[F[_]: Async: LiftIO]: F[OtelJava[F]] = Async[F].delay(GlobalOpenTelemetry.get).flatMap(OtelJava.fromJOpenTelemetry[F]) def program[F[_]: Async](otel4s: OtelJava[F]): F[Unit] = { val _ = otel4s Async[F].unit } val run: IO[Unit] = createOtel4s[IO].flatMap(otel4s => program(otel4s)) ``` -------------------------------- ### SpanExpectations example Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-traces.md Example of using SpanExpectations for flat exported-span matching. ```scala SpanExpectations.checkAllDistinct( Nil, SpanExpectation.server("GET /users"), SpanExpectation.client("SELECT users") ) ``` -------------------------------- ### Run the application Source: https://github.com/typelevel/otel4s/blob/main/docs/customization/histogram-custom-buckets/README.md Instructions on how to run the application using different build tools. ```shell $ sbt run ``` ```shell $ scala-cli run histogram-buckets.scala ``` -------------------------------- ### Basic flow example Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-logs.md Demonstrates the usual flow of running a program, collecting logs, building expectations, and checking them. ```scala import cats.effect.IO import io.opentelemetry.sdk.logs.data.LogRecordData import org.typelevel.otel4s.AnyValue import org.typelevel.otel4s.Attribute import org.typelevel.otel4s.logs.{LoggerProvider, Severity} import org.typelevel.otel4s.oteljava.context.Context import org.typelevel.otel4s.oteljava.testkit.logs._ def program(loggerProvider: LoggerProvider[IO, Context]): IO[Unit] = for { logger <- loggerProvider.logger("service").withVersion("1.0.0").get _ <- logger.logRecordBuilder .withSeverity(Severity.error) .withSeverityText("ERROR") .withBody(AnyValue.string("request failed")) .addAttributes( Attribute("http.route", "/users"), Attribute("error.type", "timeout") ) .emit } yield () def assertExpected( records: List[LogRecordData], expected: LogRecordExpectation* ): Unit = LogRecordExpectations.checkAllDistinct(records, expected: _*) match { case Right(_) => () case Left(mismatches) => sys.error(LogRecordExpectations.format(mismatches)) } def test: IO[Unit] = LogsTestkit.inMemory[IO]().use { testkit => for { _ <- program(testkit.loggerProvider) records <- testkit.finishedLogs } yield assertExpected( records, LogRecordExpectation .message("request failed") .severity(Severity.error) .severityText("ERROR") .attributesSubset( Attribute("http.route", "/users"), Attribute("error.type", "timeout") ) ) } ``` -------------------------------- ### Trace and span correlation example 2 Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-logs.md Example asserting a log is untraced. ```scala LogRecordExpectation .message("startup complete") .untraced ``` -------------------------------- ### Run the application (scala-cli) Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/dash0/README.md Command to run the OpenTelemetry for Scala example application using scala-cli. ```shell $ export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer auth_token,Dash0-Dataset=otel-metrics" $ scala-cli run tracing.scala ``` -------------------------------- ### EventSetExpectation Examples Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-traces.md Examples showcasing EventSetExpectation for collection-based event matching, including contains, and, and none. ```scala SpanExpectation .name("work") .events( EventSetExpectation .contains( EventExpectation.name("started"), EventExpectation.name("finished") ) .and(EventSetExpectation.count(2)) ) SpanExpectation .name("work") .events( EventSetExpectation.none(EventExpectation.name("exception")) ) ``` -------------------------------- ### Honeycomb API Key and Dataset Configuration Source: https://github.com/typelevel/otel4s/blob/main/docs/examples/honeycomb/README.md Shell command to export environment variables for Honeycomb API key and dataset. ```shell $ export OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=your-api-key,x-honeycomb-dataset=otel-metrics" ``` -------------------------------- ### Failure reporting example Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-metrics.md An example of how to define a function to assert expected metrics and handle mismatches. ```scala import io.opentelemetry.sdk.metrics.data.MetricData import org.typelevel.otel4s.oteljava.testkit.metrics.{MetricExpectation, MetricExpectations} def assertExpected(metrics: List[MetricData], expected: MetricExpectation*): Unit = MetricExpectations.checkAll(metrics, expected: _*) match { case Right(_) => () case Left(mismatches) => sys.error(MetricExpectations.format(mismatches)) } ``` -------------------------------- ### Trace and span correlation example 1 Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-logs.md Example correlating with trace ID and span ID. ```scala LogRecordExpectation .message("request failed") .traceId("0af7651916cd43dd8448eb211c80319c") .spanId("b7ad6b7169203331") ``` -------------------------------- ### Autoconfigure SDK Source: https://github.com/typelevel/otel4s/blob/main/docs/instrumentation/logs.md Use OtelJava.autoConfigured to autoconfigure the SDK and obtain MeterProvider, TracerProvider, and LoggerProvider. ```scala import cats.effect.{IO, IOApp} import org.typelevel.otel4s.oteljava.context.Context import org.typelevel.otel4s.oteljava.OtelJava import org.typelevel.otel4s.metrics.MeterProvider import org.typelevel.otel4s.trace.TracerProvider import org.typelevel.otel4s.logs.LoggerProvider object TelemetryApp extends IOApp.Simple { def run: IO[Unit] = OtelJava .autoConfigured[IO]() .use { case sdk => program(sdk.meterProvider, sdk.tracerProvider, sdk.loggerProvider) } def program( meterProvider: MeterProvider[IO], tracerProvider: TracerProvider[IO], loggerProvider: LoggerProvider[IO, Context], ): IO[Unit] = ??? ``` -------------------------------- ### Acquire and Release Spans Source: https://github.com/typelevel/otel4s/blob/main/docs/instrumentation/tracing.md Example showing how to trace acquire and release steps of a resource using res.trace and Resource#mapK. ```scala class Transactor[F[_]] class Redis[F[_]] def createTransactor[F[_]: Async: Tracer]: Resource[F, Transactor[F]] = Resource.make( Tracer[F].span("transactor#acquire").surround(Async[F].pure(new Transactor[F])) )(_ => Tracer[F].span("transactor#release").surround(Async[F].unit)) def createRedis[F[_]: Async: Tracer]: Resource[F, Redis[F]] = Resource.make( Tracer[F].span("redis#acquire").surround(Async[F].pure(new Redis[F])) )(_ => Tracer[F].span("redis#release").surround(Async[F].unit)) def components[F[_]: Async: Tracer]: Resource[F, (Transactor[F], Redis[F])] = for { r <- Tracer[F].span("app_lifecycle").resource tx <- createTransactor[F].mapK(r.trace) redis <- createRedis[F].mapK(r.trace) } yield (tx, redis) def run[F[_]: Async: Tracer]: F[Unit] = components[F].use { case (_ /*transactor*/, _ /*redis*/) => Tracer[F].span("app_run").surround(Async[F].unit) } ``` -------------------------------- ### TraceExpectations example Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-traces.md Example of using TraceExpectations for exact tree and forest matching where parent-child topology matters. ```scala TraceForestExpectation.unordered( TraceExpectation.unordered( SpanExpectation.server("GET /users").noParentSpanContext, TraceExpectation.leaf(SpanExpectation.client("SELECT users")) ) ) ``` -------------------------------- ### Distinct check example Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-metrics.md Example demonstrating `checkAllDistinct` for ensuring repeated expectations match different collected metrics. ```scala def checkAllDistinct(metrics: List[MetricData]) = MetricExpectations.checkAllDistinct( metrics, MetricExpectation.sum[Long]("service.counter").value(1L), MetricExpectation.sum[Long]("service.counter").value(1L) ) ``` -------------------------------- ### LinkExpectation Example Source: https://github.com/typelevel/otel4s/blob/main/docs/oteljava/testkit-traces.md An example demonstrating the usage of LinkExpectation for matching single links, including trace ID and sampled status. ```scala LinkExpectation.any LinkExpectation .any .traceIdHex("0af7651916cd43dd8448eb211c80319c") .sampled ```