Metrics
A metrics library that is built on top of Prometheus.
Install the Metrics
flowcore component add library/metrics
Or install the Metrics manually
Install the Prometheus client
bun add prom-client
This library uses the api exceptions, so you need to install it as well, or manually add it to your project.
flowcore component add library/api-exceptions
Create a lib/metrics.ts
file in your project’s lib folder, and add the following code:
import { ApiInternalServerErrorException } from "@components/api-exceptions"import Elysia from "elysia"import client, { type Metric } from "prom-client"
/** * Builder class for creating and configuring Prometheus metrics * * @example * const metricsBuilder = new MetricsBuilder() * .withPrefix('myapp_') * .withoutDefaultMetrics(false) * .build() */export class MetricsBuilder { private register: client.Registry private prefix = "app_" private defaultMetrics = true
constructor() { this.register = new client.Registry() }
/** * Sets the prefix for all metrics * @param prefix - The prefix to add to all metric names * @returns The builder instance for method chaining */ withPrefix(prefix: string) { this.prefix = prefix return this }
/** * Configures whether to collect default metrics * @param defaultMetrics - If false, disables default metrics collection * @returns The builder instance for method chaining */ withDefaultMetrics(newVal: boolean) { this.defaultMetrics = newVal return this }
/** * Builds and returns the metrics registry with the configured settings * @returns An object containing registry methods and properties */ build() { if (this.defaultMetrics) { client.collectDefaultMetrics({ register: this.register, prefix: this.prefix, }) }
return { contentType: this.register.contentType, registerMetrics: this.register.registerMetric.bind(this.register), metrics: this.register.metrics.bind(this.register), getMetric: this.register.getSingleMetric.bind(this.register), } }}
/** * Factory function to create an Elysia plugin with metrics registry * @param defaultMetrics - Whether to collect default metrics * @param prefix - Prefix for metric names * @returns Elysia plugin with configured metrics registry */export const metricsFactory = (defaultMetrics = true, prefix = "app_") => new Elysia().decorate( "registry", new MetricsBuilder().withPrefix(prefix).withDefaultMetrics(defaultMetrics).build(), )
/** * Factory function to create an Elysia plugin with a pre-configured metrics registry * @param metricsBuilder - A built metrics registry instance from MetricsBuilder * @returns Elysia plugin decorated with the provided metrics registry */export const globalMetricsFactory = (metricsBuilder: ReturnType<typeof MetricsBuilder.prototype.build>) => new Elysia().decorate("registry", metricsBuilder)
/** * Creates an Elysia plugin that registers and retrieves a specific metric * @param metric - The Prometheus metric to register * @returns Elysia plugin that provides access to the metric * @throws Error if metrics registry is not found */export const getMetric = <T extends Metric>(metric: T) => new Elysia().derive({ as: "scoped" }, async (context) => { // biome-ignore lint/suspicious/noExplicitAny: Required for context type const registry = (context as any).registry as ReturnType<typeof MetricsBuilder.prototype.build> if (registry) { const metricValue = await metric.get()
const registeredMetric = registry.getMetric(metricValue.name)
if (!registeredMetric) { registry.registerMetrics(metric) }
return { [metricValue.name]: metric, } }
throw new ApiInternalServerErrorException("Metric registry not found") })
/** * Creates an Elysia plugin that provides access to the metrics registry * @returns Elysia plugin with registry access * @throws Error if metrics registry is not found */export const getRegistry = () => new Elysia().derive({ as: "scoped" }, async (context) => { // biome-ignore lint/suspicious/noExplicitAny: Required for context type const registry = (context as any).registry as ReturnType<typeof MetricsBuilder.prototype.build> if (registry) { return { registry, } }
throw new ApiInternalServerErrorException("Metrics registry not found") })
Usage
To use the metrics, create a metrics instance using the metricsFactory
function.
import { metricsFactory } from "@components/metrics"import metricsRoute from "@/server/routes/metrics"import Elysia from "elysia"
const server = new Elysia() .use(metricsFactory(true)) .use(metricsRoute)
Then register a metric using the getMetric
function.
import { getMetric } from "@/lib/metrics"import Elysia from "elysia"import { Counter } from "prom-client"
export default new Elysia({ prefix: "/test" }) .use(getMetric<Counter>(new Counter({ name: "test", help: "test" }))) .get("/", ({ test }) => { test.inc() return "ok" })
Then add the metrics endpoint to your server.
import { getRegistry } from "@/lib/metrics"import Elysia from "elysia"
export default new Elysia({ prefix: "/metrics" }).use(getRegistry()).get("/", async ({ set, registry }) => { set.headers["Content-Type"] = registry.contentType
try { // Collect and return all metrics const metrics = await registry.metrics() return metrics } catch (error) { console.error("Error collecting metrics:", error) set.status = 500 return "Error collecting metrics" }})
to create a metric registry that you can use outside of elysia, use the globalMetricsFactory
function.
import { MetricsBuilder } from "./lib/metrics"
export const metrics = new MetricsBuilder().withPrefix("app_").withoutDefaultMetrics(true).build()
register it in elysia.
import { globalMetricsFactory } from "@components/metrics"import { metrics } from "./metrics"import Elysia from "elysia"
const server = new Elysia().use(globalMetricsFactory(metrics))
then you can use the metrics instance anywhere in your project as well as in the elysia context.
import { metrics } from "./metrics"
const counter = new Counter({ name: "test", help: "test" })metrics.registerMetrics(counter)
counter.inc()