Metrics
Counter / gauge / histogram primitives in Prometheus 0.0.4 text format with OTLP-friendly labels, plus framework auto-instrumentation wired into audit / vault / queue hot paths.
b.metrics.create() returns a registry — call counter(name) / gauge(name) / histogram(name) to register typed metrics, then requestMiddleware() for per-request counter+latency, and expositionHandler() for the /metrics scrape route. Every metric carries a per-instance labelCardinalityCap (default 10,000) — when the next label combination would push past the cap the increment drops and a single warning logs, so a runaway label (request-id, raw URL with query string, per-user dimension) can't OOM the process.
Framework modules call metrics.tap("audit.record", value, labels) at hot paths. Until a registry is active the call is a zero-cost no-op; once create() runs, taps flow into pre-registered counters / gauges (framework_audit_events_total, framework_vault_seal_total, framework_queue_depth, framework_jobs_inflight, framework_errors_total, framework_http_requests_total, framework_http_request_duration_seconds).
Best-practice route labels are the route TEMPLATE (/users/:id), not the actual path — requestMiddleware reads req.routePattern when the matcher set one and falls back to the query-stripped URL otherwise.
b.metrics.tap(name, value, labels) #
Framework hot-path tap. Modules call tap("audit.record", 1, { action, outcome }) without importing a registry. Until b.metrics.create() runs the call is a zero-cost no-op; afterwards the active registry routes the tap into pre-registered counters and gauges. Drop-silent on internal throws so a misconfigured metric cannot crash the request that triggered the tap.
// Module-level — no registry yet, no-op:
b.metrics.tap("audit.record", 1, { action: "auth.login", outcome: "success" });
// After registry creation, the same tap call increments
// framework_audit_events_total{action="auth.login", outcome="success"}.
var registry = b.metrics.create({ namespace: "myapp" });
b.metrics.tap("audit.record", 1, { action: "auth.login", outcome: "success" });
b.metrics.create(opts) #
{
namespace: string, // prepended to every metric name
defaultLabels: object, // attached to every sample
labelCardinalityCap: number, // per-metric distinct-label-set cap; default 10000
}
Build a Prometheus-format metrics registry. The returned registry exposes counter / gauge / histogram factories, requestMiddleware() for per-route auto-instrumentation, expositionHandler() for the /metrics scrape route, and exposition() for direct rendering. Activates the framework auto-tap so audit / vault / queue / error events feed pre-registered framework counters.
var m = b.metrics.create({
namespace: "myapp",
defaultLabels: { service: "api", version: "1.2.3" },
});
var requests = m.counter("http_requests_total", {
help: "Total HTTP requests",
labelNames: ["method", "route", "status"],
});
requests.inc({ method: "GET", route: "/users", status: "200" });
var latency = m.histogram("http_request_duration_seconds", {
help: "HTTP request latency",
labelNames: ["method", "route"],
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
});
latency.observe({ method: "GET", route: "/users" }, 0.123);
var depth = m.gauge("queue_depth", { labelNames: ["queueName"] });
depth.set({ queueName: "default" }, 42);
// Wire into an HTTP server.
router.use(m.requestMiddleware());
router.get("/metrics", m.expositionHandler());
b.metrics.snapshot.startWriter(opts) #
{
path: string, // absolute path to write the snapshot
intervalMs: number, // milliseconds between flushes (>=100)
fields: Function, // returns an object — written as JSON
registry: object, // optional `b.metrics.create()` handle — adds a
// structured `metrics` field carrying every
// registered counter / gauge / histogram (incl.
// bucket counts) so sidecar readers compose
// histogram_quantile() against the snapshot
fileMode: number, // POSIX mode (default 0o640 — owner rw, group r)
}
Start a periodic writer that calls opts.fields() every opts.intervalMs and writes the returned object as JSON to opts.path atomically. Returns a stop() function that clears the timer + performs one final flush before resolving.
var registry = b.metrics.create();
var latency = registry.histogram("op_latency_seconds", { buckets: [0.01, 0.1, 1] });
var stop = b.metrics.snapshot.startWriter({
path: "/run/blamejs/metrics.json",
intervalMs: 5000,
registry: registry,
fields: function () { return { uptimeMs: process.uptime() * 1000 }; },
});
// Snapshot file: { writtenAt, fields, metrics: { op_latency_seconds: { type, buckets, observations: [{ labels, counts, sum, count }] } } }
stop();
b.metrics.snapshot.read(path) #
Read + parse a snapshot file written by startWriter. Returns { writtenAt, fields }. Throws MetricsError with code metrics-snapshot/... on missing file, parse failure, or shape mismatch.
var snap = b.metrics.snapshot.read("/run/blamejs/metrics.json");
console.log("uptime:", snap.fields.uptimeMs);
console.log("written at:", snap.writtenAt);
b.metrics.snapshot.render(snap, opts) #
{
format: "text" | "prometheus", // default: "text"
prefix: string, // prometheus-only; default: "blamejs"
fieldTypes: Object, // prometheus-only; per-field type override
// map. Values: "counter" | "gauge".
}
Format a snapshot object for human or machine consumption.
format: "text" — operator-readable lines, one field per row (default) format: "prometheus" — Prometheus 0.0.4 text format
## Type detection (prometheus format only)
Per Prometheus naming convention + OpenMetrics 1.0.0 §6.2, counter metric families MUST carry the _total suffix; every other numeric field renders as a gauge. The renderer auto-detects by suffix:
- field name ends in
_total→# TYPEcounter - everything else →
# TYPEgauge
Operators with metrics that don't fit the convention (e.g. a counter named bytes_sent without the _total suffix, or a gauge that happens to end in _total) opt the right type via opts.fieldTypes:
render(snap, { format: "prometheus", fieldTypes: { bytes_sent: "counter", // override default gauge ratio_total: "gauge", // override default counter }});
Pre-v0.9.47 every field rendered as gauge regardless of name, which broke rate() queries against counter-shaped series. Operators scraping a long-running deployment will see rate(*_total[5m]) queries start returning the right answer once the new types reach the scrape target.
## Labeled registry series
A snapshot written with startWriter's registry option carries the registry's counters / gauges / histograms — label sets and histogram bucket counts — in a structured metrics field. Both formats render them: prometheus emits the same labeled / bucketed sample lines the live exposition() endpoint serves, family names verbatim (NOT prefix-qualified) so dashboards see one series name regardless of scrape source; text lists each labeled sample as a name{label="value"} row. A malformed family, metric / label name, or non-numeric sample in a hand-edited snapshot file is dropped, never rendered.
var snap = b.metrics.snapshot.read("/run/blamejs/metrics.json");
process.stdout.write(b.metrics.snapshot.render(snap));
// or for Prometheus scraping (auto-detects http_requests_total
// as a counter via the _total suffix):
res.setHeader("Content-Type", "text/plain; version=0.0.4");
res.end(b.metrics.snapshot.render(snap, { format: "prometheus", prefix: "myapp" }));
b.metrics.snapshot.shadowRegistry(opts) #
{
namespace: string, // identifier prefix; required
counters: string[], // counter names to mirror
gauges: string[], // gauge names to mirror
info: string[], // info names to mirror
cardinalityCap: number, // default 10000 per metric name
onCardinalityExceeded: "drop" | "audit-only" | "refuse", // default "drop"
}
Build a namespaced shadow metrics registry that mirrors a subset of a primary registry's counters / gauges / info for export to systems needing isolated views (sidecar / per-tenant scrape endpoint / compliance-tagged subset). Cardinality cap closes the [client_golang CVE-2022-21698](https://nvd.nist.gov/vuln/detail/CVE-2022-21698) unbounded-cardinality DoS class. Returns { inc, set, setInfo, snapshot, render, reset }.
var shadow = b.metrics.snapshot.shadowRegistry({
namespace: "tenant_a",
counters: ["requests_total", "errors_total"],
gauges: ["queue_depth"],
});
shadow.inc("requests_total");
shadow.set("queue_depth", 42);
shadow.snapshot();
Last updated 2026-08-08T16:39:49.652Z by seeder.