Skip to main content
In this example, we’ll use Dash0 to collect and visualize metrics and traces produced by an otel4s application. Unlike the Jaeger example, you don’t need to set up a collector service locally—telemetry is sent directly to the Dash0 API.
Dash0 offers a 14-day free trial. After that, 1 million spans cost 20 cents. It provides robust analysis and visualization tools for exploring telemetry data.

Project Setup

Configure your project with the required dependencies:
// Add to 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
)

run / fork := true
javaOptions += "-Dotel.java.global-autoconfigure.enabled=true"
javaOptions += "-Dotel.service.name=dash0-example"
javaOptions += "-Dotel.exporter.otlp.endpoint=https://ingress.eu-west-1.aws.dash0.com"
The endpoint URL may differ depending on your cloud region. Check the Dash0 settings for your specific endpoint.

OpenTelemetry SDK Configuration

The OpenTelemetry SDK can be configured via system properties or environment variables. See the full list of environment variable configurations for more options.

Acquiring a Dash0 Auth Token

1
Create an Account
2
Sign up at dash0.com/sign-up.
3
Log In
4
Access your Dash0 account.
6
Go to the organization settings page.
7
Generate Auth Token
8
Under “Auth Tokens”, generate a new auth token.
9
Find Your Endpoint
10
Under “Endpoints”, find the value for -Dotel.exporter.otlp.endpoint specific to your cloud region.
11
Use Different Tokens
12
Create separate auth tokens and datasets for test, production, and local development to organize your data.

Dash0 Configuration

Configure the auth token and dataset name using environment variables:
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer auth_token,Dash0-Dataset=otel-metrics"
Headers:
  • Authorization - Bearer token for authentication
  • Dash0-Dataset - The dataset name for metrics (defaults to default if not set)
Each service’s traces will automatically land in a dataset named after the otel.service.name configuration.

Application Example

This example demonstrates both metrics and tracing:
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_
  }
}

Running the Application

export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer auth_token,Dash0-Dataset=otel-metrics"
sbt run

Viewing Data in Dash0

Navigate to https://app.dash0.com/ to query and visualize your collected traces and metrics.

Traces

You’ll see the trace hierarchy with:
  • Parent span “Work.DoWork”
  • Nested “internal” spans
  • Events marking work start and completion
  • Attributes including the “steps” counter
  • Timing information for each span

Metrics

The work.execution.duration histogram will show:
  • Execution time distribution
  • Statistical aggregations
  • Time-series visualization
  • Histogram buckets
Use Dash0’s query and visualization tools to:
  • Create custom dashboards
  • Set up alerts
  • Analyze performance patterns
  • Correlate metrics with traces

Build docs developers (and LLMs) love