Skip to content

Commit f01b4dc

Browse files
authored
Otel4s Metrics example (#4804)
Adds an example for otel4s Metrics integration (added in the PR #4788)
1 parent 9e72201 commit f01b4dc

File tree

1 file changed

+117
-0
lines changed

1 file changed

+117
-0
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// {cat=Observability; effects=cats-effect; server=Netty; json=circe}: Otel4s collecting metrics
2+
3+
//> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.44
4+
//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-cats:1.11.44
5+
//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.11.44
6+
//> using dep com.softwaremill.sttp.tapir::tapir-otel4s-metrics:1.11.44
7+
//> using dep "org.typelevel::otel4s-oteljava:0.13.1"
8+
//> using deps io.opentelemetry:opentelemetry-exporter-otlp:1.54.0
9+
//> using dep "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.54.0"
10+
//> using dep ch.qos.logback:logback-classic:1.5.18
11+
12+
package sttp.tapir.examples.observability
13+
14+
import cats.effect
15+
import cats.effect.std.Dispatcher
16+
import cats.effect.{IO, IOApp}
17+
import io.circe.generic.auto.*
18+
import sttp.tapir.*
19+
import sttp.tapir.generic.auto.*
20+
import sttp.tapir.json.circe.jsonBody
21+
import sttp.tapir.server.netty.cats.{NettyCatsServer, NettyCatsServerOptions}
22+
import sttp.tapir.server.metrics.otel4s.Otel4sMetrics
23+
import org.slf4j.{Logger, LoggerFactory}
24+
import org.typelevel.otel4s.metrics.Meter
25+
import org.typelevel.otel4s.oteljava.OtelJava
26+
27+
import scala.concurrent.duration.*
28+
import sttp.tapir.server.ServerEndpoint
29+
30+
import scala.io.StdIn
31+
32+
/** This example application demonstrates how to implement metrics in a Scala application using the <a
33+
* href="https://github.com/typelevel/otel4s"> otel4s library </a>. More info about oltel4s you may find <a
34+
* href="https://typelevel.org/otel4s/">here</a>
35+
*
36+
* In order to run otel collector and collect metrics in Prometheus format, you can run it using Docker with the following command:
37+
* {{{
38+
* cat > otel-config.yaml << 'EOF'
39+
* receivers:
40+
* otlp:
41+
* protocols:
42+
* grpc:
43+
* endpoint: 0.0.0.0:4317
44+
* exporters:
45+
* prometheus:
46+
* endpoint: 0.0.0.0:8889
47+
* service:
48+
* pipelines:
49+
* metrics:
50+
* receivers: [otlp]
51+
* exporters: [prometheus]
52+
* EOF
53+
*
54+
* docker run -p 4317:4317 -p 8889:8889 -v $(pwd)/otel-config.yaml:/etc/otelcol-contrib/config.yaml otel/opentelemetry-collector-contrib:latest
55+
* }}}
56+
* You can find the collected metrics in prometheus format at http://localhost:8889/metrics
57+
*/
58+
object Otel4sMetricsExample extends IOApp.Simple:
59+
private val logger: Logger = LoggerFactory.getLogger(this.getClass.getName)
60+
61+
override def run: IO[Unit] = otel
62+
.use { otel4s =>
63+
otel4s.meterProvider
64+
.get("sttp.tapir.examples.observability")
65+
.flatMap { case given Meter[IO] => server() }
66+
}
67+
68+
private def server()(using meter: Meter[IO]): IO[Unit] =
69+
Dispatcher
70+
.parallel[IO]
71+
.map(dispatcher =>
72+
NettyCatsServer(
73+
NettyCatsServerOptions
74+
.customiseInterceptors(dispatcher)
75+
.metricsInterceptor(Otel4sMetrics.default(meter).metricsInterceptor())
76+
.options
77+
)
78+
)
79+
.use { server =>
80+
for {
81+
bind <- server
82+
.port(8080)
83+
.host("localhost")
84+
.addEndpoint(personEndpoint)
85+
.start()
86+
_ <- IO
87+
.blocking {
88+
logger.info(s"""Server started. Try it with: curl -X POST ${bind.hostName}:${bind.port}/person -d '{"name": "Jacob"}'""")
89+
logger.info("Press ENTER key to exit.")
90+
StdIn.readLine()
91+
92+
}
93+
.guarantee(bind.stop())
94+
} yield ()
95+
}
96+
97+
private def otel: effect.Resource[IO, OtelJava[IO]] = {
98+
// Please notice in your, production cases could be better to use env variable instead.
99+
// Under following link you may find more details about configuration https://typelevel.org/otel4s/sdk/configuration.html
100+
System.setProperty("otel.java.global-autoconfigure.enabled", "true")
101+
System.setProperty("otel.service.name", "Otel4s-Metrics-Example")
102+
OtelJava.autoConfigured[IO]()
103+
}
104+
105+
case class Person(name: String)
106+
107+
// Simple endpoint returning 200 or 400 response with string body
108+
val personEndpoint: ServerEndpoint[Any, IO] =
109+
endpoint.post
110+
.in("person")
111+
.in(jsonBody[Person])
112+
.out(stringBody)
113+
.errorOut(stringBody)
114+
.serverLogic { p =>
115+
IO.sleep(3.seconds) *>
116+
IO.pure(Either.cond(p.name == "Jacob", "Welcome", "Unauthorized"))
117+
}

0 commit comments

Comments
 (0)