Prometheus Utility Library
Description
A framework-agnostic Prometheus metrics exporter for TypeScript/Node.js built on prom-client. Implements the MetricsExporter interface from @pawells/metrics to provide vendor-neutral metrics collection with support for both pull mode (HTTP /metrics endpoint) and push mode (Prometheus Pushgateway).
Key features:
- Pull-based export — Buffers metric values in memory and flushes them atomically when
/metricsis scraped - Push-mode support — Optional periodic push to a Prometheus Pushgateway with Bearer token or Basic auth; each push request has a 10 s AbortController timeout
- Metric type mapping — Counter, Gauge, Updown Counter, Histogram with automatic bucket configuration, and Summary with server-side quantile configuration
- Node.js integration — Automatically collects process metrics (CPU, memory, garbage collection, event loop)
- Thread-safe buffering — Atomic swaps during flush to prevent data loss under concurrent recording
- Graceful shutdown — Properly disables collectors and cleans up resources
For NestJS applications, use PrometheusModule from @pawells/nestjs-metrics instead of initializing directly — the module manages the exporter lifecycle and registers the HTTP endpoint automatically.
Requirements
- Node.js >=22 (see package.json for exact version)
- TypeScript ~6.0.0 (as a dev dependency in your project)
- @pawells/metrics — the vendor-neutral foundation library (direct dependency, installed automatically)
- prom-client ^15.1.3 (managed by this package)
Installation
yarn add @pawells/prometheus @pawells/metrics
This package is ESM-only ("type": "module"). See exports in package.json for entry points.
Quick Start
Pull Mode (Default)
Initialize the Prometheus manager and fetch metrics from a scrape endpoint:
import { PrometheusManager } from '@pawells/prometheus';
import http from 'http';
// Initialize Prometheus exporter
PrometheusManager.Initialize();
// Create an HTTP server with a /metrics endpoint.
// Bind to 127.0.0.1 (loopback) so only the local Prometheus scraper can reach it.
// Never expose /metrics on 0.0.0.0 without a separate authentication layer.
http.createServer(async (req, res) => {
if (req.url === '/metrics') {
try {
const metrics = await PrometheusManager.GetMetrics();
res.writeHead(200, { 'Content-Type': 'text/plain; version=0.0.4; charset=utf-8' });
res.end(metrics);
}
catch (error) {
res.writeHead(500);
res.end('Failed to generate metrics');
}
}
}).listen(9090, '127.0.0.1');
console.log('Prometheus metrics available at http://127.0.0.1:9090/metrics');
NestJS Integration
For NestJS applications, import PrometheusModule from @pawells/nestjs-metrics:
import { Module } from '@nestjs/common';
import { PrometheusModule } from '@pawells/nestjs-metrics/prometheus';
@Module({
imports: [PrometheusModule],
})
export class AppModule {}
// Endpoint automatically registered at GET /metrics
Recording Metrics
Use Instrumentation from @pawells/metrics to declare and record metrics:
import { Instrumentation } from '@pawells/metrics';
// Register a metric descriptor (must happen before recording)
Instrumentation.RegisterDescriptor({
name: 'requests_total',
type: 'counter',
description: 'Total requests received',
labelNames: ['method', 'route']
});
// Record a value (forwarded to all registered exporters including Prometheus)
Instrumentation.RecordMetric({
descriptorName: 'requests_total',
labels: { method: 'GET', route: '/health' },
value: 1
});
Configuration
Configuration is managed via environment variables with the PROMETHEUS_ prefix. All variables use @pawells/config v3 with optional Zod validation. Secrets (marked SECRET below) are automatically redacted in logs.
| Variable | Type | Default | Description |
|---|---|---|---|
PROMETHEUS_PULL_ENABLED |
boolean | true |
Enable Prometheus pull mode (HTTP /metrics endpoint). When enabled, metrics are returned on-demand. |
PROMETHEUS_PUSH_ENABLED |
boolean | false |
Enable Prometheus push mode (Pushgateway). When enabled, metrics are periodically pushed to the gateway. |
PROMETHEUS_PUSHGATEWAY_URL |
string (URL) | '' |
URL of the Prometheus Pushgateway (e.g., http://localhost:9091). Required if PUSH_ENABLED=true. Must use http or https scheme. |
PROMETHEUS_PUSHGATEWAY_JOB_NAME |
string | os.hostname() |
Job label to use when pushing metrics to the Pushgateway. Defaults to the machine hostname. |
PROMETHEUS_PUSHGATEWAY_INSTANCE |
string | optional | Instance label to use when pushing metrics (defaults to hostname if not set). Useful to distinguish multiple instances of the same job. |
PROMETHEUS_PUSHGATEWAY_INTERVAL_MS |
number | 5000 |
Interval (milliseconds) between pushes to the gateway. Minimum: 100ms. |
PROMETHEUS_PUSHGATEWAY_USERNAME |
string | optional | Username for Basic authentication with the Pushgateway. SECRET — masked in logs. |
PROMETHEUS_PUSHGATEWAY_PASSWORD |
string | optional | Password for Basic authentication with the Pushgateway. SECRET — masked in logs. Both username and password required for Basic auth. |
PROMETHEUS_PUSHGATEWAY_TOKEN |
string | optional | Bearer token for Pushgateway authentication. SECRET — masked in logs. Takes precedence over Basic auth if both are set. |
PROMETHEUS_PUSHGATEWAY_HEADERS |
string (JSON) | '{}' |
Custom HTTP headers to include when pushing metrics. Must be a valid JSON object with string values. Headers are sent with every push request. |
Cross-field validation:
- If
PROMETHEUS_PUSH_ENABLED=true, thenPROMETHEUS_PUSHGATEWAY_URLmust be set, otherwise initialization fails. - If
PUSHGATEWAY_URLis set butPUSH_ENABLED=false, push mode is disabled.
Authorization priority (highest to lowest):
- Custom
Authorizationheader inPUSHGATEWAY_HEADERS - Bearer token (
PUSHGATEWAY_TOKEN) - Basic auth (username + password)
Example: Pull-only mode (default)
PROMETHEUS_PULL_ENABLED=true
PROMETHEUS_PUSH_ENABLED=false
Example: Push-mode with Basic auth
Caution: Basic auth encodes credentials as base64, not encryption. Use
https://forPUSHGATEWAY_URLin production to prevent cleartext credential exposure in transit.
PROMETHEUS_PULL_ENABLED=true
PROMETHEUS_PUSH_ENABLED=true
PROMETHEUS_PUSHGATEWAY_URL=http://pushgateway.local:9091
PROMETHEUS_PUSHGATEWAY_JOB_NAME=myapp
PROMETHEUS_PUSHGATEWAY_INTERVAL_MS=10000
PROMETHEUS_PUSHGATEWAY_USERNAME=admin
PROMETHEUS_PUSHGATEWAY_PASSWORD=secret123
Example: Push-mode with Bearer token
PROMETHEUS_PUSH_ENABLED=true
PROMETHEUS_PUSHGATEWAY_URL=http://pushgateway.local:9091
PROMETHEUS_PUSHGATEWAY_JOB_NAME=myapp
PROMETHEUS_PUSHGATEWAY_TOKEN=my-bearer-token-here
PROMETHEUS_PUSHGATEWAY_HEADERS='{"X-Custom-Header":"value"}'
API Reference
PrometheusManager
Static manager class that owns the Prometheus exporter lifecycle. All methods and properties are static. Called automatically by PrometheusModule.onModuleInit() in NestJS applications.
Static Methods:
Initialize(): void- Initializes the exporter and registers it with
Instrumentation. - If
PUSH_ENABLED=true, starts a periodic push interval. - Idempotent — subsequent calls are no-ops.
- Initializes the exporter and registers it with
async Shutdown(): Promise<void>- Clears the periodic push interval (if active), deregisters the exporter from
Instrumentation, awaits the exporter's ownShutdown(), clears the cached exporter reference, and marks the manager as uninitialized. - Idempotent — subsequent calls are no-ops.
- Clears the periodic push interval (if active), deregisters the exporter from
GetMetrics(): Promise<string>- Flushes all pending metric values and returns them in Prometheus text format (version 0.0.4).
- Throws:
PrometheusError(PROMETHEUS_NOT_INITIALIZED) if the manager has not been initialized,PrometheusError(PROMETHEUS_EXPORTER_MISSING) if initialized but the internal exporter reference is unexpectedly missing, orPrometheusError(PROMETHEUS_METRICS_GENERATION_FAILED) if the underlying metrics generation fails. - Returns: Promise resolving to metrics string.
BuildAuthorizationHeaders(headersJson: string, username?: string, password?: string, token?: string): string- Builds authorization headers with proper priority ordering (token > basic auth > custom header).
- Used internally to build the authorization headers for push requests.
- Parameters:
headersJson— Raw headers JSON string from configurationusername— Optional username for Basic authenticationpassword— Optional password for Basic authenticationtoken— Optional bearer token
- Returns: Processed headers JSON string with
Authorizationheader injected (if applicable).
Static Properties:
Initialized: boolean(getter)- Returns
trueifInitialize()has been called andShutdown()has not been called.
- Returns
PrometheusExporter
Framework-agnostic exporter class that implements MetricsExporter from @pawells/metrics. Implements the buffering and flush model described below.
Help text: descriptor.description is used as the prom-client help string. When descriptor.unit is set it is appended in the form "<description> (unit: <unit>)".
Exemplar handling: value.exemplar is accepted but not emitted. prom-client exemplars require the OpenMetrics content type and use a different data model; the field is silently ignored.
Error contract: OnDescriptorRegistered and OnMetricRecorded throw PrometheusError on real failures rather than logging and dropping. The Instrumentation registry isolates each exporter and aggregates failures into MetricsExporterError.
Buffering model:
- Metric values are buffered in memory as they are recorded (via
OnMetricRecorded) - Pending values are flushed into prom-client instruments when
GetMetrics()is called (atomic swap pattern) - Each metric has a max pending buffer of 1000 values; oldest entries are culled if exceeded
- For updown counters, running totals are maintained across scrapes (persistent state); the 1000-entry buffer cap applies only to the
Pendingmap —GaugeValues(running totals) are capped at 10,000 unique label combinations per metric, with the oldest label combination evicted and a warning logged on overflow
Type mapping:
| Descriptor Type | prom-client Instrument | Behavior |
|---|---|---|
counter |
Counter | Monotonically increasing; values added via inc(labels, value) |
gauge |
Gauge | Point-in-time value; set via set(labels, value) |
updown_counter |
Gauge | Can increase or decrease; running total persisted across scrapes via internal map |
histogram |
Histogram | Distribution with buckets; values added via observe(labels, value) |
summary |
Summary | Server-side quantile distribution; values added via observe(labels, value); percentiles from descriptor.quantiles (prom-client defaults apply when absent) |
Key Methods:
OnDescriptorRegistered(descriptor: TMetricDescriptor): void- Called by
Instrumentationwhen a metric descriptor is registered. - Pre-creates the corresponding prom-client instrument (Counter, Gauge, Histogram, or Summary).
- Initializes an empty pending buffer for the metric.
- Re-registering an already-known descriptor is a no-op.
- Throws
PrometheusErrorif the name is empty, the type is unsupported, or prom-client instrument construction fails.
- Called by
OnMetricRecorded(value: TMetricValue): void- Called by
Instrumentationfor every recorded metric value. - Appends the value to the pending buffer (max 1000 per metric).
- Oldest entries are culled if the buffer is exceeded.
value.exemplar, when present, is accepted but not forwarded to prom-client (see Exemplar handling above).- Throws
PrometheusErrorif the metric's descriptor was not previously registered with this exporter.
- Called by
GetMetrics(): Promise<string>- Flushes all pending values into prom-client instruments.
- Returns metrics in Prometheus text format (version 0.0.4).
- Includes Node.js default metrics and all custom metrics.
Flush(): Promise<void>- No-op. Prometheus is pull-based: buffered values are materialised into prom-client instruments when the
/metricsendpoint is scraped (or pushed byPrometheusManagerwhen a Pushgateway is configured). This method exists to satisfy theMetricsExporter.Flushlifecycle contract and resolves immediately.
- No-op. Prometheus is pull-based: buffered values are materialised into prom-client instruments when the
Shutdown(): Promise<void>- Disables all registered collectors, clears the registry, and releases internal state.
- Called during application shutdown to prevent hanging (especially for event loop histogram).
Properties:
readonly Name = 'prometheus'- Stable identifier used by the
Instrumentationregistry for exporter deduplication and diagnostic tagging.
- Stable identifier used by the
PrometheusError
Error class thrown when a Prometheus exporter operation fails. Extends BaseError from @pawells/typescript-common, aligning with the other exporter packages (@pawells/open-telemetry, @pawells/pyroscope).
export class PrometheusError extends BaseError<IPrometheusErrorMetadata> {
static readonly Code: Readonly<Record<string, string>>; // frozen enum of every code thrown by this package
get code(): string; // getter, not a stored field — delegates to BaseError.Code
constructor(message: string, options?: { code?: string; cause?: Error });
}
Properties:
code— Machine-readable error code (a getter, not a plain field); delegates toBaseError.Code, defaulting toPrometheusError.Code.PROMETHEUS_ERRORwhen no explicit code was suppliedcause— Original error that caused this error, if any (propagated through the nativeErrorcause chain)
Example:
try {
const metrics = await PrometheusManager.GetMetrics();
}
catch (error) {
if (error instanceof PrometheusError) {
console.error(`Prometheus error: ${error.message} (code: ${error.code})`);
}
}
Configuration Schema
Configuration values are accessed at runtime via the exported PrometheusConfig object. Use PrometheusConfig.Get(key) to read validated, environment-variable-backed values:
import { PrometheusConfig } from '@pawells/prometheus';
const url = PrometheusConfig.Get('PUSHGATEWAY_URL');
const enabled = PrometheusConfig.Get('PUSH_ENABLED');
See the Configuration section above for the complete list of variables, types, defaults, and descriptions.
License
MIT — Phillip Aaron Wells. See LICENSE for details.