Metric
Records and reads measurements from Effect programs.
A Metric<Input, State> accepts typed update values and stores an aggregated
state that can be read directly or included in a snapshot. Metrics are used
for counters, gauges, frequencies, histograms, summaries, and timers. This
module includes metric constructors, update and read helpers, attributes,
histogram boundaries, registry snapshots, text dumps, and controls for
enabling runtime metrics.
Annotations
Returns a new metric that is powered by this one, but which accepts updates of the specified new type, which must be transformable to the input type of this metric.
Signature
declare const mapInput: { <Input, Input2>(f: (input: Input2, context: Context<never>) => Input): <State>(self: Metric<Input, State>) => Metric<Input2, State>; <Input, State, Input2>(self: Metric<Input, State>, f: (input: Input2, context: Context<never>) => Input): Metric<Input2, State>;}Example
(Mapping metric inputs)
import { Effect, Metric } from "effect"
const durationHistogram = Metric.histogram("request_duration_ms", { description: "Request duration in milliseconds", boundaries: Metric.linearBoundaries({ start: 0, width: 100, count: 10 })})
// Accept duration strings while recording numeric millisecondsconst durationStringHistogram = Metric.mapInput( durationHistogram, (input: string) => Number(input))
const program = Effect.gen(function*() { yield* Metric.update(durationStringHistogram, "250") return yield* Metric.value(durationStringHistogram)})
const value = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const values = [value.count, value.sum] // => [1, 250]Constants
CurrentMetricAttributesKey
Service key for the current metric attributes context.
Signature
declare const CurrentMetricAttributesKey: "effect/Metric/CurrentMetricAttributes"Example
(Accessing the current metric attributes key)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // The key is used internally by the Effect runtime to manage metric attributes const key = Metric.CurrentMetricAttributesKey
// Create metrics with base attributes const requestCounter = Metric.counter("requests_total", { description: "Total HTTP requests" })
// The CurrentMetricAttributes service provides default attributes // that get applied to all metrics in the current context const baseAttributes = { service: "api", version: "1.0" }
// Use withAttributes to apply attributes to metrics const taggedCounter1 = Metric.withAttributes(requestCounter, baseAttributes) const program1 = Metric.update(taggedCounter1, 1)
const taggedCounter2 = Metric.withAttributes(requestCounter, { ...baseAttributes, endpoint: "/users" }) const program2 = Metric.update(taggedCounter2, 5)
yield* program1 yield* program2
return { keyValue: key, // "effect/Metric/CurrentMetricAttributes" keyType: typeof key, // "string" isConstant: key === "effect/Metric/CurrentMetricAttributes" // true }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const key = result // => { keyValue: "effect/Metric/CurrentMetricAttributes", keyType: "string", isConstant: true }FiberRuntimeMetricsKey
Service key for the fiber runtime metrics service.
Signature
declare const FiberRuntimeMetricsKey: "effect/observability/Metric/FiberRuntimeMetricsKey"Example
(Accessing the fiber runtime metrics key)
import { Metric } from "effect"
Metric.FiberRuntimeMetricsKey // => "effect/observability/Metric/FiberRuntimeMetricsKey"Constructors
boundariesFromIterable
Creates histogram bucket boundaries from an iterable set of values.
Details
Processes any iterable of numbers by removing duplicates, filtering out non-positive values, and automatically appending positive infinity as the final boundary.
Signature
declare function boundariesFromIterable(iterable: Iterable<number>): readonly Array<number>Example
(Creating boundaries from values)
import { Metric } from "effect"
Metric.boundariesFromIterable([-5, 0, 10, 10, 25, 50]) // => [10, 25, 50, Infinity]Represents a Counter metric that tracks cumulative numerical values over time. Counters can be incremented and decremented and provide a running total of changes.
Details
The optional description describes the counter, and attributes attach
dimensions to it. Set bigint to create a counter that accepts bigint
inputs. Set incremental to true to create a counter that can only ever be
incremented.
Signature
declare const counter: { (name: string, options?: { readonly attributes?: Metric.Attributes; readonly bigint?: false; readonly description?: string; readonly incremental?: boolean; }): Counter<number>; (name: string, options: { readonly attributes?: Metric.Attributes; readonly bigint: true; readonly description?: string; readonly incremental?: boolean; }): Counter<bigint>;}Example
(Creating counter metrics)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create a basic counter for tracking requests const requestCounter = Metric.counter("http_requests_total", { description: "Total number of HTTP requests processed" })
// Create an incremental-only counter for events const eventCounter = Metric.counter("events_processed", { description: "Events processed (increment only)", incremental: true })
// Create a bigint counter for large values const bytesCounter = Metric.counter("bytes_transferred", { description: "Total bytes transferred", bigint: true, attributes: { service: "file-transfer" } })
// Update counters with values yield* Metric.update(requestCounter, 1) // Increment by 1 yield* Metric.update(requestCounter, 5) // Increment by 5 (total: 6) yield* Metric.update(eventCounter, 1) // Increment by 1 yield* Metric.update(bytesCounter, 1024n) // Add 1024 bytes
// Get current counter values const requestValue = yield* Metric.value(requestCounter) const eventValue = yield* Metric.value(eventCounter) const bytesValue = yield* Metric.value(bytesCounter)
return { requestValue, eventValue, bytesValue }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const counts = [result.requestValue.count, result.eventValue.count, result.bytesValue.count] // => [6, 1, 1024n]exponentialBoundaries
Creates histogram bucket boundaries with exponentially increasing values.
Details
Creates boundaries that grow exponentially, useful for metrics that span multiple orders of magnitude. Each boundary is calculated as start * factor^i.
Signature
declare function exponentialBoundaries(options: { readonly count: number; readonly factor: number; readonly start: number;}): readonly Array<number>Example
(Creating exponential boundaries)
import { Metric } from "effect"
Metric.exponentialBoundaries({ start: 1, factor: 2, count: 5 }) // => [1, 2, 4, 8, Infinity]Creates a Frequency metric which can be used to count the number of
occurrences of a string.
When to use
Use when you need a metric for counting how often a specific event or incident occurs.
Details
The optional description describes the frequency, and attributes attach
dimensions to it. Use preregisteredWords to initialize occurrence counts
for known string values before updates arrive.
Signature
declare function frequency(name: string, options?: { readonly attributes?: Attributes; readonly description?: string; readonly preregisteredWords?: readonly Array<string>;}): FrequencyExample
(Creating frequency metrics)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create a frequency metric for HTTP status codes const statusFrequency = Metric.frequency("http_status_codes", { description: "Frequency of HTTP response status codes", preregisteredWords: ["200", "404", "500"] // Pre-register common codes })
// Create a frequency metric for user actions const userActionFrequency = Metric.frequency("user_actions", { description: "Frequency of user actions performed", attributes: { application: "web-app" } })
// Create a frequency metric for error types const errorTypeFrequency = Metric.frequency("error_types", { description: "Frequency of different error types" })
// Record different occurrences yield* Metric.update(statusFrequency, "200") // Success response yield* Metric.update(statusFrequency, "200") // Another success yield* Metric.update(statusFrequency, "404") // Not found error yield* Metric.update(statusFrequency, "500") // Server error yield* Metric.update(statusFrequency, "200") // Another success
yield* Metric.update(userActionFrequency, "login") yield* Metric.update(userActionFrequency, "view_dashboard") yield* Metric.update(userActionFrequency, "login") yield* Metric.update(userActionFrequency, "logout")
yield* Metric.update(errorTypeFrequency, "ValidationError") yield* Metric.update(errorTypeFrequency, "NetworkError") yield* Metric.update(errorTypeFrequency, "ValidationError")
// Get frequency counts const statusCounts = yield* Metric.value(statusFrequency) const actionCounts = yield* Metric.value(userActionFrequency) const errorCounts = yield* Metric.value(errorTypeFrequency)
// statusCounts.occurrences will be: // Map { "200" => 3, "404" => 1, "500" => 1 } // actionCounts.occurrences will be: // Map { "login" => 2, "view_dashboard" => 1, "logout" => 1 } // errorCounts.occurrences will be: // Map { "ValidationError" => 2, "NetworkError" => 1 }
return { statusCounts, actionCounts, errorCounts }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const counts = [ result.statusCounts.occurrences.get("200"), result.actionCounts.occurrences.get("login"), result.errorCounts.occurrences.get("ValidationError")]counts // => [3, 2, 2]Represents a Gauge metric that tracks and reports a single numerical value
at a specific moment.
When to use
Use when you need a metric for instantaneous values, such as memory usage or CPU load.
Details
The optional description describes the gauge, and attributes attach
dimensions to it. Set bigint to create a gauge that accepts bigint
inputs.
Signature
declare const gauge: { (name: string, options?: { readonly attributes?: Metric.Attributes; readonly bigint?: false; readonly description?: string; }): Gauge<number>; (name: string, options: { readonly attributes?: Metric.Attributes; readonly bigint: true; readonly description?: string; }): Gauge<bigint>;}Example
(Creating gauge metrics)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create a gauge for tracking memory usage const memoryGauge = Metric.gauge("memory_usage_mb", { description: "Current memory usage in megabytes" })
// Create a gauge for CPU utilization const cpuGauge = Metric.gauge("cpu_utilization", { description: "Current CPU utilization percentage", attributes: { host: "server-01" } })
// Create a bigint gauge for large values const diskSpaceGauge = Metric.gauge("disk_free_bytes", { description: "Free disk space in bytes", bigint: true })
// Set gauge values (replaces current value) yield* Metric.update(memoryGauge, 512) // Set to 512 MB yield* Metric.update(cpuGauge, 85.5) // Set to 85.5% yield* Metric.update(diskSpaceGauge, 1024000000n) // Set to ~1GB
// Modify gauge values (adds to current value) yield* Metric.modify(memoryGauge, 128) // Increase by 128 MB (total: 640) yield* Metric.modify(cpuGauge, -10.5) // Decrease by 10.5% (total: 75%)
// Update with new absolute values yield* Metric.update(memoryGauge, 800) // Set to 800 MB (replaces 640)
// Get current gauge values const memoryValue = yield* Metric.value(memoryGauge) const cpuValue = yield* Metric.value(cpuGauge) const diskValue = yield* Metric.value(diskSpaceGauge)
return { memoryValue, cpuValue, diskValue }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const values = [result.memoryValue.value, result.cpuValue.value, result.diskValue.value] // => [800, 75, 1024000000n]Represents a Histogram metric that records observations into buckets.
When to use
Use when you need a metric for measuring the distribution of values within a range.
Details
The optional description describes the histogram, and attributes attach
dimensions to it. The required boundaries option defines the histogram
bucket boundaries.
Signature
declare function histogram(name: string, options: { readonly attributes?: Attributes; readonly boundaries: readonly Array<number>; readonly description?: string;}): Histogram<number>Example
(Creating histogram metrics)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create a histogram for API response times const responseTimeHistogram = Metric.histogram("api_response_time", { description: "Distribution of API response times in milliseconds", boundaries: Metric.linearBoundaries({ start: 0, width: 50, count: 10 }) // Creates buckets: 0-50ms, 50-100ms, 100-150ms, ..., 350-400ms, 400ms+ })
// Create a histogram for request payload sizes const payloadSizeHistogram = Metric.histogram("payload_size", { description: "Distribution of request payload sizes in KB", boundaries: Metric.exponentialBoundaries({ start: 1, factor: 2, count: 8 }), // Creates exponential buckets: 1KB, 2KB, 4KB, 8KB, 16KB, 32KB, 64KB, 128KB+ attributes: { service: "api-gateway" } })
// Create a histogram with custom boundaries const customHistogram = Metric.histogram("custom_metric", { description: "Custom distribution metric", boundaries: [0.1, 0.5, 1, 2.5, 5, 10, 25, 50, 100] })
// Record various response times yield* Metric.update(responseTimeHistogram, 25) // Goes in 0-50ms bucket yield* Metric.update(responseTimeHistogram, 75) // Goes in 50-100ms bucket yield* Metric.update(responseTimeHistogram, 125) // Goes in 100-150ms bucket yield* Metric.update(responseTimeHistogram, 200) // Goes in 150-200ms bucket yield* Metric.update(responseTimeHistogram, 75) // Another 50-100ms
// Record payload sizes yield* Metric.update(payloadSizeHistogram, 3) // Goes in 2-4KB bucket yield* Metric.update(payloadSizeHistogram, 15) // Goes in 8-16KB bucket yield* Metric.update(payloadSizeHistogram, 0.5) // Goes in 0-1KB bucket
// Get histogram state with distribution data const responseTimeState = yield* Metric.value(responseTimeHistogram) const payloadSizeState = yield* Metric.value(payloadSizeHistogram)
// responseTimeState will contain: // - buckets: [[50, 1], [100, 3], [150, 4], [200, 5], ...] // - count: 5, min: 25, max: 200, sum: 500 // - Useful for calculating percentiles, averages, etc.
return { responseTimeState, payloadSizeState }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const values = [result.responseTimeState.count, result.responseTimeState.sum, result.payloadSizeState.count]values // => [5, 500, 3]linearBoundaries
Creates histogram bucket boundaries from a linear sequence and appends positive infinity.
Details
Generates count - 1 candidate boundaries using start + index * width for
each zero-based index, then applies the same normalization as
boundariesFromIterable: non-positive values are removed, duplicates are
collapsed, and Infinity is appended.
Signature
declare function linearBoundaries(options: { readonly count: number; readonly start: number; readonly width: number;}): readonly Array<number>Example
(Creating linear boundaries)
import { Metric } from "effect"
Metric.linearBoundaries({ start: 10, width: 20, count: 5 }) // => [10, 30, 50, 70, Infinity]Creates a Summary metric that records observations and calculates quantiles
which takes a value as input and uses the current time.
When to use
Use when you need a metric that records statistical information about a set of values, including quantiles.
Details
The optional description describes the summary, and attributes attach
dimensions to it. maxAge controls how long observations are retained,
maxSize controls how many observations are kept, and quantiles lists the
quantiles to calculate, such as [0.5, 0.9].
Signature
declare function summary(name: string, options: { readonly attributes?: Attributes; readonly description?: string; readonly maxAge: Input; readonly maxSize: number; readonly quantiles: readonly Array<number>;}): Summary<number>Example
(Creating summary metrics)
import { Duration, Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create a summary for API response times const responseTimeSummary = Metric.summary("api_response_time", { description: "API response time quantiles over 5-minute windows", maxAge: Duration.minutes(5), // Keep observations for 5 minutes maxSize: 1000, // Maximum 1000 observations in memory quantiles: [0.5, 0.9, 0.95, 0.99] // 50th, 90th, 95th, 99th percentiles })
// Create a summary for request payload sizes const payloadSizeSummary = Metric.summary("request_payload_size", { description: "Request payload size distribution over 2-minute windows", maxAge: Duration.minutes(2), // Shorter window for recent trends maxSize: 500, // Smaller buffer for memory efficiency quantiles: [0.5, 0.75, 0.9], // Median, 75th, 90th percentiles attributes: { service: "upload-service" } })
// Record deterministic response times const responseTimes = [82, 96, 104, 118, 135, 170, 210, 240] for (const responseTime of responseTimes) { yield* Metric.update(responseTimeSummary, responseTime) }
// Record some payload sizes yield* Metric.update(payloadSizeSummary, 1.2) // 1.2KB yield* Metric.update(payloadSizeSummary, 5.8) // 5.8KB yield* Metric.update(payloadSizeSummary, 15.6) // 15.6KB yield* Metric.update(payloadSizeSummary, 3.4) // 3.4KB
// Get summary statistics with quantiles const responseStats = yield* Metric.value(responseTimeSummary) const payloadStats = yield* Metric.value(payloadSizeSummary)
// Both summaries include quantile information for their configured windows.
return { responseStats, payloadStats }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const response = result.responseStatsconst payload = result.payloadStatsconst responseValues = [response.count, response.min, response.max, response.sum] // => [8, 82, 240, 1155]const payloadValues = [payload.count, payload.min, payload.max, payload.sum] // => [4, 1.2, 15.6, 26]summaryWithTimestamp
Creates a Summary metric that records observations with explicit
timestamps and calculates quantiles.
When to use
Use when you need a metric that records statistical information about a set of values together with timestamps.
Details
Inputs to this metric are [value, timestamp] pairs; the current clock is
used when reading quantiles against the configured maxAge.
The optional description describes the summary, and attributes attach
dimensions to it. maxAge controls how long observations are retained,
maxSize controls how many observations are kept, and quantiles lists the
quantiles to calculate, such as [0.5, 0.9].
Signature
declare function summaryWithTimestamp(name: string, options: { readonly attributes?: Attributes; readonly description?: string; readonly maxAge: Input; readonly maxSize: number; readonly quantiles: readonly Array<number>;}): Summary<[value: number, timestamp: number]>Example
(Creating summaries with explicit timestamps)
import { Metric } from "effect"
const responseTimesSummary = Metric.summaryWithTimestamp( "response_times_summary", { description: "Measures the distribution of response times", maxAge: "60 seconds", // Retain observations for 60 seconds. maxSize: 1000, // Keep a maximum of 1000 observations. quantiles: [0.5, 0.9, 0.99] // Calculate 50th, 90th, and 99th quantiles. })const metadata = [responseTimesSummary.id, responseTimesSummary.type] // => ["response_times_summary", "Summary"]Creates a timer metric, based on a Histogram, which keeps track of
durations in milliseconds.
Details
The unit of time will automatically be added to the metric as a tag (i.e.
"time_unit: milliseconds").
If options.boundaries is not provided, the boundaries will be computed
using Metric.exponentialBoundaries({ start: 0.5, factor: 2, count: 35 }).
Signature
declare function timer(name: string, options?: { readonly attributes?: Attributes; readonly boundaries?: readonly Array<number>; readonly description?: string;}): Histogram<Duration>Example
(Recording durations with a timer)
import { Duration, Effect, Metric } from "effect"
// Create a timer metric to track API request durationsconst apiRequestTimer = Metric.timer("api_request_duration", { description: "Duration of API requests", attributes: { service: "user-api" }})
// Record a measured API operation durationconst apiOperation = Effect.gen(function*() { const duration = Duration.millis(120) yield* Metric.update(apiRequestTimer, duration)
const state = yield* Metric.value(apiRequestTimer) return { count: state.count, min: state.min, max: state.max, sum: state.sum }})
await Effect.runPromise( Effect.provideService(apiOperation, Metric.MetricRegistry, new Map())) // => { count: 1, min: 120, max: 120, sum: 120 }Formatting
Returns a human-readable string representation of all currently registered metrics in a tabular format.
Details
This debugging utility captures a snapshot of all metrics and formats them in an easy-to-read table showing names, descriptions, types, attributes, and current state values.
Signature
declare const dump: Effect<string>Example
(Dumping metrics as text)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create and update some metrics for demonstration const requestCounter = Metric.counter("http_requests_total", { description: "Total HTTP requests" }) const responseTime = Metric.gauge("response_time_ms", { description: "Current response time in milliseconds" }) const statusFreq = Metric.frequency("http_status_codes", { description: "Frequency of HTTP status codes" })
// Update metrics with some values yield* Metric.update(requestCounter, 1) yield* Metric.update(requestCounter, 1) yield* Metric.update(responseTime, 125) yield* Metric.update(statusFreq, "200") yield* Metric.update(statusFreq, "404") yield* Metric.update(statusFreq, "200")
// Get formatted dump of all metrics const metricsReport = yield* Metric.dump return metricsReport})
const report = await Effect.runPromise( Effect.provideService(program, Metric.MetricRegistry, new Map()))const included = [ report.includes("http_requests_total"), report.includes("response_time_ms"), report.includes("http_status_codes")]included // => [true, true, true]Getters
Retrieves the current state of the specified Metric.
Details
The returned state depends on the metric type. Counters return
CounterState<number | bigint> with count and incremental, gauges return
GaugeState<number | bigint> with value, frequencies return
FrequencyState with occurrences, histograms return HistogramState with
buckets, count, min, max, and sum, and summaries return SummaryState with
quantiles, count, min, max, and sum.
Signature
declare function value<Input, State>(self: Metric<Input, State>): Effect<State>Example
(Reading metric state)
import { Effect, Metric } from "effect"
const requestCounter = Metric.counter("modify_requests")const responseTime = Metric.histogram("response_time", { boundaries: [100, 500, 1000, 2000]})
const program = Effect.gen(function*() { // Update metrics yield* Metric.update(requestCounter, 1) yield* Metric.update(responseTime, 750)
// Get current values const counterState = yield* Metric.value(requestCounter) const histogramState = yield* Metric.value(responseTime) return { requestCount: counterState.count, count: histogramState.count, min: histogramState.min, max: histogramState.max, average: histogramState.sum / histogramState.count }})
await Effect.runPromise( Effect.provideService(program, Metric.MetricRegistry, new Map())) // => { requestCount: 1, count: 1, min: 750, max: 750, average: 750 }Guards
Returns true if the specified value is a Metric, otherwise returns false.
When to use
Use when you need runtime type checking and ensuring that a value conforms to the Metric interface before performing metric operations.
Signature
declare function isMetric(u: unknown): u is Metric<never, unknown>Example
(Checking metric values)
import { Metric } from "effect"
Metric.isMetric(Metric.counter("requests")) // => trueMetric.isMetric({ name: "requests" }) // => falseLayers
disableRuntimeMetricsLayer
Layer that disables automatic collection of fiber runtime metrics.
Signature
declare const disableRuntimeMetricsLayer: Layer<never, never, never>Example
(Disabling runtime metrics with a layer)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Disable runtime metrics collection const disabledLayer = Metric.disableRuntimeMetricsLayer
return yield* Effect.gen(function*() { // Check that metrics service is disabled const metricsService = yield* Metric.FiberRuntimeMetrics
// Run some Effects - no metrics will be collected yield* Effect.forkChild(Effect.sleep("50 millis")) yield* Effect.forkChild(Effect.sleep("100 millis")) yield* Effect.sleep("200 millis")
// Create test metrics to show they still work const testCounter = Metric.counter("test_counter") yield* Metric.update(testCounter, 1) const counterValue = yield* Metric.value(testCounter)
return { counterValue, metricsEnabled: metricsService !== undefined } }).pipe(Effect.provide(disabledLayer))})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const values = [result.counterValue.count, result.metricsEnabled] // => [1, false]enableRuntimeMetricsLayer
Layer that enables automatic collection of fiber runtime metrics across an entire Effect application.
When to use
Use when you need runtime metrics collection for all Effects in the application context rather than wrapping individual Effects.
Signature
declare const enableRuntimeMetricsLayer: Layer<never, never, never>Example
(Enabling runtime metrics with a layer)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { const service = yield* Metric.FiberRuntimeMetrics return service === Metric.FiberRuntimeMetricsImpl})
await Effect.runPromise(Effect.provide(program, Metric.enableRuntimeMetricsLayer)) // => trueMapping
withAttributes
Returns a new metric that applies the specified attributes to all operations.
Details
Attributes are key-value pairs that provide additional context for metrics, enabling filtering, grouping, and more detailed analysis. Each combination of attribute values creates a separate metric series.
Signature
declare const withAttributes: { (attributes: Attributes): <Input, State>(self: Metric<Input, State>) => Metric<Input, State>; <Input, State>(self: Metric<Input, State>, attributes: Attributes): Metric<Input, State>;}Example
(Applying metric attributes)
import { Effect, Metric } from "effect"
const requestCounter = Metric.counter("http_requests_total", { description: "Total HTTP requests"})
// Create tagged versions of the metricconst getRequests = Metric.withAttributes(requestCounter, { method: "GET", endpoint: "/api/users"})
const postRequests = Metric.withAttributes(requestCounter, { method: "POST", endpoint: "/api/users"})
const program = Effect.gen(function*() { // These will be tracked as separate metric series yield* Metric.update(getRequests, 1) // http_requests_total{method="GET", endpoint="/api/users"} yield* Metric.update(postRequests, 1) // http_requests_total{method="POST", endpoint="/api/users"} yield* Metric.update(getRequests, 1) // Increments the GET counter
// You can also chain attributes const taggedMetric = requestCounter.pipe( Metric.withAttributes({ service: "user-api" }), Metric.withAttributes({ version: "v1" }) )
yield* Metric.update(taggedMetric, 1) // http_requests_total{service="user-api", version="v1"}})
const result = Effect.gen(function*() { yield* program const get = yield* Metric.value(getRequests) const post = yield* Metric.value(postRequests) return [get.count, post.count] as const})
await Effect.runPromise(Effect.provideService(result, Metric.MetricRegistry, new Map())) // => [2, 1]withConstantInput
Returns a new metric that is powered by this one, but which accepts updates of any type, and translates them to updates with the specified constant update value.
Signature
declare const withConstantInput: { <Input>(input: Input): <State>(self: Metric<Input, State>) => Metric<unknown, State>; <Input, State>(self: Metric<Input, State>, input: Input): Metric<unknown, State>;}Example
(Ignoring inputs with a constant value)
import { Effect, Metric } from "effect"
// Create a counter that normally expects a number incrementconst requestCounter = Metric.counter("total_requests", { description: "Total number of requests processed"})
// Create a version that always increments by 1, regardless of inputconst simpleRequestCounter = Metric.withConstantInput(requestCounter, 1)
const program = Effect.gen(function*() { // These all increment the counter by 1, ignoring the input value yield* Metric.update(simpleRequestCounter, "any string") yield* Metric.update(simpleRequestCounter, { complex: "object" }) yield* Metric.update(simpleRequestCounter, 999) // Still increments by 1
const value = yield* Metric.value(simpleRequestCounter) return value // Counter state will show count: 3})
const value = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const count = value.count // => 3Models
A Counter metric that tracks cumulative values that typically only increase.
When to use
Use when counters are useful for tracking monotonically increasing values like request counts, bytes processed, errors encountered, or any value that accumulates over time.
Signature
interface Counter<in Input extends number | bigint> extends Metric<Input, CounterState<Input>> {}Example
(Using counter metrics)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create different types of counters const requestCounter: Metric.Counter<number> = Metric.counter( "http_requests", { description: "Total HTTP requests processed", incremental: true // Only allows increments } )
const bytesCounter: Metric.Counter<bigint> = Metric.counter( "bytes_processed", { description: "Total bytes processed", bigint: true, attributes: { service: "data-processor" } } )
// Update counters yield* Metric.update(requestCounter, 1) // Increment by 1 yield* Metric.update(requestCounter, 5) // Increment by 5 (total: 6) yield* Metric.update(bytesCounter, 1024n) // Add 1024 bytes
// Read counter state const requestState: Metric.CounterState<number> = yield* Metric.value( requestCounter ) const bytesState: Metric.CounterState<bigint> = yield* Metric.value( bytesCounter )
// Counter state contains: // - count: current accumulated value // - incremental: whether only increments are allowed
return { requests: { count: requestState.count, incremental: requestState.incremental }, bytes: { count: bytesState.count, incremental: bytesState.incremental } }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const counts = [result.requests.count, result.bytes.count] // => [6, 1024n]CounterState interface
State interface for Counter metrics containing the current count and increment mode.
Signature
interface CounterState<in Input extends number | bigint> { readonly count: Input extends bigint ? bigint : number; readonly incremental: boolean;}Example
(Reading counter state)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create different types of counters const requestCounter = Metric.counter("http_requests_total") const errorCounter = Metric.counter("errors_total", { incremental: true }) const byteCounter = Metric.counter("bytes_processed", { bigint: true })
// Update counters yield* Metric.update(requestCounter, 5) // Add 5 requests yield* Metric.update(requestCounter, -2) // Subtract 2 (allowed for non-incremental) yield* Metric.update(errorCounter, 3) // Add 3 errors yield* Metric.update(errorCounter, -1) // Attempt to subtract (ignored for incremental) yield* Metric.update(byteCounter, 1024000n) // Add bytes as bigint
// Read counter states const requestState: Metric.CounterState<number> = yield* Metric.value( requestCounter ) const errorState: Metric.CounterState<number> = yield* Metric.value( errorCounter ) const byteState: Metric.CounterState<bigint> = yield* Metric.value( byteCounter ) // CounterState contains: // - count: current count value (number or bigint based on counter type) // - incremental: whether counter only allows increases
return { requests: { total: requestState.count, // 3 (5 - 2, decrements allowed) canDecrease: !requestState.incremental // true }, errors: { total: errorState.count, // 3 (subtract ignored) canDecrease: !errorState.incremental // false }, bytes: { total: byteState.count, // 1024000n canDecrease: !byteState.incremental // true } }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const counts = [result.requests.total, result.errors.total, result.bytes.total] // => [3, 3, 1024000n]A Frequency metric interface that counts occurrences of discrete string values.
When to use
Use when frequency metrics are ideal for tracking categorical data where you want to count how many times specific string values occur, such as HTTP status codes, user actions, error types, or any discrete string-based events.
Signature
interface Frequency extends Metric<string, FrequencyState> {}Example
(Using frequency metrics)
import { Effect, Metric } from "effect"
// Function that accepts any Frequency metricconst analyzeFrequencyMetric = (freq: Metric.Frequency) => Effect.gen(function*() { const state = yield* Metric.value(freq)
// Access the frequency state const occurrences: ReadonlyMap<string, number> = state.occurrences
// Find most frequent value let maxCount = 0 let mostFrequent = "" for (const [value, count] of occurrences) { if (count > maxCount) { maxCount = count mostFrequent = value } }
return { mostFrequent, maxCount, totalUniqueValues: occurrences.size } })
const program = Effect.gen(function*() { // Create frequency metrics const statusCodes: Metric.Frequency = Metric.frequency("http_status", { description: "HTTP status code frequency" })
const userActions: Metric.Frequency = Metric.frequency("user_actions", { description: "User action frequency" })
// Record some occurrences yield* Metric.update(statusCodes, "200") yield* Metric.update(statusCodes, "200") yield* Metric.update(statusCodes, "404") yield* Metric.update(statusCodes, "500") yield* Metric.update(statusCodes, "200")
yield* Metric.update(userActions, "login") yield* Metric.update(userActions, "view_dashboard") yield* Metric.update(userActions, "login")
// Use the function with different frequency metrics const statusAnalysis = yield* analyzeFrequencyMetric(statusCodes) const actionAnalysis = yield* analyzeFrequencyMetric(userActions) return { statusAnalysis, actionAnalysis }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const values = [result.statusAnalysis.mostFrequent, result.actionAnalysis.mostFrequent] // => ["200", "login"]FrequencyState interface
State interface for Frequency metrics containing occurrence counts for discrete string values.
Signature
interface FrequencyState { readonly occurrences: ReadonlyMap<string, number>;}Example
(Reading frequency state)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create frequency metrics for different categories const statusCodeFreq = Metric.frequency("http_status_codes", { description: "HTTP status code distribution" })
const userActionFreq = Metric.frequency("user_actions", { description: "User action frequency" })
// Record occurrences yield* Metric.update(statusCodeFreq, "200") // Success yield* Metric.update(statusCodeFreq, "200") // Another success yield* Metric.update(statusCodeFreq, "404") // Not found yield* Metric.update(statusCodeFreq, "500") // Server error yield* Metric.update(statusCodeFreq, "200") // Another success
yield* Metric.update(userActionFreq, "login") yield* Metric.update(userActionFreq, "click") yield* Metric.update(userActionFreq, "login") yield* Metric.update(userActionFreq, "scroll") yield* Metric.update(userActionFreq, "click") yield* Metric.update(userActionFreq, "click")
// Read frequency states const statusState: Metric.FrequencyState = yield* Metric.value(statusCodeFreq) const actionState: Metric.FrequencyState = yield* Metric.value(userActionFreq)
// FrequencyState contains: // - occurrences: ReadonlyMap<string, number> with string values and their counts
// Analyze frequency distributions const getMostFrequent = (occurrences: ReadonlyMap<string, number>) => { let maxKey = "" let maxCount = 0 for (const [key, count] of occurrences) { if (count > maxCount) { maxKey = key maxCount = count } } return { key: maxKey, count: maxCount } }
const topStatus = getMostFrequent(statusState.occurrences) const topAction = getMostFrequent(actionState.occurrences) return { statusCodes: { totalResponses: Array.from(statusState.occurrences.values()).reduce( (a, b) => a + b, 0 ), // 5 mostCommon: topStatus, // { key: "200", count: 3 } uniqueCodes: statusState.occurrences.size // 3 }, userActions: { totalActions: Array.from(actionState.occurrences.values()).reduce( (a, b) => a + b, 0 ), // 6 mostCommon: topAction, // { key: "click", count: 3 } uniqueActions: actionState.occurrences.size // 3 } }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const mostCommon = [result.statusCodes.mostCommon, result.userActions.mostCommon]mostCommon.map(({ key, count }) => [key, count]) // => [["200", 3], ["click", 3]]A Gauge metric that tracks instantaneous values that can go up or down.
When to use
Use when gauges are useful for tracking current state values like memory usage, CPU load, active connections, queue sizes, or any value that represents a current level.
Signature
interface Gauge<in Input extends number | bigint> extends Metric<Input, GaugeState<Input>> {}Example
(Using gauge metrics)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create different types of gauges const memoryGauge: Metric.Gauge<number> = Metric.gauge("memory_usage_mb", { description: "Current memory usage in megabytes" })
const diskSpaceGauge: Metric.Gauge<bigint> = Metric.gauge("disk_free_bytes", { description: "Available disk space in bytes", bigint: true, attributes: { mount: "/var" } })
// Set gauge values (absolute values) yield* Metric.update(memoryGauge, 512) // Set to 512 MB yield* Metric.update(memoryGauge, 640) // Set to 640 MB (replaces 512) yield* Metric.update(diskSpaceGauge, 5000000000n) // Set to ~5GB free
// Modify gauge values (relative changes) yield* Metric.modify(memoryGauge, 128) // Add 128 MB (total: 768) yield* Metric.modify(memoryGauge, -64) // Subtract 64 MB (total: 704)
// Read gauge state const memoryState: Metric.GaugeState<number> = yield* Metric.value( memoryGauge ) const diskState: Metric.GaugeState<bigint> = yield* Metric.value( diskSpaceGauge ) // Gauge state contains: // - value: current instantaneous value
return { memory: { currentValue: memoryState.value }, // 704 disk: { currentValue: diskState.value } // 5000000000n }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const values = [result.memory.currentValue, result.disk.currentValue] // => [704, 5000000000n]GaugeState interface
State interface for Gauge metrics containing the current instantaneous value.
Signature
interface GaugeState<in Input extends number | bigint> { readonly value: Input extends bigint ? bigint : number;}Example
(Reading gauge state)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create different types of gauges const temperatureGauge = Metric.gauge("room_temperature_celsius", { description: "Current room temperature" })
const diskSpaceGauge = Metric.gauge("disk_usage_bytes", { description: "Current disk usage", bigint: true })
const queueSizeGauge = Metric.gauge("queue_size", { description: "Current queue size" })
// Set gauge values (absolute values) yield* Metric.update(temperatureGauge, 22.5) // Set to 22.5°C yield* Metric.update(diskSpaceGauge, 5000000000n) // Set to 5GB usage yield* Metric.update(queueSizeGauge, 10) // Set to 10 items
// Update gauge values (new absolute values) yield* Metric.update(temperatureGauge, 23.1) // Temperature changed yield* Metric.update(queueSizeGauge, 15) // Queue grew
// Read gauge states const tempState: Metric.GaugeState<number> = yield* Metric.value( temperatureGauge ) const diskState: Metric.GaugeState<bigint> = yield* Metric.value( diskSpaceGauge ) const queueState: Metric.GaugeState<number> = yield* Metric.value( queueSizeGauge ) // GaugeState contains: // - value: current instantaneous value (number or bigint based on gauge type)
return { environment: { temperature: tempState.value, // 23.1 temperatureUnit: "°C" }, system: { diskUsage: diskState.value, // 5000000000n diskUsageGB: Number(diskState.value) / 1_000_000_000, // 5 queueSize: queueState.value // 15 } }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const values = [result.environment.temperature, result.system.diskUsage, result.system.queueSize]values // => [23.1, 5000000000n, 15]A Histogram metric that records observations in configurable buckets to analyze value distributions.
When to use
Use when histograms are ideal for measuring request durations, response sizes, and other continuous values where you need to understand the distribution of values rather than just aggregates.
Signature
interface Histogram<Input> extends Metric<Input, HistogramState> {}Example
(Using histogram metrics)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create histograms with different boundary strategies const responseTimeHistogram: Metric.Histogram<number> = Metric.histogram( "http_response_time_ms", { description: "HTTP response time distribution in milliseconds", boundaries: Metric.linearBoundaries({ start: 0, width: 50, count: 20 }) // 50, 100, ..., 900, Infinity } )
const fileSizeHistogram: Metric.Histogram<number> = Metric.histogram( "file_size_bytes", { description: "File size distribution in bytes", boundaries: Metric.exponentialBoundaries({ start: 1, factor: 2, count: 10 }) // 1, 2, 4, 8, ..., 512 } )
// Record observations (values get placed into appropriate buckets) yield* Metric.update(responseTimeHistogram, 125) // Goes into 100-150ms bucket yield* Metric.update(responseTimeHistogram, 75) // Goes into 50-100ms bucket yield* Metric.update(responseTimeHistogram, 200) // Goes into 150-200ms bucket yield* Metric.update(responseTimeHistogram, 45) // Goes into 0-50ms bucket
yield* Metric.update(fileSizeHistogram, 3) // Goes into 2-4 bytes bucket yield* Metric.update(fileSizeHistogram, 15) // Goes into 8-16 bytes bucket yield* Metric.update(fileSizeHistogram, 100) // Goes into 64-128 bytes bucket
// Read histogram state const responseTimeState: Metric.HistogramState = yield* Metric.value( responseTimeHistogram ) const fileSizeState: Metric.HistogramState = yield* Metric.value( fileSizeHistogram ) // Histogram state contains: // - buckets: Array of [boundary, cumulativeCount] pairs // - count: total number of observations // - min: smallest observed value // - max: largest observed value // - sum: sum of all observed values
return { responseTime: { totalRequests: responseTimeState.count, // 4 fastestRequest: responseTimeState.min, // 45 slowestRequest: responseTimeState.max, // 200 totalTime: responseTimeState.sum, // 445 averageTime: responseTimeState.sum / responseTimeState.count // 111.25 }, fileSize: { totalFiles: fileSizeState.count, // 3 smallestFile: fileSizeState.min, // 3 largestFile: fileSizeState.max, // 100 totalBytes: fileSizeState.sum // 118 } }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const values = [result.responseTime.totalRequests, result.responseTime.totalTime, result.fileSize.totalBytes]values // => [4, 445, 118]HistogramState interface
State interface for Histogram metrics containing bucket distributions and aggregate statistics.
Signature
interface HistogramState { readonly buckets: readonly Array<[number, number]>; readonly count: number; readonly max: number; readonly min: number; readonly sum: number;}Example
(Reading histogram state)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create histogram with linear boundaries const responseTimeHistogram = Metric.histogram("api_response_time_ms", { description: "API response time distribution", boundaries: Metric.linearBoundaries({ start: 0, width: 100, count: 10 }) // 100, 200, ..., 800, Infinity })
// Record observations yield* Metric.update(responseTimeHistogram, 50) // Fast response yield* Metric.update(responseTimeHistogram, 150) // Average response yield* Metric.update(responseTimeHistogram, 750) // Slow response yield* Metric.update(responseTimeHistogram, 250) // Average response yield* Metric.update(responseTimeHistogram, 95) // Fast response
// Read histogram state const state: Metric.HistogramState = yield* Metric.value( responseTimeHistogram )
// HistogramState contains: // - buckets: Array of [boundary, cumulativeCount] pairs showing distribution // - count: total number of observations // - min: smallest observed value // - max: largest observed value // - sum: sum of all observed values
// Analyze bucket distribution const analyzeBuckets = (buckets: ReadonlyArray<[number, number]>) => { const analysis: Array< { range: string; count: number; percentage: number } > = [] let previousCount = 0 const totalCount = buckets[buckets.length - 1]?.[1] ?? 0
for (let i = 0; i < buckets.length; i++) { const [boundary, cumulativeCount] = buckets[i] const bucketCount = cumulativeCount - previousCount const percentage = totalCount > 0 ? (bucketCount / totalCount) * 100 : 0 const prevBoundary = i === 0 ? 0 : buckets[i - 1][0]
analysis.push({ range: `${prevBoundary}-${boundary}ms`, count: bucketCount, percentage: Math.round(percentage * 10) / 10 }) previousCount = cumulativeCount } return analysis }
const bucketAnalysis = analyzeBuckets(state.buckets) return { responseTime: { totalRequests: state.count, // 5 fastestResponse: state.min, // 50 slowestResponse: state.max, // 750 averageResponse: state.sum / state.count, // 268 totalTime: state.sum, // 1340 distribution: bucketAnalysis // Example distribution: // [{ range: "0-100ms", count: 2, percentage: 40.0 }, // { range: "100-200ms", count: 1, percentage: 20.0 }, // { range: "200-300ms", count: 1, percentage: 20.0 }, // { range: "700-800ms", count: 1, percentage: 20.0 }] } }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const stats = result.responseTimeconst values = [stats.totalRequests, stats.fastestResponse, stats.slowestResponse, stats.totalTime]values // => [5, 50, 750, 1295]A Metric<Input, State> represents a concurrent metric which accepts update
values of type Input and are aggregated to a value of type State.
Details
For example, a counter metric would have type Metric<number, number>,
representing the fact that the metric can be updated with numbers (the amount
to increment or decrement the counter by), and the state of the counter is a
number.
There are five primitive metric types supported by Effect:
- Counters
- Frequencies
- Gauges
- Histograms
- Summaries
Signature
interface Metric<in Input, out State> extends Pipeable { readonly "~effect/observability/Metric": "~effect/observability/Metric"; readonly attributes: Readonly<Record<string, string>> | undefined; readonly description: string | undefined; readonly id: string; Input: Contravariant<Input>; readonly modifyUnsafe: (input: Input, context: Context<never>) => void; State: Covariant<State>; readonly type: Type; readonly updateUnsafe: (input: Input, context: Context<never>) => void; readonly valueUnsafe: (context: Context<never>) => State;}Example
(Using multiple metric types)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create different types of metrics const requestCounter: Metric.Counter<number> = Metric.counter("requests", { description: "Total requests processed" })
const memoryGauge: Metric.Gauge<number> = Metric.gauge("memory_usage", { description: "Current memory usage in MB" })
const statusFrequency: Metric.Frequency = Metric.frequency("status_codes", { description: "HTTP status code frequency" })
// All metrics share the same interface for updates and reads yield* Metric.update(requestCounter, 1) yield* Metric.update(memoryGauge, 128) yield* Metric.update(statusFrequency, "200")
// All metrics can be read with Metric.value const counterState = yield* Metric.value(requestCounter) const gaugeState = yield* Metric.value(memoryGauge) const frequencyState = yield* Metric.value(statusFrequency)
// Metrics have common properties accessible through the interface: // - id: unique identifier // - type: metric type ("Counter", "Gauge", "Frequency", etc.) // - description: optional human-readable description // - attributes: optional key-value attributes for tagging
return { counter: { id: requestCounter.id, type: requestCounter.type, state: counterState }, gauge: { id: memoryGauge.id, type: memoryGauge.type, state: gaugeState }, frequency: { id: statusFrequency.id, type: statusFrequency.type, state: frequencyState } }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const values = [ result.counter.state.count, result.gauge.state.value, result.frequency.state.occurrences.get("200")] // => [1, 128, 1]A Summary metric that calculates quantiles over a sliding time window of observations.
When to use
Use when summaries provide statistical insights into value distributions by tracking specific quantiles (percentiles) such as median (50th), 95th percentile, 99th percentile, etc. They're ideal for understanding performance characteristics like response time distributions.
Signature
interface Summary<Input> extends Metric<Input, SummaryState> {}Example
(Using summary metrics)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create summaries with different quantile configurations const responseTimeSummary: Metric.Summary<number> = Metric.summary( "api_response_time_ms", { description: "API response time distribution in milliseconds", maxAge: "5 minutes", // Keep observations for 5 minutes maxSize: 1000, // Keep up to 1000 observations quantiles: [0.5, 0.95, 0.99] // Track median, 95th, and 99th percentiles } )
const requestSizeSummary: Metric.Summary<number> = Metric.summary( "request_size_bytes", { description: "Request payload size distribution", maxAge: "10 minutes", maxSize: 500, quantiles: [0.25, 0.5, 0.75, 0.9] // Track quartiles and 90th percentile } )
// Record observations (values are stored in time-based sliding window) yield* Metric.update(responseTimeSummary, 120) // Fast response yield* Metric.update(responseTimeSummary, 250) // Average response yield* Metric.update(responseTimeSummary, 45) // Very fast response yield* Metric.update(responseTimeSummary, 890) // Slow response yield* Metric.update(responseTimeSummary, 156) // Average response
yield* Metric.update(requestSizeSummary, 1024) // 1KB request yield* Metric.update(requestSizeSummary, 512) // 512B request yield* Metric.update(requestSizeSummary, 2048) // 2KB request
// Read summary state const responseTimeState: Metric.SummaryState = yield* Metric.value( responseTimeSummary ) const requestSizeState: Metric.SummaryState = yield* Metric.value( requestSizeSummary )
// Summary state contains: // - quantiles: Array of [quantile, optionalValue] pairs // - count: total number of observations in window // - min: smallest observed value in window // - max: largest observed value in window // - sum: sum of all observed values in window
// Extract quantile values safely const getQuantileValue = ( quantiles: ReadonlyArray<readonly [number, number | undefined]>, q: number ) => quantiles.find(([quantile]) => quantile === q)?.[1]
const median = getQuantileValue(responseTimeState.quantiles, 0.5) const p95 = getQuantileValue(responseTimeState.quantiles, 0.95) const p99 = getQuantileValue(responseTimeState.quantiles, 0.99) return { responseTime: { totalRequests: responseTimeState.count, // 5 fastestResponse: responseTimeState.min, // 45 slowestResponse: responseTimeState.max, // 890 totalTime: responseTimeState.sum, // 1461 averageTime: responseTimeState.sum / responseTimeState.count, // 292.2 medianTime: median ?? null, // ~156 p95Time: p95 ?? null, // ~890 p99Time: p99 ?? null // ~890 }, requestSize: { totalRequests: requestSizeState.count, // 3 averageSize: requestSizeState.sum / requestSizeState.count // ~1194.7 } }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const counts = [result.responseTime.totalRequests, result.responseTime.totalTime, result.requestSize.totalRequests]counts // => [5, 1461, 3]SummaryState interface
State interface for Summary metrics containing quantile calculations and aggregate statistics.
Signature
interface SummaryState { readonly count: number; readonly max: number; readonly min: number; readonly quantiles: readonly Array<readonly [number, number | undefined]>; readonly sum: number;}Example
(Reading summary state)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create summary with specific quantiles const responseTimeSummary = Metric.summary("api_response_latency", { description: "API response time distribution with quantiles", maxAge: "5 minutes", maxSize: 1000, quantiles: [0.5, 0.95, 0.99] // Track median, 95th, and 99th percentiles })
// Record observations over time yield* Metric.update(responseTimeSummary, 120) // Fast response yield* Metric.update(responseTimeSummary, 250) // Average response yield* Metric.update(responseTimeSummary, 45) // Very fast response yield* Metric.update(responseTimeSummary, 890) // Slow response yield* Metric.update(responseTimeSummary, 156) // Average response yield* Metric.update(responseTimeSummary, 78) // Fast response yield* Metric.update(responseTimeSummary, 340) // Slower response
// Read summary state const state: Metric.SummaryState = yield* Metric.value(responseTimeSummary)
// SummaryState contains: // - quantiles: Array of [quantile, optionalValue] pairs showing percentile values // - count: total number of observations in current window // - min: smallest observed value in window // - max: largest observed value in window // - sum: sum of all observed values in window
// Extract quantile information safely const extractQuantiles = ( quantiles: ReadonlyArray<readonly [number, number | undefined]> ) => { const result: Record<string, number | null> = {} for (const [quantile, valueOption] of quantiles) { const percentile = Math.round(quantile * 100) result[`p${percentile}`] = valueOption ?? null } return result }
const quantileValues = extractQuantiles(state.quantiles) return { latencyAnalysis: { totalRequests: state.count, // 7 fastestResponse: state.min, // 45 slowestResponse: state.max, // 890 averageResponse: state.sum / state.count, // ~268.4 totalLatency: state.sum, // 1879 percentiles: quantileValues, // Example percentiles: // { p50: 156, p95: 890, p99: 890 } performance: { fast: quantileValues.p50 !== null && quantileValues.p50 < 200 ? "Good" : "Needs improvement", reliability: quantileValues.p95 !== null && quantileValues.p95 < 500 ? "Reliable" : "Concerning" } } }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const analysis = result.latencyAnalysisconst values = [analysis.totalRequests, analysis.fastestResponse, analysis.slowestResponse, analysis.totalLatency]values // => [7, 45, 890, 1879]Mutations
Modifies the metric with the specified input.
Details
The behavior of modify depends on the metric type. Counters add the input
value to the current count, gauges add the input value to the current gauge
value, frequencies increment the occurrence count for the input string,
histograms record the input value in the appropriate bucket, and summaries
record the input observation.
Signature
declare const modify: { <Input>(input: Input): <State>(self: Metric<Input, State>) => Effect<void>; <Input, State>(self: Metric<Input, State>, input: Input): Effect<void>;}Example
(Modifying metric values)
import { Effect, Metric } from "effect"
const temperatureGauge = Metric.gauge("temperature")const requestCounter = Metric.counter("requests")
const program = Effect.gen(function*() { // Set initial temperature yield* Metric.update(temperatureGauge, 20)
// Modify by adding/subtracting values yield* Metric.modify(temperatureGauge, 5) // Now 25 yield* Metric.modify(temperatureGauge, -3) // Now 22
// For counters, modify increments by the specified amount yield* Metric.modify(requestCounter, 10) // Add 10 to counter yield* Metric.modify(requestCounter, 5) // Add 5 more (total: 15)
const temp = yield* Metric.value(temperatureGauge) const requests = yield* Metric.value(requestCounter) return [temp.value, requests.count] as const})
await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) // => [22, 15]Updates the metric with the specified input.
Details
The behavior of update depends on the metric type. Counters add the input
value to the current count, gauges replace the current value with the input
value, frequencies increment the occurrence count for the input string,
histograms record the input value in the appropriate bucket, and summaries
record the input value as a new observation.
Signature
declare const update: { <Input>(input: Input): <State>(self: Metric<Input, State>) => Effect<void>; <Input, State>(self: Metric<Input, State>, input: Input): Effect<void>;}Example
(Updating metric values)
import { Effect, Metric } from "effect"
const cpuUsage = Metric.gauge("cpu_usage_percent")const httpStatus = Metric.frequency("http_status_codes")const responseTime = Metric.histogram("response_time_ms", { boundaries: [100, 500, 1000, 2000]})
const program = Effect.gen(function*() { // Update gauge to specific values yield* Metric.update(cpuUsage, 45.2) yield* Metric.update(cpuUsage, 67.8) // Replaces previous value
// Track HTTP status code occurrences yield* Metric.update(httpStatus, "200") yield* Metric.update(httpStatus, "404") yield* Metric.update(httpStatus, "200") // Increments 200 count
// Record response times yield* Metric.update(responseTime, 250) yield* Metric.update(responseTime, 750) yield* Metric.update(responseTime, 1500)
// Check current states const cpu = yield* Metric.value(cpuUsage) const statuses = yield* Metric.value(httpStatus) const times = yield* Metric.value(responseTime) return [cpu.value, statuses.occurrences.get("200"), times.count] as const})
await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) // => [67.8, 2, 3]Other
The Metric namespace provides a comprehensive system for collecting, aggregating, and observing
application metrics in Effect applications.
Example
(Collecting application metrics)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create different types of metrics const requestCounter = Metric.counter("http_requests_total") const responseTimeHistogram = Metric.histogram("http_response_time", { boundaries: Metric.linearBoundaries({ start: 0, width: 10, count: 10 }) }) const activeConnectionsGauge = Metric.gauge("active_connections") const statusFrequency = Metric.frequency("http_status_codes")
// Update metrics yield* Metric.update(requestCounter, 1) yield* Metric.update(responseTimeHistogram, 45.2) yield* Metric.update(activeConnectionsGauge, 12) yield* Metric.update(statusFrequency, "200")
// Get metric values const counterValue = yield* Metric.value(requestCounter) const histogramValue = yield* Metric.value(responseTimeHistogram) const gaugeValue = yield* Metric.value(activeConnectionsGauge) const frequencyValue = yield* Metric.value(statusFrequency)
return { counter: counterValue, histogram: histogramValue, gauge: gaugeValue, frequency: frequencyValue }})
const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map()))const values = [result.counter.count, result.gauge.value] // => [1, 12]Providing Services
disableRuntimeMetrics
Disables automatic collection of fiber runtime metrics for the provided Effect.
When to use
Use when you need to disable runtime metrics for a specific effect while keeping them enabled elsewhere.
Signature
declare const disableRuntimeMetrics: <A, E, R>(self: Effect<A, E, R>) => Effect<A, E, R>Example
(Disabling runtime metrics for an effect)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { const service = yield* Metric.FiberRuntimeMetrics return service === undefined})
await Effect.runPromise(Metric.disableRuntimeMetrics(program)) // => trueenableRuntimeMetrics
Enables automatic collection of fiber runtime metrics for the provided Effect.
Details
When enabled, automatically tracks fiber lifecycle metrics including active fibers, started fibers, successful completions, and failures. These metrics provide valuable insights into the concurrency patterns and health of your Effect application.
Signature
declare const enableRuntimeMetrics: <A, E, R>(self: Effect<A, E, R>) => Effect<A, E, R>Example
(Enabling runtime metrics for an effect)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { const service = yield* Metric.FiberRuntimeMetrics return service === Metric.FiberRuntimeMetricsImpl})
await Effect.runPromise(Metric.enableRuntimeMetrics(program)) // => trueServices
CurrentMetricAttributes
Context reference for metric attributes applied from the current Effect context.
When to use
Use to provide default attributes that should be merged into metric updates and reads in a scoped part of a program.
Details
The default value is an empty attribute set. Metric reads and updates merge these contextual attributes with the metric's own attributes to select the metric series being accessed.
Signature
declare const CurrentMetricAttributes: Reference<Readonly<Record<string, string>>>Example
(Providing current metric attributes)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Access current metric attributes yield* Metric.CurrentMetricAttributes
// Set new attributes context const newAttributes = { service: "api", version: "1.0" } const result = yield* Effect.provideService( Effect.gen(function*() { const updatedAttributes = yield* Metric.CurrentMetricAttributes return updatedAttributes }), Metric.CurrentMetricAttributes, newAttributes )
return result})
const attributes = await Effect.runPromise(program)const actual = attributes // => { service: "api", version: "1.0" }FiberRuntimeMetrics
Context reference for the optional service that records fiber runtime metrics.
When to use
Use to provide or inspect the service that receives fiber start and end notifications for automatic runtime metrics.
Details
When provided, the runtime can notify the service about child-fiber start and
end events. When the reference is undefined, automatic fiber runtime metric
collection is disabled.
Signature
declare const FiberRuntimeMetrics: Reference<FiberRuntimeMetricsService | undefined>Example
(Accessing the fiber runtime metrics service)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { const metricsService = yield* Metric.FiberRuntimeMetrics return metricsService === Metric.FiberRuntimeMetricsImpl})
const result = await Effect.runPromise( Effect.provideService(program, Metric.FiberRuntimeMetrics, Metric.FiberRuntimeMetricsImpl))const isDefault = result // => trueFiberRuntimeMetricsImpl
Default implementation of the fiber runtime metrics service.
Signature
declare const FiberRuntimeMetricsImpl: FiberRuntimeMetricsServiceExample
(Accessing the default fiber metrics implementation)
import { Metric } from "effect"
[ typeof Metric.FiberRuntimeMetricsImpl.recordFiberStart, typeof Metric.FiberRuntimeMetricsImpl.recordFiberEnd] // => ["function", "function"]FiberRuntimeMetricsService interface
Interface for the fiber runtime metrics service that tracks fiber lifecycle events.
Signature
interface FiberRuntimeMetricsService { readonly recordFiberEnd: (context: Context<never>, exit: Exit<unknown, unknown>) => void; readonly recordFiberStart: (context: Context<never>) => void;}Example
(Providing a custom fiber metrics service)
import { Context, Exit, Metric } from "effect"
const events: Array<string> = []const customMetricsService: Metric.FiberRuntimeMetricsService = { recordFiberStart: () => { events.push("start") }, recordFiberEnd: (_context, exit) => { events.push(Exit.isSuccess(exit) ? "success" : "failure") }}
customMetricsService.recordFiberStart(Context.empty())customMetricsService.recordFiberEnd(Context.empty(), Exit.succeed("ok"))events // => ["start", "success"]MetricRegistry
Context reference for the metric registry in the current context.
When to use
Use when you need a custom metric registry for an isolated program or test instead of the default registry.
Details
By default, the reference creates an empty Map the first time it is
resolved. Metrics register their metadata and hooks lazily in this map when
they are read or updated.
Gotchas
Because Context.Reference caches default values, the default Map is
shared by contexts that do not provide an override. Provide MetricRegistry
with a fresh Map when isolation matters.
See
- snapshot for reading all registered metrics from the current
Effectcontext - snapshotUnsafe for reading all registered metrics from an explicit
Context
Signature
declare const MetricRegistry: Reference<Map<string, Metadata<any, any>>>Snapshotting
Captures a snapshot of all registered metrics in the current context.
Details
Returns an array of metric snapshots, each containing the metric's metadata (name, description, type) and current state (values, counts, etc.).
Signature
declare const snapshot: Effect<ReadonlyArray<Metric.Snapshot>>Example
(Capturing metric snapshots)
import { Effect, Metric } from "effect"
const program = Effect.gen(function*() { // Create and update some metrics const requestCounter = Metric.counter("http_requests", { description: "Total HTTP requests" }) const responseTime = Metric.histogram("response_time_ms", { description: "Response time in milliseconds", boundaries: Metric.linearBoundaries({ start: 0, width: 100, count: 5 }) })
// Update the metrics with some values yield* Metric.update(requestCounter, 1) yield* Metric.update(requestCounter, 1) yield* Metric.update(responseTime, 150) yield* Metric.update(responseTime, 75)
// Take a snapshot of all metrics const snapshots = yield* Metric.snapshot
return snapshots})
const snapshots = await Effect.runPromise( Effect.provideService(program, Metric.MetricRegistry, new Map()))const ids = snapshots.map((snapshot) => snapshot.id).sort() // => ["http_requests", "response_time_ms"]snapshotUnsafe
Captures a snapshot of all registered metrics synchronously using the provided service context.
When to use
Use to read metric snapshots from an explicit Context in low-level
integrations, exporters, or debugging tools that already have the context.
Details
This is the "unsafe" version that bypasses Effect's safety guarantees and requires
manual handling of the services context. Use the safe snapshot function for normal
application code.
Signature
declare function snapshotUnsafe(context: Context<never>): readonly Array<Snapshot>Example
(Capturing snapshots from a context)
import { Effect, Metric } from "effect"
const requestCounter = Metric.counter("http_requests")const program = Effect.gen(function*() { yield* Metric.update(requestCounter, 1) const context = yield* Effect.context() return Metric.snapshotUnsafe(context).map((snapshot) => snapshot.id)})
await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) // => ["http_requests"]