Metrics
Track custom metrics in your TypeScript application
Encore provides built-in support for defining custom metrics in your TypeScript applications. Once defined, metrics are automatically collected and displayed in the Encore Cloud Dashboard, and can be exported to third-party observability services.
See the Platform metrics documentation for information about integrations with third-party services like Grafana Cloud and Datadog.
Defining custom metrics
Define custom metrics by importing from encore.dev/metrics and
creating a new metric using the Counter, CounterGroup, Gauge, or GaugeGroup classes.
For example, to count the number of orders processed:
import { Counter } from "encore.dev/metrics";
export const ordersProcessed = new Counter("orders_processed");
function process(order: Order) {
// ...
ordersProcessed.increment();
}
Metric types
Encore currently supports two metric types: counters and gauges.
Counters measure the count of something. A counter's value must always increase, never decrease. (Note that the value gets reset to 0 when the application restarts.) Typical use cases include counting the number of requests, the amount of data processed, and so on.
Gauges measure the current value of something. Unlike counters, a gauge's value can fluctuate up and down. Typical use cases include measuring CPU usage, the number of active instances running of a process, and so on.
Counter example
import { Counter } from "encore.dev/metrics";
export const ordersProcessed = new Counter("orders_processed");
function processOrder() {
ordersProcessed.increment();
// ... process order
}
You can also increment by a specific value instead of 1:
import { Counter } from "encore.dev/metrics";
export const bytesProcessed = new Counter("bytes_processed");
function processData(data: Buffer) {
bytesProcessed.increment(data.length);
// ... process data
}
Gauge example
import { Gauge } from "encore.dev/metrics";
export const cpuUsage = new Gauge("cpu_usage");
function updateMetrics() {
const usage = getCpuUsage(); // returns a number between 0-100
cpuUsage.set(usage);
}
Another example tracking active connections:
import { Gauge } from "encore.dev/metrics";
let activeCount = 0;
export const activeConnections = new Gauge("active_connections");
function onConnect() {
activeCount++;
activeConnections.set(activeCount);
}
function onDisconnect() {
activeCount--;
activeConnections.set(activeCount);
}
Defining labels
Encore's metrics package provides a type-safe way of attaching labels to metrics. To define labels, create an interface type representing the labels and then use CounterGroup or GaugeGroup.
The labels interface defines the structure of labels, where each property corresponds to a single label. Each property must be of type string, number, or boolean.
Please note
When using number type for labels, the value will be converted to an integer using Math.floor().
Counter with labels
import { CounterGroup } from "encore.dev/metrics";
interface Labels {
success: boolean;
}
export const ordersProcessed = new CounterGroup<Labels>("orders_processed");
function process(order: Order) {
let success = false;
try {
// ... process order
success = true;
} catch (err) {
success = false;
}
ordersProcessed.with({ success }).increment();
}
Gauge with labels
import { GaugeGroup } from "encore.dev/metrics";
interface ConnectionLabels {
region: string;
}
export const activeConnectionsByRegion = new GaugeGroup<ConnectionLabels>("active_connections");
const connectionCounts = new Map<string, number>();
function onConnect(region: string) {
const count = (connectionCounts.get(region) || 0) + 1;
connectionCounts.set(region, count);
activeConnectionsByRegion.with({ region }).set(count);
}
function onDisconnect(region: string) {
const count = Math.max(0, (connectionCounts.get(region) || 0) - 1);
connectionCounts.set(region, count);
activeConnectionsByRegion.with({ region }).set(count);
}
Multiple labels
You can define multiple labels for a metric:
import { CounterGroup } from "encore.dev/metrics";
interface JobLabels {
jobType: string;
priority: number;
success: boolean;
}
export const jobsProcessed = new CounterGroup<JobLabels>("jobs_processed");
function processJob(jobType: string, priority: number) {
try {
// ... process job
jobsProcessed.with({ jobType, priority, success: true }).increment();
} catch (err) {
jobsProcessed.with({ jobType, priority, success: false }).increment();
}
}
Metric references
Encore uses static analysis to determine which services are using each metric, and what operations each service is performing.
This means metric objects can't be passed around however you like, as it makes static analysis impossible in many cases. To simplify your workflow, given these restrictions, Encore supports defining a "reference" to a metric that can be passed around any way you want.
To create a reference, call the .ref() method on any metric:
import { Counter } from "encore.dev/metrics";
export const ordersProcessed = new Counter("orders_processed");
// Create a reference that can be passed around
const metricRef = ordersProcessed.ref();
// Pass the reference to other functions
function logMetric(metric: Counter) {
metric.increment();
}
logMetric(metricRef);
This works for all metric types (Counter, CounterGroup, Gauge, and GaugeGroup).
Take care
Each combination of label values creates a unique time series tracked in memory and stored by the monitoring system. Using numerous labels can lead to a combinatorial explosion, causing high cloud expenses and degraded performance.
As a general rule, limit the unique time series to tens or hundreds at most, rather than thousands.