Last active
February 21, 2025 17:45
-
-
Save calvinlfer/ef0bd9e930e2f794ca7d6bf74c64e7c9 to your computer and use it in GitHub Desktop.
Ship ZIO Metrics to OpenTelemetry (OLTP gRPC) using zio-telemetry-opentelemetry by manually providing the instrumentation (this portion connects ZIO Metrics to the ZIO Opentelemetry machinery). I also have an example that uses auto-instrumentation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val scala3Version = "3.6.3" | |
lazy val root = project | |
.in(file(".")) | |
.settings( | |
name := "zio-telemetry-playground", | |
version := "0.1.0-SNAPSHOT", | |
scalaVersion := scala3Version, | |
libraryDependencies ++= | |
Seq( | |
"dev.zio" %% "zio" % "2.1.15", | |
"dev.zio" %% "zio-logging-slf4j-bridge" % "2.4.0", // route all SLF4J logs to ZIO RTS | |
"dev.zio" %% "zio-opentelemetry" % "3.1.1", // integration for OTLP metrics + ZIO | |
"dev.zio" %% "zio-opentelemetry-zio-logging" % "3.1.1", // integration for OTLP logs + ZIO | |
"io.opentelemetry" % "opentelemetry-sdk" % "1.47.0", | |
"io.opentelemetry" % "opentelemetry-exporter-otlp" % "1.47.0" | |
) | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
services: | |
otel-lgtm: | |
image: grafana/otel-lgtm:latest | |
container_name: otel-lgtm | |
ports: | |
- "3000:3000" # Grafana UI | |
- "4317:4317" # OpenTelemetry Collector gRPC | |
- "4318:4318" # OpenTelemetry Collector HTTP | |
volumes: | |
- ./otel-lgtm-data:/var/lib/grafana |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import zio.* | |
import zio.telemetry.opentelemetry.OpenTelemetry | |
import io.opentelemetry.sdk.OpenTelemetrySdk | |
import io.opentelemetry.api.metrics.MeterProvider | |
import io.opentelemetry.sdk.metrics.SdkMeterProvider | |
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter | |
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter | |
import io.opentelemetry.sdk.metrics.`export`.PeriodicMetricReader | |
import io.opentelemetry.sdk.resources.Resource as OltpResource | |
import zio.metrics.Metric | |
import java.util.concurrent.TimeUnit | |
import zio.metrics.jvm.DefaultJvmMetrics | |
import io.opentelemetry.sdk.logs.`export`.SimpleLogRecordProcessor | |
import io.opentelemetry.sdk.logs.`export`.BatchLogRecordProcessor | |
import io.opentelemetry.sdk.logs.SdkLoggerProvider | |
import zio.logging.slf4j.bridge.Slf4jBridge | |
object Main extends ZIOAppDefault: | |
override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] = | |
// metrics | |
val meterProvider: RIO[Scope, SdkMeterProvider] = | |
val metricExporter: RIO[Scope, OtlpGrpcMetricExporter] = | |
ZIO.fromAutoCloseable: | |
ZIO.attempt: | |
OtlpGrpcMetricExporter.builder().setEndpoint("http://localhost:4317").build() | |
metricExporter | |
.flatMap: exporter => | |
ZIO | |
.fromAutoCloseable: | |
ZIO.attempt: | |
PeriodicMetricReader | |
.builder(exporter) | |
.setInterval(5, TimeUnit.SECONDS) | |
.build() | |
.map: meterReader => | |
SdkMeterProvider | |
.builder() | |
.setResource( | |
OltpResource | |
.builder() | |
.put("service.name", "zio-telemetry-playground") | |
.put("job", "zio-telemetry-playground") | |
.build() | |
) | |
.registerMetricReader(meterReader) | |
.build() | |
// logging | |
val loggerProvider: RIO[Scope, SdkLoggerProvider] = | |
val logExporter = | |
ZIO.fromAutoCloseable: | |
ZIO.attempt: | |
OtlpGrpcLogRecordExporter | |
.builder() | |
.setEndpoint("http://localhost:4317") | |
.setCompression("gzip") | |
.build() | |
logExporter | |
.flatMap: exporter => | |
ZIO.fromAutoCloseable: | |
ZIO.attempt: | |
BatchLogRecordProcessor | |
.builder(exporter) | |
.setMaxExportBatchSize(100) | |
.build() | |
.flatMap: processor => | |
ZIO.fromAutoCloseable: | |
ZIO.attempt: | |
SdkLoggerProvider | |
.builder() | |
.setResource( | |
OltpResource | |
.builder() | |
.put("service.name", "zio-telemetry-playground") | |
.put("job", "zio-telemetry-playground") | |
.build() | |
) | |
.addLogRecordProcessor(processor) | |
.build() | |
val otelSdkLayer: TaskLayer[OpenTelemetrySdk] = | |
ZLayer.scoped: | |
for | |
meterProvider <- meterProvider | |
loggerProvider <- loggerProvider | |
otelSdk <- ZIO.fromAutoCloseable: | |
ZIO.attempt: | |
OpenTelemetrySdk | |
.builder() | |
.setMeterProvider(meterProvider) | |
.setLoggerProvider(loggerProvider) | |
.build() | |
yield otelSdk | |
Runtime.removeDefaultLoggers >>> | |
Slf4jBridge.initialize >>> // route all SLF4J logs to ZIO RTS | |
otelSdkLayer >+> | |
OpenTelemetry.contextZIO >+> | |
OpenTelemetry.metrics("zio-telemetry-playground", None, None) >+> | |
OpenTelemetry.logging("zio-telemetry-playground", logLevel = LogLevel.Debug) >+> // log all ZIO logs to OTLP | |
OpenTelemetry.zioMetrics >>> // ship all metrics to OTLP | |
DefaultJvmMetrics.live.unit | |
override def run = | |
val metric: Metric.Counter[Any] = | |
Metric | |
.counter("test_cal") | |
.contramap[Any](_ => 1L) | |
(ZIO.logInfo("Hello from cal!") | |
@@ ZIOAspect.annotated( | |
"bim" -> "boom", | |
"bim2" -> "boom2", | |
"bim3" -> "boom3" | |
) | |
@@ metric).repeat(Schedule.spaced(1.second)) |
Author
calvinlfer
commented
Feb 20, 2025
Auto instrumentation makes this even easier provided you use the java-agent:
build.sbt
val scala3Version = "3.6.3"
inThisBuild(
List(
semanticdbEnabled := true
)
)
lazy val root = project
.in(file("."))
.enablePlugins(JavaAgent)
.settings(
name := "zio-telemetry-playground",
version := "0.1.0-SNAPSHOT",
scalaVersion := scala3Version,
scalacOptions ++= Seq("-Wunused:imports"),
javaAgents += "io.opentelemetry.javaagent" % "opentelemetry-javaagent" % "2.13.1" % "compile",
fork := true,
libraryDependencies ++=
Seq(
"dev.zio" %% "zio" % "2.1.15",
"dev.zio" %% "zio-logging-slf4j-bridge" % "2.4.0", // route all SLF4J logs to ZIO RTS
"dev.zio" %% "zio-opentelemetry" % "3.1.1",
"dev.zio" %% "zio-opentelemetry-zio-logging" % "3.1.1",
"io.opentelemetry" % "opentelemetry-sdk" % "1.47.0",
"io.opentelemetry" % "opentelemetry-exporter-otlp" % "1.47.0"
)
)
project/plugins.sbt
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.4")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.14.2")
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.11.1")
addSbtPlugin("com.github.sbt" % "sbt-javaagent" % "0.1.7")
addSbtPlugin("nl.gn0s1s" % "sbt-dotenv" % "3.1.1")
.env
OTEL_SERVICE_NAME="zio-telemetry-auto-example"
OTEL_EXPORTER_OTLP_PROTOCOL="grpc"
OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"
# 100% sampling rate for traces (we aren't using traces in this example)
OTEL_TRACES_SAMPLER="always_on"
Main.scala
import zio.*
import zio.logging.slf4j.bridge.Slf4jBridge
import zio.metrics.Metric
import zio.metrics.jvm.DefaultJvmMetrics
import zio.telemetry.opentelemetry.OpenTelemetry
object Main extends ZIOAppDefault:
override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] =
Runtime.removeDefaultLoggers >>>
Slf4jBridge.initialize >>> // route all SLF4J logs to ZIO RTS
OpenTelemetry.global >+> // auto-instrumentation handshake (requires javaagent)
OpenTelemetry.contextJVM >+> // auto-instrumentation context propagation handshake
OpenTelemetry.metrics("zio-telemetry-playground", None, None) >+>
OpenTelemetry.logging("zio-telemetry-playground", logLevel = LogLevel.Debug) >+> // log all ZIO logs to OTLP
OpenTelemetry.zioMetrics >>> // ship all metrics to OTLP
DefaultJvmMetrics.live.unit
override def run =
val metric: Metric.Counter[Any] =
Metric
.counter("test_cal")
.contramap[Any](_ => 1L)
(ZIO.logInfo("Hello from cal auto!")
@@ ZIOAspect.annotated(
"iam" -> "auto",
"bim" -> "boom"
) @@ metric).repeat(Schedule.spaced(1.second))
An example using zio-telemetry + ZIO showing how to do:
- tracing
- logging
- metrics
Main.scala
import zio.*
import zio.logging.slf4j.bridge.Slf4jBridge
import zio.metrics.Metric
import zio.metrics.jvm.DefaultJvmMetrics
import zio.telemetry.opentelemetry.OpenTelemetry
import zio.telemetry.opentelemetry.context.ContextStorage
import zio.telemetry.opentelemetry.tracing.Tracing
object Main extends ZIOApp:
override type Environment = ContextStorage & Tracing
override implicit def environmentTag: EnvironmentTag[Environment] = EnvironmentTag.tagFromTagMacro[Environment]
override val bootstrap: ZLayer[ZIOAppArgs, Any, Environment] =
ZLayer.make[Environment](
Runtime.removeDefaultLoggers,
Slf4jBridge.initialize, // route all SLF4J logs to ZIO RTS
OpenTelemetry.global, // auto-instrumentation handshake (requires javaagent)
OpenTelemetry.contextJVM, // auto-instrumentation context propagation handshake
OpenTelemetry.tracing("zio-telemetry-playground", None, None, logAnnotated = true),
OpenTelemetry.metrics("zio-telemetry-playground", None, None),
OpenTelemetry.logging("zio-telemetry-playground", logLevel = LogLevel.Debug), // log all ZIO logs to OTLP
OpenTelemetry.zioMetrics, // ship all metrics to OTLP
DefaultJvmMetrics.live.unit
)
override def run =
val metric: Metric.Counter[Any] =
Metric
.counter("test_cal")
.contramap[Any](_ => 1L)
ZIO.serviceWithZIO[Tracing]: tracing =>
(ZIO.logInfo("Hello from cal auto!")
@@ ZIOAspect.annotated("iam" -> "auto", "bim" -> "boom")
@@ metric
@@ tracing.aspects.span("test_span_cal")).repeat(Schedule.spaced(1.second))
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment