What is express-prom-bundle?
The express-prom-bundle package is a middleware for Express.js that integrates Prometheus metrics collection and reporting. It allows you to easily monitor the performance and health of your Express applications by exposing various metrics that Prometheus can scrape.
What are express-prom-bundle's main functionalities?
Basic Setup
This code demonstrates the basic setup of express-prom-bundle in an Express application. It initializes the middleware and includes it in the app, which will start collecting and exposing metrics.
const express = require('express');
const promBundle = require('express-prom-bundle');
const app = express();
const metricsMiddleware = promBundle({ includeMethod: true });
app.use(metricsMiddleware);
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Custom Metrics
This code demonstrates how to create and use custom metrics with express-prom-bundle. It shows how to define a custom counter and increment it in a route handler.
const express = require('express');
const promBundle = require('express-prom-bundle');
const promClient = require('prom-client');
const app = express();
const metricsMiddleware = promBundle({ includeMethod: true });
app.use(metricsMiddleware);
const customCounter = new promClient.Counter({
name: 'custom_counter',
help: 'Example of a custom counter',
});
app.get('/increment', (req, res) => {
customCounter.inc();
res.send('Counter incremented');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Custom Metrics Endpoint
This code demonstrates how to customize the endpoint where Prometheus metrics are exposed. By setting the `metricsPath` option, you can change the default `/metrics` endpoint to a custom path.
const express = require('express');
const promBundle = require('express-prom-bundle');
const app = express();
const metricsMiddleware = promBundle({ includeMethod: true, metricsPath: '/custom-metrics' });
app.use(metricsMiddleware);
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Other packages similar to express-prom-bundle
prom-client
prom-client is a Prometheus client for Node.js that allows you to create and expose custom metrics. Unlike express-prom-bundle, it does not provide middleware for Express, so you need to manually integrate it into your application.
express-status-monitor
express-status-monitor is a simple, self-hosted module based on Socket.io and Chart.js to report realtime server metrics for Express-based applications. It provides a web-based dashboard for monitoring but does not integrate with Prometheus.
express prometheus bundle
Express middleware with popular prometheus metrics in one bundle. It's also compatible with koa v1 and v2 (see below).
Internally it uses prom-client. See: https://github.com/siimon/prom-client
Included metrics:
up
: normally is just 1http_request_duration_seconds
: http latency histogram labeled with status_code
, method
and path
Install
npm install express-prom-bundle
Sample Usage
const promBundle = require("express-prom-bundle");
const app = require("express")();
const metricsMiddleware = promBundle({includeMethod: true});
app.use(metricsMiddleware);
app.use();
app.listen(3000);
ALERT!
The order in which the routes are registered is important, since
only the routes registered after the express-prom-bundle will be measured
You can use this to your advantage to bypass some of the routes.
See the example below.
Usage with Node Cluster
if (cluster.isMaster) {
const numCPUs = Math.max(2, os.cpus().length);
const workers: cluster.Worker[] = [];
for (let i=1; i < numCPUs; i++) {
const worker = forkWorker();
workers.push(worker);
}
const metricsApp = express();
metricsApp.use('/cluster_metrics', promBundle.clusterMetrics());
metricsApp.listen(9999);
console.log('metrics listening on 9999');
} else {
const app = express();
app.use(promBundle({includeMethod: true});
app.use('/api', require('./api'));
app.listen(3000);
}
The code the master process runs will expose an API with a single endpoint /cluster_metrics
which returns an aggregate of all metrics from all the workers.
Options
Which labels to include in http_request_duration_seconds
metric:
- includeStatusCode: HTTP status code (200, 400, 404 etc.), default: true
- includeMethod: HTTP method (GET, PUT, ...), default: false
- includePath: URL path (see importent details below), default: false
- customLabels: an object containing extra labels, e.g.
{project_name: 'hello_world'}
.
Most useful together with transformLabels callback, otherwise it's better to use native Prometheus relabeling.
Extra transformation callbacks:
- normalizePath:
function(req)
or Array
- if function is provided, then it should generate path value from express
req
- if array is provided, then it should be an array of tuples
[regex, replacement]
. The regex
can be a string and is automatically converted into JS regex. - ... see more details in the section below
- urlValueParser: options passed when instantiating url-value-parser.
This is the easiest way to customize which parts of the URL should be replaced with "#val".
See the docs of url-value-parser module for details.
- formatStatusCode:
function(res)
producing final status code from express res
object, e.g. you can combine 200
, 201
and 204
to just 2xx
. - transformLabels:
function(labels, req, res)
transforms the labels object, e.g. setting dynamic values to customLabels
Other options:
- buckets: buckets used for
http_request_duration_seconds
histogram - autoregister: if
/metrics
endpoint should be registered. (Default: true) - promClient: options for promClient startup, e.g. collectDefaultMetrics. This option was added
to keep
express-prom-bundle
runnable using confit (e.g. with kraken.js) without writing any JS code,
see advanced example
More details on includePath option
Let's say you want to have latency statistics by URL path,
e.g. separate metrics for /my-app/user/
, /products/by-category
etc.
Just taking req.path
as a label value won't work as IDs are often part of the URL,
like /user/12352/profile
. So what we actually need is a path template.
The module tries to figure out what parts of the path are values or IDs,
and what is an actual path. The example mentioned before would be
normalized to /user/#val/profile
and that will become the value for the label.
These conversions are handled by normalizePath
function.
You can extend this magical behavior by providing
additional RegExp rules to be performed,
or override normalizePath
with your own function.
Example 1 (add custom RegExp):
app.use(promBundle({
normalizePath: [
['^/customer/.*', '/customer/#name'],
['^.*/order-list', '/#name/order-list']
],
urlValueParser: {
minHexLength: 5,
extraMasks: [
'ORD[0-9]{5,}'
]
}
}));
Example 2 (override normalizePath function):
app.use(promBundle());
const originalNormalize = promBundle.normalizePath;
promBundle.normalizePath = (req, opts) => {
const path = originalNormalize(req, opts);
return (path.match(/^\/docs/) && !path.match(/^\/login/)) ? '/docs/*' : path;
};
For more details:
express example
setup std. metrics but exclude up
-metric:
const express = require("express");
const app = express();
const promBundle = require("express-prom-bundle");
app.get("/status", (req, res) => res.send("i am healthy"));
app.use("/((?!foo))*", promBundle({includePath: true}));
app.get("/foo", (req, res) => res.send("bar"));
app.get("/hello", (req, res) => res.send("ok"));
app.listen(3000);
See an advanced example on github
koa v2 example
const promBundle = require("express-prom-bundle");
const Koa = require("koa");
const c2k = require("koa-connect");
const metricsMiddleware = promBundle({ });
const app = new Koa();
app.use(c2k(metricsMiddleware));
app.use();
app.listen(3000);
using with cluster
You'll need to use an additional clusterMetrics() middleware.
In the example below the master process will expose an API with a single endpoint /metrics
which returns an aggregate of all metrics from all the workers.
const cluster = require('cluster');
const promBundle = require('./src/index');
const numCPUs = Math.max(2, require('os').cpus().length);
const express = require('express');
if (cluster.isMaster) {
for (let i = 1; i < numCPUs; i++) {
cluster.fork();
}
const metricsApp = express();
metricsApp.use('/metrics', promBundle.clusterMetrics());
metricsApp.listen(9999);
console.log('cluster metrics listening on 9999');
console.log('call localhost:9999/metrics for aggregated metrics');
} else {
const app = express();
app.use(promBundle({
autoregister: false,
includeMethod: true
}));
app.use((req, res) => res.send(`hello from pid ${process.pid}\n`));
app.listen(3000);
console.log(`worker ${process.pid} listening on 3000`);
}
using with kraken.js
Here is meddleware config sample, which can be used in a standard kraken.js application.
In this case the stats for URI paths and HTTP methods are collected separately,
while replacing all HEX values starting from 5 characters and all emails in the path as #val.
{
"middleware": {
"expressPromBundle": {
"route": "/((?!status|favicon.ico|robots.txt))*",
"priority": 0,
"module": {
"name": "express-prom-bundle",
"arguments": [
{
"includeMethod": true,
"includePath": true,
"buckets": [0.1, 1, 5],
"promClient": {
"collectDefaultMetrics": {
"timeout": 2000
}
},
"urlValueParser": {
"minHexLength": 5,
"extraMasks": [
"^[0-9]+\\.[0-9]+\\.[0-9]+$"
]
}
}
]
}
}
}
}
License
MIT