Record application metrics
Use this page when you want to record counters, gauges, up-down counters, histograms, or observable measurements in application code.
Prerequisites
1. Get a Meter
Get a Meter from the MeterProvider you created during setup.
import cats.effect.{IO, IOApp}
import org.typelevel.otel4s.metrics.Meter
import org.typelevel.otel4s.oteljava.OtelJava
object Main extends IOApp.Simple {
def run: IO[Unit] =
OtelJava.autoConfigured[IO]().use { otel4s =>
otel4s.meterProvider.get("auth-service").flatMap { implicit meter =>
program
}
}
def program(implicit meter: Meter[IO]): IO[Unit] =
IO(meter).void
}
get("auth-service") names the instrumentation scope for the meter. Use a stable name that identifies the code emitting
telemetry, such as your application or module name.
2. Create the instruments you need
Create instruments once and reuse them while the application runs.
- Use
Counterfor values that only go up. - Use
Gaugefor non-additive values such as queue depth or cache size. - Use
UpDownCounterfor values that can go up and down. - Use
Histogramfor distributions such as durations or payload sizes.
import cats.effect.IO
import org.typelevel.otel4s.metrics.{Counter, Gauge, Histogram, UpDownCounter, Meter}
case class UserMetrics(
missingUsers: Counter[IO, Long],
cachedUsers: Gauge[IO, Long],
activeRequests: UpDownCounter[IO, Long],
lookupDuration: Histogram[IO, Double]
)
object UserMetrics {
def create(implicit meter: Meter[IO]): IO[UserMetrics] =
for {
missingUsers <- meter.counter[Long]("user.lookup.missing").create
cachedUsers <- meter.gauge[Long]("user.storage.size").create
activeRequests <- meter.upDownCounter[Long]("http.server.active_requests").create
lookupDuration <- meter.histogram[Double]("user.lookup.duration").withUnit("ms").create
} yield UserMetrics(missingUsers, cachedUsers, activeRequests, lookupDuration)
}
3. Record measurements in application code
Use the instruments inline with the work they measure.
import java.util.concurrent.TimeUnit
import cats.effect.{IO, Ref}
import cats.syntax.all._
case class User(id: Long, email: String)
class UserService(
storage: Ref[IO, Map[Long, User]],
metrics: UserMetrics
) {
def handleRequest(userId: Long): IO[Option[User]] =
metrics.activeRequests.inc() *>
metrics.lookupDuration
.recordDuration(TimeUnit.MILLISECONDS)
.surround(
storage.get.flatMap { current =>
metrics.cachedUsers.record(current.size.toLong) *>
IO.pure(current.get(userId)).flatTap {
case Some(_) => IO.unit
case None => metrics.missingUsers.inc()
}
}
)
.guarantee(metrics.activeRequests.dec())
}
4. Register an observable instrument for on-demand values
Use an observable instrument when the value should be read at collection time instead of being recorded inline.
import cats.effect.Resource
def registerStorageSize(
storage: Ref[IO, Map[Long, User]]
)(implicit meter: Meter[IO]): Resource[IO, Unit] =
meter
.observableGauge[Long]("user.storage.size")
.withDescription("Current number of cached users")
.createWithCallback { cb =>
storage.get.flatMap(users => cb.record(users.size.toLong))
}
.void
What's next
- Create an instrument with metadata from an OpenTelemetry semantic convention: Create metrics from semantic metric specs
- Export runtime metrics from Cats Effect: Register Cats Effect runtime metrics
- Customize histogram buckets for a specific metric: Customize histogram buckets
- Look up the core metrics interfaces and operations: Metrics API reference