Skip to main content

Quickstart

This guide will help you get started with otel4s and create your first instrumented application with both metrics and tracing.

Which module do I need?

Before getting started, determine which module suits your use case:
  • Developing an application and want to export telemetry → Use otel4s-oteljava
  • Developing a library and only need trace interfaces → Use otel4s-core-trace
  • Developing a library and need both tracing and metrics → Use otel4s-core
For this quickstart, we’ll use otel4s-oteljava to build a complete application with telemetry export.

Installation

Add the following to your build.sbt:
build.sbt
libraryDependencies ++= Seq(
  "org.typelevel" %% "otel4s-oteljava" % "0.15.0",
  "io.opentelemetry" % "opentelemetry-exporter-otlp" % "1.59.0" % Runtime,
  "io.opentelemetry" % "opentelemetry-sdk-extension-autoconfigure" % "1.59.0" % Runtime
)
javaOptions += "-Dotel.java.global-autoconfigure.enabled=true"
Explanation:
  1. Add the otel4s-oteljava library
  2. Add an OpenTelemetry exporter (without it, the application will crash)
  3. Add the OpenTelemetry autoconfigure extension
  4. Enable OpenTelemetry SDK autoconfigure mode

Create your first instrumented application

1

Import required dependencies

import cats.effect.{IO, IOApp}
import org.typelevel.otel4s.metrics.MeterProvider
import org.typelevel.otel4s.oteljava.OtelJava
import org.typelevel.otel4s.trace.TracerProvider
2

Initialize OtelJava

Use OtelJava.autoConfigured to create an otel4s instance. This method reads configuration from environment variables and system properties:
object Main extends IOApp.Simple {
  def run: IO[Unit] =
    OtelJava.autoConfigured[IO]().use { otel4s =>
      program(otel4s.meterProvider, otel4s.tracerProvider)
    }
}
OtelJava.autoConfigured creates an isolated non-global instance. If you create multiple instances, those instances won’t interoperate (i.e., they won’t be able to see each other’s spans).
3

Implement your instrumented program

Create meters and tracers, then use them to collect metrics and traces:
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 ()
4

Configure the exporter

Set environment variables to configure where telemetry is sent:
export OTEL_SERVICE_NAME=my-service
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
Or use system properties:
sbt \
  -Dotel.service.name=my-service \
  -Dotel.exporter.otlp.endpoint=http://localhost:4317 \
  run

Complete example

Here’s the complete working example:
Main.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 ()
}

Using the noop Tracer and Meter

If you use a library that supports otel4s (e.g., Skunk) but don’t want to use OpenTelemetry, you can provide no-op instances:
Using imports:
import cats.effect.IO
import org.typelevel.otel4s.trace.Tracer

def program[F[_]: Tracer]: F[Unit] = ???

import Tracer.Implicits.noop
val io: IO[Unit] = program[IO]
Using implicit vals:
import cats.effect.IO
import org.typelevel.otel4s.metrics.Meter
import org.typelevel.otel4s.trace.Tracer

def program[F[_]: Meter: Tracer]: F[Unit] = ???

implicit val meter: Meter[IO] = Meter.noop
implicit val tracer: Tracer[IO] = Tracer.noop
val io: IO[Unit] = program[IO]

Next steps

Module structure

Learn about the modular architecture and which module you need

Tracing guide

Deep dive into distributed tracing with otel4s

Metrics guide

Learn how to collect and export metrics

Examples

Explore working examples with real backends

Build docs developers (and LLMs) love