|
| 1 | +# Dash0 - metrics and traces |
| 2 | + |
| 3 | +In this example, we are going to use [Dash0](https://www.dash0.com) to collect and visualize metrics and traces produced by an application. |
| 4 | +We will cover the configuration of OpenTelemetry exporter, as well as the instrumentation of the application using the otel4s library. |
| 5 | + |
| 6 | +Unlike [Jaeger example](../jaeger-docker/README.md), you do not need to set up a collector service locally. The metrics and traces will be sent to a remote Dash0 API. |
| 7 | + |
| 8 | +At the time of writing, Dash0 offers 14 day free trial, afterwards 1 million spans cost 20 cents. |
| 9 | +It offers robust analysis and visualization tools that are handy for exploring the world of telemetry. |
| 10 | + |
| 11 | +### Project setup |
| 12 | + |
| 13 | +Configure the project using your favorite tool: |
| 14 | + |
| 15 | +@:select(build-tool) |
| 16 | + |
| 17 | +@:choice(sbt) |
| 18 | + |
| 19 | +Add settings to the `build.sbt`: |
| 20 | +```scala |
| 21 | +libraryDependencies ++= Seq( |
| 22 | + "org.typelevel" %% "otel4s-oteljava" % "@VERSION@", // <1> |
| 23 | + "io.opentelemetry" % "opentelemetry-exporter-otlp" % "@OPEN_TELEMETRY_VERSION@" % Runtime, // <2> |
| 24 | + "io.opentelemetry" % "opentelemetry-sdk-extension-autoconfigure" % "@OPEN_TELEMETRY_VERSION@" % Runtime // <3> |
| 25 | +) |
| 26 | +run / fork := true |
| 27 | +javaOptions += "-Dotel.java.global-autoconfigure.enabled=true" // <4> |
| 28 | +javaOptions += "-Dotel.service.name=dash0-example" // <5> |
| 29 | +javaOptions += "-Dotel.exporter.otlp.endpoint=https://ingress.eu-west-1.aws.dash0.com // <6> |
| 30 | +``` |
| 31 | +
|
| 32 | +@:choice(scala-cli) |
| 33 | +
|
| 34 | +Add directives to the `tracing.scala`: |
| 35 | +```scala |
| 36 | +//> using dep "org.typelevel::otel4s-oteljava:@VERSION@" // <1> |
| 37 | +//> using dep "io.opentelemetry:opentelemetry-exporter-otlp:@OPEN_TELEMETRY_VERSION@" // <2> |
| 38 | +//> using dep "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:@OPEN_TELEMETRY_VERSION@" // <3> |
| 39 | +//> using javaOpt "-Dotel.java.global-autoconfigure.enabled=true" // <4> |
| 40 | +//> using javaOpt "-Dotel.service.name=dash0-example" // <5> |
| 41 | +//> using javaOpt "-Dotel.exporter.otlp.endpoint=https://ingress.eu-west-1.aws.dash0.com" // <6> |
| 42 | +``` |
| 43 | + |
| 44 | +@:@ |
| 45 | + |
| 46 | +1) Add the `otel4s` library |
| 47 | +2) Add an OpenTelemetry exporter. Without the exporter, the application will crash |
| 48 | +3) Add an OpenTelemetry autoconfigure extension |
| 49 | +4) Enable OpenTelemetry SDK autoconfigure mode |
| 50 | +5) Add the name of the application to use in the traces |
| 51 | +6) Add the Dash0 API endpoint |
| 52 | + |
| 53 | +### OpenTelemetry SDK configuration |
| 54 | + |
| 55 | +As mentioned above, we use `otel.java.global-autoconfigure.enabled` and `otel.service.name` system properties to configure the |
| 56 | +OpenTelemetry SDK. |
| 57 | +The SDK can be configured via environment variables too. Check the full list |
| 58 | +of [environment variable configurations](https://opentelemetry.io/docs/languages/java/configuration) |
| 59 | +for more options. |
| 60 | + |
| 61 | +### Acquiring a Dash0 Auth token |
| 62 | + |
| 63 | +First, you must create an account on the [Dash0 website](https://www.dash0.com/sign-up). |
| 64 | +Once you have done this, log into your account and navigate to the organization settings page. Under Auth Tokens, you can generate a new auth token. |
| 65 | +Under Endpoints you can also discover the value for `-Dotel.exporter.otlp.endpoint` that we used above. It can differ depending on your cloud region choice. |
| 66 | + |
| 67 | +Use a different auth tokens and datasets for test, production, and local development. This organizes your data in Dash0. |
| 68 | + |
| 69 | +### Dash0 configuration |
| 70 | + |
| 71 | +In order to send metrics and traces to Dash0, the auth token and metrics dataset name need to be configured. |
| 72 | +Since the auth token is sensitive data, we advise providing them via environment variables: |
| 73 | + |
| 74 | +```shell |
| 75 | +$ export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer auth_token,Dash0-Dataset=otel-metrics" |
| 76 | +``` |
| 77 | + |
| 78 | +1) `Authorization` - the Bearer auth token |
| 79 | +2) `Dash0-Dataset` - the name of the dataset to send metrics to. |
| 80 | + |
| 81 | +Each service's traces will land in a dataset defined in 'otel.service.name'. |
| 82 | + |
| 83 | +**Note:** if the `Dash0-Dataset` header is not configured, the **metrics** will be sent to a dataset called `default`. |
| 84 | + |
| 85 | +### Application example |
| 86 | + |
| 87 | +```scala mdoc:silent |
| 88 | +import java.util.concurrent.TimeUnit |
| 89 | + |
| 90 | +import cats.effect.{Async, IO, IOApp} |
| 91 | +import cats.effect.std.Console |
| 92 | +import cats.effect.std.Random |
| 93 | +import cats.syntax.all._ |
| 94 | +import org.typelevel.otel4s.{Attribute, AttributeKey} |
| 95 | +import org.typelevel.otel4s.oteljava.OtelJava |
| 96 | +import org.typelevel.otel4s.metrics.Histogram |
| 97 | +import org.typelevel.otel4s.trace.Tracer |
| 98 | + |
| 99 | +import scala.concurrent.duration._ |
| 100 | + |
| 101 | +trait Work[F[_]] { |
| 102 | + def doWork: F[Unit] |
| 103 | +} |
| 104 | + |
| 105 | +object Work { |
| 106 | + def apply[F[_]: Async: Tracer: Console](histogram: Histogram[F, Double]): Work[F] = |
| 107 | + new Work[F] { |
| 108 | + def doWork: F[Unit] = |
| 109 | + Tracer[F].span("Work.DoWork").use { span => |
| 110 | + span.addEvent("Starting the work.") *> |
| 111 | + doWorkInternal(steps = 10) *> |
| 112 | + span.addEvent("Finished working.") |
| 113 | + } |
| 114 | + |
| 115 | + def doWorkInternal(steps: Int): F[Unit] = { |
| 116 | + val step = Tracer[F] |
| 117 | + .span("internal", Attribute(AttributeKey.long("steps"), steps.toLong)) |
| 118 | + .surround { |
| 119 | + for { |
| 120 | + random <- Random.scalaUtilRandom |
| 121 | + delay <- random.nextIntBounded(1000) |
| 122 | + _ <- Async[F].sleep(delay.millis) |
| 123 | + _ <- Console[F].println("Doin' work") |
| 124 | + } yield () |
| 125 | + } |
| 126 | + |
| 127 | + val metered = histogram.recordDuration(TimeUnit.MILLISECONDS).surround(step) |
| 128 | + |
| 129 | + if (steps > 0) metered *> doWorkInternal(steps - 1) else metered |
| 130 | + } |
| 131 | + } |
| 132 | +} |
| 133 | + |
| 134 | +object TracingExample extends IOApp.Simple { |
| 135 | + def run: IO[Unit] = { |
| 136 | + OtelJava |
| 137 | + .autoConfigured[IO]() |
| 138 | + .evalMap { otel4s => |
| 139 | + otel4s.tracerProvider.get("com.service.runtime") |
| 140 | + .flatMap { implicit tracer: Tracer[IO] => |
| 141 | + for { |
| 142 | + meter <- otel4s.meterProvider.get("com.service.runtime") |
| 143 | + histogram <- meter.histogram[Double]("work.execution.duration").create |
| 144 | + _ <- Work[IO](histogram).doWork |
| 145 | + } yield () |
| 146 | + } |
| 147 | + } |
| 148 | + .use_ |
| 149 | + } |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +### Run the application |
| 154 | + |
| 155 | +@:select(build-tool) |
| 156 | + |
| 157 | +@:choice(sbt) |
| 158 | + |
| 159 | +```shell |
| 160 | +$ export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer auth_token,Dash0-Dataset=otel-metrics" |
| 161 | +$ sbt run |
| 162 | +``` |
| 163 | + |
| 164 | +@:choice(scala-cli) |
| 165 | + |
| 166 | +```shell |
| 167 | +$ export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer auth_token,Dash0-Dataset=otel-metrics" |
| 168 | +$ scala-cli run tracing.scala |
| 169 | +``` |
| 170 | + |
| 171 | +@:@ |
| 172 | + |
| 173 | +### Query collected traces and metrics |
| 174 | + |
| 175 | +You can query collected traces and metrics at https://app.dash0.com/. |
| 176 | + |
0 commit comments