Warning
The otel4s-opentelemetry-java
is an experimental distribution of the OpenTelemetry Java agent
that includes instrumentation for Cats Effect and otel4s.
These instrumentations are somewhat hacky and may behave unpredictably in complex or non-trivial environments.
Please refer to the Limitations
section before using the agent.
The agent is identical to the OpenTelemetry Java agent, but also includes instrumentation for Cats Effect and otel4s.
See https://github.com/iRevive/otel4s-showcase demo.
- OpenTelemetry SDK: 1.53.0
- OpenTelemetry Java Agent: 2.19.0
The agent can be configured via sbt-javaagent plugin:
lazy val service = project
.enablePlugins(JavaAgent)
.in(file("service"))
.settings(
name := "service",
javaAgents += "io.github.irevive" % "otel4s-opentelemetry-javaagent" % "0.0.3", // <1>
run / fork := true, // <2>
javaOptions += "-Dcats.effect.trackFiberContext=true", // <3>
libraryDependencies ++= Seq( // <4>
"org.typelevel" %% "otel4s-oteljava" % "0.13.1",
"org.typelevel" %% "otel4s-oteljava-context-storage" % "0.13.1",
"io.opentelemetry" % "opentelemetry-exporter-otlp" % "1.52.0" % Runtime,
"io.opentelemetry" % "opentelemetry-sdk-extension-autoconfigure" % "1.52.0" % Runtime
)
)
- Register
otel4s-opentelemetry-javaagent
as a Java agent - Make sure the VM will be forked when running
- Enable Cats Effect fiber context tracking
- Add all necessary dependencies
The application can be configured in the following way:
object Server extends IOApp.Simple {
def run: IO[Unit] = {
given LocalProvider[IO, Context] = IOLocalContextStorage.localProvider[IO] // <1>
for {
otel4s <- Resource.eval(OtelJava.global[IO]) // <2>
given MeterProvider[IO] <- Resource.pure(otel4s.meterProvider)
given TracerProvider[IO] <- Resource.pure(otel4s.tracerProvider)
given Meter[IO] <- Resource.eval(MeterProvider[IO].get("service"))
given Tracer[IO] <- Resource.eval(TracerProvider[IO].get("service"))
// your app logic
} yield ()
}.useForever
}
IOLocalContextStorage.localProvider
- will automatically pick up agent's context when availableOtelJava.global[IO]
- you must use the global instance, since the agent will autoconfigure it
If everything is configured correctly, you will see the following log entries:
[otel.javaagent 2025-07-27 09:37:18:069 +0300] [main] INFO io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: otel4s-0.0.1-otel-2.18.1
IOLocalContextStorage: agent-provided IOLocal is detected
Cats Effect has its context propagation mechanism known as IOLocal. The 3.6.0 release provides a way to represent IOLocal as a ThreadLocal
, which creates an opportunity to manipulate the context from the outside.
- Agent instruments the constructor of
IORuntime
and stores aThreadLocal
representation of theIOLocal[Context]
in the bootstrap classloader, so the agent and application both access the same instance - Instrumentation installs a custom
ContextStorage
wrapper (for the agent context storage). This wrapper usesFiberLocalContextHelper
to retrieve the fiber's current context (if available) - Agent instruments
IOFiber
's constructor and starts the fiber with the currently available context
This instrumentation is not designed to support multiple deployments within the same JVM. For example, when deploying two WAR files to the same Tomcat instance, issues may arise since both instances will try to configure the bootstrap's shared context.
Check the open-telemetry/opentelemetry-java-instrumentation#13576 for more information.
Scala 2.13:
$ export SMOKE_TEST_JAVA_VERSION=11
$ export SMOKE_TEST_SCALA_VERSION=2.13
$ ./gradlew smoke-tests-images:http4s:jibDockerBuild -Djib.dockerClient.executable=$(which docker)
$ ./gradlew smoke-tests:build
Scala 3:
$ export SMOKE_TEST_JAVA_VERSION=11
$ export SMOKE_TEST_SCALA_VERSION=3
$ ./gradlew smoke-tests-images:http4s:jibDockerBuild -Djib.dockerClient.executable=$(which docker)
$ ./gradlew smoke-tests:build
$ SDK_VERSION="1.52.0"
$ AGENT_VERSION="2.18.2"
$ RELEASE_VERSION="0.0.2"
$ ./scripts/update-sdk-version.sh $SDK_VERSION
$ ./scripts/update-agent-version.sh $AGENT_VERSION
$ ./scripts/update-release-version.sh RELEASE_VERSION
Once the CI is green, you can publish a tag:
$ git tag -a v0.0.2 -m "v0.0.2"
$ git push origin v0.0.2