ZIO provides comprehensive metrics and observability capabilities for monitoring application performance, collecting telemetry data, and gaining insights into system behavior.
ZIO's metric system allows you to collect, aggregate, and export various types of metrics with composable metric definitions.
/**
* A Metric represents a composable way to collect measurements
*/
sealed trait Metric[In, Out] {
/** Contramap the input type */
def contramap[In2](f: In2 => In): Metric[In2, Out]
/** Map the output type */
def map[Out2](f: Out => Out2): Metric[In, Out2]
/** Combine with another metric */
def zip[In2, Out2](that: Metric[In2, Out2]): Metric[(In, In2), (Out, Out2)]
/** Add tags to the metric */
def tagged(tag: MetricLabel, tags: MetricLabel*): Metric[In, Out]
/** Add tags conditionally */
def taggedWith[A](f: A => Set[MetricLabel]): Metric[(In, A), Out]
}
/**
* Common metric types
*/
object Metric {
/** Counter that only increases */
def counter(name: String): Metric[Double, MetricState.Counter]
/** Gauge that can increase or decrease */
def gauge(name: String): Metric[Double, MetricState.Gauge]
/** Histogram for distribution measurements */
def histogram(name: String, boundaries: MetricKeyType.Histogram.Boundaries): Metric[Double, MetricState.Histogram]
/** Summary for tracking count, sum, min, max */
def summary(name: String): Metric[Double, MetricState.Summary]
/** Set gauge for tracking distinct values */
def setGauge(name: String): Metric[Set[String], MetricState.Gauge]
/** Frequency metric for counting occurrences */
def frequency(name: String): Metric[String, MetricState.Frequency]
}
/**
* Metric labels for categorization
*/
case class MetricLabel(key: String, value: String)
object MetricLabel {
def apply(key: String, value: String): MetricLabel = new MetricLabel(key, value)
}
/**
* Metric key for identification
*/
case class MetricKey[Type <: MetricKeyType](
name: String,
keyType: Type,
description: Option[String] = None,
tags: Set[MetricLabel] = Set.empty
)Usage Examples:
import zio._
import zio.metrics._
// Define metrics
val requestCounter = Metric.counter("http_requests_total")
.tagged(MetricLabel("service", "api"))
val responseTimeHistogram = Metric.histogram(
"http_request_duration_seconds",
MetricKeyType.Histogram.Boundaries.exponential(0.001, 2.0, 10)
).tagged(MetricLabel("endpoint", "/users"))
val activeConnectionsGauge = Metric.gauge("active_connections")
// Use metrics in application code
val httpHandler = for {
startTime <- Clock.nanoTime
// Increment request counter
_ <- requestCounter.update(1.0)
// Increment active connections
_ <- activeConnectionsGauge.update(1.0)
// Process request
response <- processRequest()
// Record response time
endTime <- Clock.nanoTime
duration = (endTime - startTime) / 1e9
_ <- responseTimeHistogram.update(duration)
// Decrement active connections
_ <- activeConnectionsGauge.update(-1.0)
} yield responseThe metric client provides centralized metric collection and export capabilities.
/**
* Client for collecting and exporting metrics
*/
trait MetricClient {
/** Get current snapshot of all metrics */
def snapshot: UIO[Set[MetricPair.Untyped]]
/** Track a metric value */
def track[In, Out](metric: Metric[In, Out], value: In): UIO[Unit]
/** Get specific metric state */
def get[Out](key: MetricKey[_]): UIO[Option[Out]]
}
/**
* Metric pair containing key and state
*/
sealed trait MetricPair[+Type <: MetricKeyType] {
def metricKey: MetricKey[Type]
def metricState: MetricState
}
object MetricPair {
type Untyped = MetricPair[MetricKeyType]
}
/**
* Different types of metric states
*/
sealed trait MetricState
object MetricState {
/** Counter state */
final case class Counter(count: Double, startTime: java.time.Instant) extends MetricState
/** Gauge state */
final case class Gauge(value: Double, startTime: java.time.Instant) extends MetricState
/** Histogram state */
final case class Histogram(
buckets: Chunk[(Double, Long)],
count: Long,
min: Double,
max: Double,
sum: Double,
startTime: java.time.Instant
) extends MetricState
/** Summary state */
final case class Summary(
error: Double,
quantiles: Chunk[(Double, Option[Double])],
count: Long,
min: Double,
max: Double,
sum: Double,
startTime: java.time.Instant
) extends MetricState
/** Frequency state */
final case class Frequency(occurrences: Map[String, Long]) extends MetricState
}Usage Examples:
// Create and configure metric client
val metricClientLayer = ZLayer.succeed(MetricClient.default)
// Collect metrics snapshot
val metricsReport = for {
client <- ZIO.service[MetricClient]
snapshot <- client.snapshot
_ <- ZIO.foreach(snapshot) { pair =>
Console.printLine(s"${pair.metricKey.name}: ${pair.metricState}")
}
} yield ()
// Custom metric tracking
val customTracking = for {
client <- ZIO.service[MetricClient]
metric = Metric.counter("custom_events")
_ <- client.track(metric, 1.0)
state <- client.get(metric.key)
} yield stateZIO automatically collects various system and runtime metrics when enabled.
/**
* JVM and system metrics automatically collected
*/
object BuiltInMetrics {
/** JVM memory usage metrics */
val jvmMemoryUsed: Metric[Any, MetricState.Gauge]
val jvmMemoryCommitted: Metric[Any, MetricState.Gauge]
val jvmMemoryMax: Metric[Any, MetricState.Gauge]
/** JVM garbage collection metrics */
val jvmGcTime: Metric[Any, MetricState.Counter]
val jvmGcCollections: Metric[Any, MetricState.Counter]
/** Thread pool metrics */
val jvmThreadsActive: Metric[Any, MetricState.Gauge]
val jvmThreadsDaemon: Metric[Any, MetricState.Gauge]
val jvmThreadsPeak: Metric[Any, MetricState.Gauge]
/** ZIO fiber metrics */
val fiberCount: Metric[Any, MetricState.Gauge]
val fiberStarted: Metric[Any, MetricState.Counter]
val fiberCompleted: Metric[Any, MetricState.Counter]
/** System CPU and load metrics */
val systemCpuUsage: Metric[Any, MetricState.Gauge]
val systemLoadAverage: Metric[Any, MetricState.Gauge]
/** Enable built-in metrics collection */
def enable: ZLayer[Any, Nothing, Unit]
}Usage Examples:
// Enable built-in metrics
val app = myApplication.provide(
// Other layers
MetricClient.default,
BuiltInMetrics.enable
)
// Monitor fiber lifecycle
val fiberMonitoring = for {
_ <- Console.printLine("Starting intensive computation")
// This will be tracked by fiber metrics
fibers <- ZIO.foreach(1 to 100) { i =>
intensiveComputation(i).fork
}
results <- ZIO.foreach(fibers)(_.join)
// Check current fiber count
client <- ZIO.service[MetricClient]
fiberCount <- client.get(BuiltInMetrics.fiberCount.key)
_ <- Console.printLine(s"Current fiber count: $fiberCount")
} yield resultsMetric aspects provide declarative ways to add metrics to ZIO effects and layers.
/**
* Aspects for adding metrics to effects
*/
object MetricAspect {
/** Count executions of an effect */
def count(metric: Metric[Long, MetricState.Counter]): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any]
/** Time duration of effect execution */
def timed(metric: Metric[Duration, MetricState.Histogram]): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any]
/** Track gauge value during effect */
def trackGauge(metric: Metric[Double, MetricState.Gauge], value: => Double): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any]
/** Count successes and failures separately */
def countErrors(
successMetric: Metric[Long, MetricState.Counter],
errorMetric: Metric[Long, MetricState.Counter]
): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any]
/** Track different metrics based on result */
def trackResult[E, A](
f: Either[E, A] => List[(Metric[Double, _], Double)]
): ZIOAspect[Nothing, Any, E, E, A, A]
}Usage Examples:
// Add metrics to effects using aspects
val databaseQuery = queryDatabase(userId)
@@ MetricAspect.count(Metric.counter("db_queries"))
@@ MetricAspect.timed(Metric.histogram("db_query_duration", boundaries))
@@ MetricAspect.countErrors(
Metric.counter("db_queries_success"),
Metric.counter("db_queries_error")
)
// Track business metrics
val orderProcessing = processOrder(order)
@@ MetricAspect.trackResult {
case Right(order) => List(
(Metric.counter("orders_completed"), 1.0),
(Metric.gauge("order_value"), order.totalAmount)
)
case Left(error) => List(
(Metric.counter("orders_failed"), 1.0)
)
}
// Track resource usage
val resourceIntensiveTask = computeResults()
@@ MetricAspect.trackGauge(Metric.gauge("cpu_intensive_tasks"), 1.0)Export metrics to external monitoring systems and integrate with observability platforms.
/**
* Metric exporters for external systems
*/
trait MetricExporter {
/** Export metrics snapshot */
def export(snapshot: Set[MetricPair.Untyped]): Task[Unit]
}
/**
* Common metric export formats
*/
object MetricExporter {
/** Export to Prometheus format */
def prometheus(registry: Registry): MetricExporter
/** Export to StatsD */
def statsD(client: StatsDClient): MetricExporter
/** Export to console for debugging */
val console: MetricExporter
/** Export to JSON format */
def json(writer: JsonWriter): MetricExporter
/** Composite exporter for multiple destinations */
def composite(exporters: MetricExporter*): MetricExporter
}
/**
* Periodic metric export scheduler
*/
object MetricScheduler {
/** Schedule periodic metric exports */
def schedule(
client: MetricClient,
exporter: MetricExporter,
interval: Duration
): ZIO[Any, Nothing, Fiber.Runtime[Throwable, Nothing]]
}Usage Examples:
// Set up metric export to multiple systems
val metricExportLayer = ZLayer.scoped {
for {
client <- ZIO.service[MetricClient]
// Create composite exporter
exporter = MetricExporter.composite(
MetricExporter.prometheus(prometheusRegistry),
MetricExporter.statsD(statsDClient),
MetricExporter.console // For debugging
)
// Schedule periodic export
scheduler <- MetricScheduler.schedule(client, exporter, 30.seconds)
// Ensure cleanup
_ <- ZIO.addFinalizer(scheduler.interrupt.ignore)
} yield ()
}
// Full application with metrics
val monitoredApp = myApplication.provide(
MetricClient.default,
BuiltInMetrics.enable,
metricExportLayer,
// Other application layers
)
// Manual metric export
val exportMetrics = for {
client <- ZIO.service[MetricClient]
snapshot <- client.snapshot
_ <- MetricExporter.console.export(snapshot)
} yield ()Advanced performance monitoring and profiling capabilities for production applications.
/**
* Performance monitoring utilities
*/
object PerformanceMonitoring {
/** Monitor method execution with detailed timing */
def profileMethod[R, E, A](
methodName: String,
thresholds: Map[String, Duration] = Map.empty
)(effect: ZIO[R, E, A]): ZIO[R, E, A]
/** Monitor memory allocation during effect */
def trackMemory[R, E, A](effect: ZIO[R, E, A]): ZIO[R, E, (A, MemoryUsage)]
/** Monitor fiber scheduling and execution */
def trackFiberMetrics[R, E, A](effect: ZIO[R, E, A]): ZIO[R, E, A]
/** Create performance dashboard data */
def dashboardSnapshot: UIO[PerformanceDashboard]
}
/**
* Memory usage information
*/
case class MemoryUsage(
heapUsed: Long,
heapCommitted: Long,
heapMax: Long,
nonHeapUsed: Long,
nonHeapCommitted: Long
)
/**
* Performance dashboard data
*/
case class PerformanceDashboard(
uptime: Duration,
fiberStats: FiberStats,
memoryStats: MemoryUsage,
gcStats: GCStats,
threadStats: ThreadStats
)Usage Examples:
// Profile critical methods
val criticalOperation = performCriticalTask()
@@ PerformanceMonitoring.profileMethod(
"critical_task",
Map(
"warning" -> 100.millis,
"critical" -> 500.millis
)
)
// Monitor memory-intensive operations
val memoryIntensiveTask = for {
(result, memUsage) <- PerformanceMonitoring.trackMemory {
processLargeDataset(dataset)
}
_ <- Console.printLine(s"Memory used: ${memUsage.heapUsed / 1024 / 1024} MB")
} yield result
// Create performance monitoring dashboard
val performanceDashboard = for {
dashboard <- PerformanceMonitoring.dashboardSnapshot
_ <- Console.printLine(s"Uptime: ${dashboard.uptime}")
_ <- Console.printLine(s"Active fibers: ${dashboard.fiberStats.active}")
_ <- Console.printLine(s"Heap usage: ${dashboard.memoryStats.heapUsed}")
} yield dashboard