Skip to content

Commit

Permalink
OtelMetricsTests and Metrics example
Browse files Browse the repository at this point in the history
  • Loading branch information
lhns committed Apr 3, 2024
1 parent 42ed72d commit 18c713f
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 3 deletions.
3 changes: 3 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,11 @@ lazy val `core-jvm-tests` = project
.settings(
libraryDependencies ++= Seq(
"io.opentelemetry" % "opentelemetry-sdk-testing" % openTelemetryV % Test,
"org.http4s" %%% "http4s-server" % http4sV % Test,
"org.typelevel" %%% "cats-effect-testkit" % catsEffectV % Test,
"org.typelevel" %%% "munit-cats-effect" % munitCatsEffectV % Test,
"org.typelevel" %%% "otel4s-oteljava-metrics" % otel4sV % Test,
"org.typelevel" %%% "otel4s-oteljava-metrics-testkit" % otel4sV % Test,
"org.typelevel" %%% "otel4s-oteljava-trace" % otel4sV % Test,
"org.typelevel" %%% "otel4s-oteljava-trace-testkit" % otel4sV % Test,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2023 http4s.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.http4s.otel4s

import cats.data.OptionT
import cats.effect.IO
import io.opentelemetry.sdk.metrics.data.{MetricData => JMetricData}
import munit.CatsEffectSuite
import org.http4s._
import org.http4s.server.middleware.Metrics
import org.typelevel.otel4s.metrics.Meter
import org.typelevel.otel4s.oteljava.AttributeConverters._
import org.typelevel.otel4s.oteljava.testkit.metrics.MetricsTestkit
import io.opentelemetry.api.common.{Attributes => JAttributes}
import scala.jdk.CollectionConverters._

class OtelMetricsTests extends CatsEffectSuite {
test("OtelMetrics") {
MetricsTestkit
.inMemory[IO]()
.use { testkit =>
for {
meterIO <- testkit.meterProvider.get("meter")
metricsOps <- {
implicit val meter: Meter[IO] = meterIO
OtelMetrics.metricsOps[IO]()
}
_ <- {
val fakeServer =
HttpRoutes[IO](e => OptionT.liftF(e.body.compile.drain.as(Response[IO](Status.Ok))))
val meteredServer = Metrics[IO](metricsOps)(fakeServer)

meteredServer
.run(Request[IO](Method.GET))
.semiflatMap(_.body.compile.drain)
.value
}
metrics <- testkit.collectMetrics[JMetricData]
} yield {
def attributes(attrs: JAttributes): Map[String, String] =
attrs.toScala.toSeq.map(e => e.key.name -> e.value.toString).toMap

val activeRequests = metrics.find(_.getName == "http.server.active_requests").get
val activeRequestsDataPoints: Map[Map[String, String], Long] =
activeRequests.getLongSumData.getPoints.asScala.toList
.map(e => attributes(e.getAttributes) -> e.getValue)
.toMap

val requestDuration = metrics.find(_.getName == "http.server.request.duration").get
val requestDurationDataPoints: Map[Map[String, String], Long] =
requestDuration.getHistogramData.getPoints.asScala.toList
.map(e => attributes(e.getAttributes) -> e.getCount)
.toMap

assertEquals(
activeRequestsDataPoints,
Map(
Map("classifier" -> "") -> 0L
),
)

assertEquals(
requestDurationDataPoints,
Map(
Map(
"classifier" -> "",
"http.phase" -> "headers",
"http.request.method" -> "GET",
) -> 1L,
Map(
"classifier" -> "",
"http.phase" -> "body",
"http.request.method" -> "GET",
"http.response.status_code" -> "200",
) -> 1L,
),
)
}
}
}
}
16 changes: 13 additions & 3 deletions examples/src/main/scala/example/Http4sExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@
package example

import cats.effect._
import cats.effect.syntax.all._
import com.comcast.ip4s._
import fs2.io.net.Network
import org.http4s.ember.client.EmberClientBuilder
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.implicits._
import org.http4s.otel4s.OtelMetrics
import org.http4s.otel4s.middleware.ClientMiddleware
import org.http4s.otel4s.middleware.ServerMiddleware
import org.http4s.server.Server
import org.http4s.server.middleware.Metrics
import org.typelevel.otel4s.Otel4s
import org.typelevel.otel4s.metrics.Meter
import org.typelevel.otel4s.oteljava.OtelJava
import org.typelevel.otel4s.trace.Tracer

Expand All @@ -50,15 +54,19 @@ object Http4sExample extends IOApp with Common {
def tracer[F[_]](otel: Otel4s[F]): F[Tracer[F]] =
otel.tracerProvider.tracer("Http4sExample").get

def meter[F[_]](otel: Otel4s[F]): F[Meter[F]] =
otel.meterProvider.meter("Http4sExample").get

// Our main app resource
def server[F[_]: Async: Network: Tracer]: Resource[F, Server] =
def server[F[_]: Async: Network: Tracer: Meter]: Resource[F, Server] =
for {
client <- EmberClientBuilder
.default[F]
.build
.map(ClientMiddleware.default.build)
metricsOps <- OtelMetrics.metricsOps[F]().toResource
app = ServerMiddleware.default[F].buildHttpApp {
routes(client).orNotFound
Metrics(metricsOps)(routes(client)).orNotFound
}
sv <- EmberServerBuilder.default[F].withPort(port"8080").withHttpApp(app).build
} yield sv
Expand All @@ -69,7 +77,9 @@ object Http4sExample extends IOApp with Common {
.autoConfigured[IO]()
.flatMap { otel4s =>
Resource.eval(tracer(otel4s)).flatMap { implicit T: Tracer[IO] =>
server[IO]
Resource.eval(meter(otel4s)).flatMap { implicit M: Meter[IO] =>
server[IO]
}
}
}
.use(_ => IO.never)
Expand Down

0 comments on commit 18c713f

Please sign in to comment.