Overview

otel4s-oteljava modules provide implementation of the API interfaces. The implementation uses opentelemetry-java under the hood.

There are several advantages of using the otel4s-oteljava:

It's a recommended backend to use on JVM.

Getting Started

Add settings to the build.sbt:

libraryDependencies ++= Seq(
  "org.typelevel" %% "otel4s-oteljava" % "0.11.2", // <1>
  "io.opentelemetry" % "opentelemetry-exporter-otlp" % "1.45.0" % Runtime, // <2>
  "io.opentelemetry" % "opentelemetry-sdk-extension-autoconfigure" % "1.45.0" % Runtime // <3>
)
javaOptions += "-Dotel.java.global-autoconfigure.enabled=true" // <4>

Add directives to the *.scala file:

//> using lib "org.typelevel::otel4s-oteljava:0.11.2" // <1>
//> using lib "io.opentelemetry:opentelemetry-exporter-otlp:1.45.0" // <2>
//> using lib "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.45.0" // <3>
//> using `java-opt` "-Dotel.java.global-autoconfigure.enabled=true" // <4>
  1. Add the otel4s-oteljava library
  2. Add an OpenTelemetry exporter. Without the exporter, the application will crash
  3. Add an OpenTelemetry autoconfigure extension
  4. Enable OpenTelemetry SDK autoconfigure mode

Creating an autoconfigured instance

You can use OtelJava.autoConfigured to autoconfigure the SDK:

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] =
    ???
}

The OtelJava.autoConfigured(...) relies on the environment variables and system properties to configure the SDK. For example, use export OTEL_SERVICE_NAME=auth-service to configure the name of the service.

See the full set of the supported options.

OtelJava.autoConfigured creates an isolated non-global instance. If you create multiple instances, those instances won't interoperate (i.e. be able to see each others spans).

Accessing the global instance

There are several reasons to use the global instance:

In such a case, you can use the OtelJava.global to use the global OpenTelemetry instance:

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] =
    ???
}