Skip to content

Instantly share code, notes, and snippets.

@iRevive
Created May 22, 2026 16:11
Show Gist options
  • Select an option

  • Save iRevive/3d8cd0f6d4ad72d002ef22b5e00460d5 to your computer and use it in GitHub Desktop.

Select an option

Save iRevive/3d8cd0f6d4ad72d002ef22b5e00460d5 to your computer and use it in GitHub Desktop.
Scala Native + otel4s
//> using scala 2.13.18
//> using platform native
//> using nativeVersion 0.5.11
//> using dep org.typelevel::cats-effect::3.7.0
//> using dep org.typelevel::otel4s-instrumentation-metrics::0.16.0
//> using dep org.typelevel::otel4s-sdk::0.18.0
//> using dep org.typelevel::otel4s-sdk-contrib-metrics::0.18.0
//> using dep org.typelevel::otel4s-sdk-exporter::0.18.0
//> using dep org.http4s::http4s-ember-server::0.23.34
//> using dep org.http4s::http4s-dsl::0.23.34
//> using dep org.http4s::http4s-otel4s-middleware-metrics::0.17.0
//> using dep org.http4s::http4s-otel4s-middleware-trace-client::0.17.0
//> using dep org.http4s::http4s-otel4s-middleware-trace-server::0.17.0
import cats.effect._
import com.comcast.ip4s._
import org.http4s.Uri.Fragment
import org.http4s.client.Client
import org.http4s.client.middleware.{Metrics => ClientMetrics}
import org.http4s.server.middleware.{Metrics => ServerMetrics}
import org.typelevel.otel4s.instrumentation.ce.IORuntimeMetrics
import org.typelevel.otel4s.metrics.MeterProvider
import org.typelevel.otel4s.trace.TracerProvider
import org.typelevel.otel4s.sdk.OpenTelemetrySdk
import org.typelevel.otel4s.sdk.exporter.otlp.autoconfigure.OtlpExportersAutoConfigure
import org.typelevel.otel4s.sdk.contrib.metrics.runtime.RuntimeMetrics
import org.http4s.{Headers, HttpApp, HttpRoutes, Query, Request, Response}
import org.http4s.dsl.io._
import org.http4s.ember.client.EmberClientBuilder
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.syntax.all._
import org.http4s.otel4s.middleware.metrics.OtelMetrics
import org.http4s.otel4s.middleware.trace.client._
import org.http4s.otel4s.middleware.trace.redact
import org.http4s.otel4s.middleware.trace.redact.HeaderRedactor
import org.http4s.otel4s.middleware.trace.server.ServerMiddleware
import org.http4s.otel4s.middleware.trace.server.ServerSpanDataProvider
import org.typelevel.otel4s.trace.Tracer
object Main extends IOApp.Simple {
def run: IO[Unit] =
OpenTelemetrySdk
.autoConfigured[IO](
_.addExportersConfigurer(OtlpExportersAutoConfigure[IO])
)
.use { autoConfigured =>
val sdk = autoConfigured.sdk
implicit val mp: MeterProvider[IO] = sdk.meterProvider
RuntimeMetrics.register[IO].surround {
IORuntimeMetrics
.register[IO](runtime.metrics, IORuntimeMetrics.Config.default)
.surround {
program(sdk.meterProvider, sdk.tracerProvider)
}
}
}
def program(implicit
meterProvider: MeterProvider[IO],
tracerProvider: TracerProvider[IO]
): IO[Unit] = {
val redactor = new UriRedactor {
def redactFragment(fragment: Fragment): Option[Fragment] = None
def redactPath(path: Path): Path = path
def redactQuery(query: Query): Query = query
}
for {
clientMiddleware <- ClientMiddleware.builder[IO](ClientSpanDataProvider.openTelemetry(redactor)).build.toResource
clientMetricsOps <- OtelMetrics.clientMetricsOps[IO]().toResource
httpClient <- EmberClientBuilder
.default[IO]
.build
.map(clientMiddleware.wrap)
.map(ClientMetrics(clientMetricsOps)(_))
serverMiddleware <- ServerMiddleware.builder[IO](ServerSpanDataProvider.openTelemetry(redactor)).build.toResource
serverMetricsOps <- OtelMetrics.serverMetricsOps[IO]().toResource
appTracer <- tracerProvider.get("app").toResource
server <- {
implicit val T = appTracer
startHttpSever(serverMiddleware.wrapHttpApp(ServerMetrics(serverMetricsOps)(routes(httpClient)).orNotFound))
}
_ <- IO.println("Bound to: " + server.address).toResource
} yield ()
}.useForever
private def startHttpSever(httpApp: HttpApp[IO]) =
EmberServerBuilder
.default[IO]
.withHost(host"127.0.0.1")
.withPort(port"9000")
.withHttpApp(httpApp)
.build
private def routes(httpClient: Client[IO])(implicit T: Tracer[IO]): HttpRoutes[IO] =
HttpRoutes.of {
case req @ GET -> Root / "weather" / location =>
Tracer[IO].span("checkWeather").surround {
for {
r <- httpClient.expect[String](s"https://wttr.in/$location?format=3")
} yield Response().withEntity(r)
}
case GET -> Root / "health" =>
IO(Response())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment