Skip to content

Commit

Permalink
Merge pull request #3213 from softwaremill/otel-example
Browse files Browse the repository at this point in the history
Add OpenTelemetry metrics example
  • Loading branch information
adamw authored Oct 3, 2023
2 parents 4b31111 + fe19dec commit 3d981a7
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 0 deletions.
3 changes: 3 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2037,6 +2037,9 @@ lazy val examples: ProjectMatrix = (projectMatrix in file("examples"))
"com.github.jwt-scala" %% "jwt-circe" % Versions.jwtScala,
"org.mock-server" % "mockserver-netty" % Versions.mockServer,
"io.circe" %% "circe-generic-extras" % Versions.circeGenericExtras,
"io.opentelemetry" % "opentelemetry-sdk" % Versions.openTelemetry,
"io.opentelemetry" % "opentelemetry-sdk-metrics" % Versions.openTelemetry,
"io.opentelemetry" % "opentelemetry-exporter-otlp" % Versions.openTelemetry,
scalaTest.value
),
libraryDependencies ++= loggerDependencies,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package sttp.tapir.examples.observability

import com.typesafe.scalalogging.StrictLogging
import io.circe.generic.auto._
import io.opentelemetry.api.OpenTelemetry
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter
import io.opentelemetry.sdk.OpenTelemetrySdk
import io.opentelemetry.sdk.metrics.SdkMeterProvider
import io.opentelemetry.sdk.metrics.`export`.PeriodicMetricReader
import sttp.tapir._
import sttp.tapir.generic.auto._
import sttp.tapir.json.circe.jsonBody
import sttp.tapir.server.ServerEndpoint
import sttp.tapir.server.metrics.opentelemetry.OpenTelemetryMetrics
import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerOptions}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}

/** This example uses a gRPC <a href="https://opentelemetry.io/docs/concepts/components/#exporters">exporter</a> to send metrics to a <a href="https://opentelemetry.io/docs/collector/">collector</a>, which by
* default is expected to be running on `localhost:4317`.
*
* You can run a collector locally using Docker with the following command:
* {{{
* docker run -p 4317:4317 otel/opentelemetry-collector:latest
* }}}
*
* Please refer to the <a href="https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/">exporter
* configuration</a> if you need to use a different host/port.
*
* The example code requires the following dependencies:
* {{{
* val openTelemetryVersion = <OpenTelemetry version>
*
* libraryDependencies ++= Seq(
* "io.opentelemetry" % "opentelemetry-sdk" % openTelemetryVersion,
* "io.opentelemetry" % "opentelemetry-sdk-metrics" % openTelemetryVersion,
* "io.opentelemetry" % "opentelemetry-exporter-otlp" % openTelemetryVersion
* )
* }}}
*
* Once this example app and the collector are running, and after you send some requests to the `/person` endpoint, you should start seeing
* the metrics in the collector logs (look for `InstrumentationScope tapir 1.0.0`), e.g.:
* {{{
* InstrumentationScope tapir 1.0.0
* Metric #0
* Descriptor:
* -> Name: request_active
* -> Description: Active HTTP requests
* -> Unit: 1
* -> DataType: Sum
* -> IsMonotonic: false
* -> AggregationTemporality: Cumulative
*
* ...
* }}}
*/
object OpenTelemetryMetricsExample extends App with StrictLogging {

case class Person(name: String)

// Simple endpoint returning 200 or 400 response with string body
val personEndpoint: ServerEndpoint[Any, Future] =
endpoint.post
.in("person")
.in(jsonBody[Person])
.out(stringBody)
.errorOut(stringBody)
.serverLogic { p =>
Thread.sleep(3000)
Future.successful(Either.cond(p.name == "Jacob", "Welcome", "Unauthorized"))
}

// An exporter that sends metrics to a collector over gRPC
val grpcExporter = OtlpGrpcMetricExporter.builder().build()

// A metric reader that exports using the gRPC exporter
val metricReader: PeriodicMetricReader = PeriodicMetricReader.builder(grpcExporter).build()

// A meter registry whose meters are read by the above reader
val meterProvider: SdkMeterProvider = SdkMeterProvider.builder().registerMetricReader(metricReader).build()

// An instance of OpenTelemetry using the above meter registry
val otel: OpenTelemetry = OpenTelemetrySdk.builder().setMeterProvider(meterProvider).build()

val openTelemetryMetrics = OpenTelemetryMetrics.default[Future](otel)

val serverOptions: NettyFutureServerOptions =
NettyFutureServerOptions.customiseInterceptors
// Adds an interceptor which collects metrics by executing callbacks
.metricsInterceptor(openTelemetryMetrics.metricsInterceptor())
.options

Await.ready(NettyFutureServer().port(8080).addEndpoint(personEndpoint, serverOptions).start(), 1.minute)

logger.info(s"""Server started. Try it with: curl -X POST localhost:8080/person -d '{"name": "Jacob"}'""")
}

0 comments on commit 3d981a7

Please sign in to comment.