mirror of
https://github.com/BreizhHardware/express-prom-bundle.git
synced 2026-01-19 00:37:36 +01:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b1aa494cb | ||
|
|
65549a769b | ||
|
|
de83ac09a0 | ||
|
|
52865dfb02 | ||
|
|
5c6ed64a31 | ||
|
|
d8c6492163 | ||
|
|
c92b85ae96 | ||
|
|
48f8b992fd | ||
|
|
61e4343a8c | ||
|
|
7b89690d3b | ||
|
|
40db5cacbd | ||
|
|
43334b923f | ||
|
|
20eb668e36 | ||
|
|
53c4505378 |
72
README.md
72
README.md
@@ -11,7 +11,7 @@ Included metrics:
|
||||
* `up`: normally is just 1
|
||||
* `http_request_duration_seconds`: http latency histogram labeled with `status_code`, `method` and `path`
|
||||
|
||||
**Please not version 2.x is NOT compatible with 1.x**
|
||||
**Please note version 2.x is NOT backwards compatible with 1.x**
|
||||
|
||||
## Install
|
||||
|
||||
@@ -36,7 +36,7 @@ app.listen(3000);
|
||||
|
||||
**ALERT!**
|
||||
|
||||
The order in wich the routes are registered is important, since
|
||||
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.
|
||||
@@ -48,21 +48,42 @@ See the example below.
|
||||
* **includeMethod**: include HTTP method (GET, PUT, ...) as a label to `http_request_duration_seconds`
|
||||
* **includePath**: include URL path as a label (see below)
|
||||
* **normalizePath**: boolean or `function(req)` - path normalization for `includePath` option
|
||||
* **excludeRoutes**: array of strings or regexp specifying which routes should be skipped for `http_request_duration_seconds` metric. It uses `req.path` as subject when checking
|
||||
* **autoregister**: if `/metrics` endpoint should be registered. (Default: **true**)
|
||||
* **whitelist**, **blacklist**: array of strings or regexp specifying which metrics to include/exclude
|
||||
* **excludeRoutes**: (deprecated) array of strings or regexp specifying which routes should be skipped for `http_request_duration_seconds` metric. It uses `req.originalUrl` as subject when checking. You want normally use express or meddleware features instead of this options.
|
||||
|
||||
### More details on includePath option
|
||||
|
||||
The goal is to have separate latency statistics by URL path, e.g. `/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.
|
||||
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.
|
||||
|
||||
You can override this magical behavior and define your own function by providing an optional callback using **normalizePath** option.
|
||||
You can override this magical behavior and define your own function by
|
||||
providing an optional callback using **normalizePath** option.
|
||||
You can also replace the default **normalizePath** function globally.
|
||||
This is handy if the rest of the middleware is done elsewhere
|
||||
e.g. via `kraken.js meddleware`.
|
||||
|
||||
```javascript
|
||||
app.use(promBundle(/* options? */));
|
||||
|
||||
// let's reuse the existing one and just add some
|
||||
// functionality on top
|
||||
const originalNormalize = promBunle.normalizePath;
|
||||
promBunle.normalizePath = (req, opts) => {
|
||||
const path = originalNormalize(req, opts);
|
||||
// count all docs (no matter which file) as a single path
|
||||
return path.match(/^\/docs/) ? '/docs/*' : path;
|
||||
};
|
||||
```
|
||||
|
||||
For more details:
|
||||
* [url-value-parser](https://www.npmjs.com/package/url-value-parser) - magic behind automatic path normalization
|
||||
* [normalizePath.js](https://github.com/jochen-schweizer/express-prom-bundle/blob/master/src/normalizePath.js) - source code for path processing, for you
|
||||
* [normalizePath.js](https://github.com/jochen-schweizer/express-prom-bundle/blob/master/src/normalizePath.js) - source code for path processing
|
||||
|
||||
|
||||
|
||||
@@ -71,23 +92,20 @@ For more details:
|
||||
setup std. metrics but exclude `up`-metric:
|
||||
|
||||
```javascript
|
||||
"use strict";
|
||||
|
||||
const express = require("express");
|
||||
const app = express();
|
||||
const promBundle = require("express-prom-bundle");
|
||||
|
||||
|
||||
// calls to this route will not appear in metrics
|
||||
// because it's applied before promBundle
|
||||
app.get("/status", (req, res) => res.send("i am healthy"));
|
||||
|
||||
app.use(promBundle({
|
||||
includePath: true,
|
||||
excludeRoutes: ["/foo"]
|
||||
}));
|
||||
// register metrics collection for all routes
|
||||
// ... except those starting with /foo
|
||||
app.use("/((?!foo))*", promBundle({includePath: true}));
|
||||
|
||||
// this call will NOT appear in metrics, because it matches excludeRoutes
|
||||
// this call will NOT appear in metrics,
|
||||
// because express will skip the metrics middleware
|
||||
app.get("/foo", (req, res) => res.send("bar"));
|
||||
|
||||
// calls to this route will appear in metrics
|
||||
@@ -113,8 +131,34 @@ app.use(/* your middleware */);
|
||||
app.listen(3000);
|
||||
```
|
||||
|
||||
## using with kraken.js
|
||||
|
||||
Here is meddleware config sample, which can be used in a standard **kraken.js** application:
|
||||
|
||||
```json
|
||||
{
|
||||
"middleware": {
|
||||
"expressPromBundle": {
|
||||
"route": "/((?!status|favicon.ico|robots.txt))*",
|
||||
"priority": 0,
|
||||
"module": {
|
||||
"name": "express-prom-bundle",
|
||||
"arguments": [
|
||||
{
|
||||
"includeMethod": true,
|
||||
"buckets": [0.1, 1, 5]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Changelog
|
||||
|
||||
* **2.1.0**
|
||||
* deprecate **excludeRoutes**, use **req.originalPath** instead of **req.path**
|
||||
* **2.0.0**
|
||||
* the reason for the version lift were:
|
||||
* compliance to official naming recommendation: https://prometheus.io/docs/practices/naming/
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"name": "express-prom-bundle",
|
||||
"version": "2.0.0",
|
||||
"version": "2.2.0",
|
||||
"description": "express middleware with popular prometheus metrics in one bundle",
|
||||
"main": "src/index.js",
|
||||
"keywords": [
|
||||
"prometheus",
|
||||
"metrics",
|
||||
"express",
|
||||
"bundle"
|
||||
"path",
|
||||
"method"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "node_modules/jasme/run.js"
|
||||
|
||||
@@ -169,6 +169,30 @@ describe('index', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('normalizePath can be replaced', done => {
|
||||
const app = express();
|
||||
const original = bundle.normalizePath;
|
||||
bundle.normalizePath = () => 'dummy';
|
||||
const instance = bundle({
|
||||
includePath: true,
|
||||
});
|
||||
app.use(instance);
|
||||
app.use('/test', (req, res) => res.send('it worked'));
|
||||
const agent = supertest(app);
|
||||
agent
|
||||
.get('/test')
|
||||
.end(() => {
|
||||
agent
|
||||
.get('/metrics')
|
||||
.end((err, res) => {
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.text).toMatch(/"dummy"/m);
|
||||
bundle.normalizePath = original;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Koa: metrics returns up=1', done => {
|
||||
const app = koa();
|
||||
const bundled = bundle({
|
||||
|
||||
23
spec/normalizeStatusCode.spec.js
Normal file
23
spec/normalizeStatusCode.spec.js
Normal file
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
/* eslint-env jasmine */
|
||||
|
||||
const normalizeStatusCode = require('../src/normalizeStatusCode');
|
||||
|
||||
describe('normalizeStatusCode', () => {
|
||||
it('returns original if disabled in opts', () => {
|
||||
expect(
|
||||
normalizeStatusCode({status_code: 404}, {normalizeStatusCode: false})
|
||||
).toBe(404);
|
||||
});
|
||||
|
||||
it('returns run callback if configured', () => {
|
||||
expect(
|
||||
normalizeStatusCode(
|
||||
{status_code: 500},
|
||||
{
|
||||
formatStatusCode: res => String(res.status_code).slice(0, -2) + 'xx'
|
||||
}
|
||||
)
|
||||
).toBe('5xx');
|
||||
});
|
||||
});
|
||||
24
src/index.js
24
src/index.js
@@ -1,8 +1,8 @@
|
||||
'use strict';
|
||||
const onFinished = require('on-finished');
|
||||
const url = require('url');
|
||||
const promClient = require('prom-client');
|
||||
const normalizePath = require('./normalizePath');
|
||||
const normalizeStatusCode = require('./normalizeStatusCode');
|
||||
|
||||
function matchVsRegExps(element, regexps) {
|
||||
for (let regexp of regexps) {
|
||||
@@ -39,7 +39,7 @@ function prepareMetricNames(opts, metricTemplates) {
|
||||
}
|
||||
|
||||
function main(opts) {
|
||||
opts = Object.assign({autoregister: true}, opts || {});
|
||||
opts = Object.assign({autoregister: true}, opts);
|
||||
if (arguments[2] && arguments[1] && arguments[1].send) {
|
||||
arguments[1].status(500)
|
||||
.send('<h1>500 Error</h1>\n'
|
||||
@@ -96,18 +96,17 @@ function main(opts) {
|
||||
metrics.up.set(1);
|
||||
}
|
||||
|
||||
const metricsMiddleware = function(req,res) {
|
||||
const metricsMiddleware = function(req, res) {
|
||||
res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||
res.end(promClient.register.metrics());
|
||||
};
|
||||
|
||||
const middleware = function (req, res, next) {
|
||||
|
||||
const path = req.path || url.parse(req.url).pathname;
|
||||
const path = req.originalUrl;
|
||||
let labels;
|
||||
|
||||
if (opts.autoregister && path === '/metrics') {
|
||||
return metricsMiddleware(req,res);
|
||||
if (opts.autoregister && path.match(/^\/metrics\/?$/)) {
|
||||
return metricsMiddleware(req, res);
|
||||
}
|
||||
|
||||
if (opts.excludeRoutes && matchVsRegExps(path, opts.excludeRoutes)) {
|
||||
@@ -118,12 +117,17 @@ function main(opts) {
|
||||
labels = {'status_code': 0};
|
||||
let timer = metrics[httpMtricName].startTimer(labels);
|
||||
onFinished(res, () => {
|
||||
labels.status_code = res.statusCode;
|
||||
if (opts.normalizeStatusCode) {
|
||||
labels.status_code = main.normalizeStatusCode(res, opts);
|
||||
} else {
|
||||
labels.status_code = res.statusCode;
|
||||
}
|
||||
|
||||
if (opts.includeMethod) {
|
||||
labels.method = req.method;
|
||||
}
|
||||
if (opts.includePath) {
|
||||
labels.path = normalizePath(req, opts);
|
||||
labels.path = main.normalizePath(req, opts);
|
||||
}
|
||||
timer();
|
||||
});
|
||||
@@ -140,4 +144,6 @@ function main(opts) {
|
||||
}
|
||||
|
||||
main.promClient = promClient;
|
||||
main.normalizePath = normalizePath;
|
||||
main.normalizeStatusCode = normalizeStatusCode;
|
||||
module.exports = main;
|
||||
|
||||
@@ -24,4 +24,3 @@ module.exports = function(req, opts) {
|
||||
}
|
||||
return urlValueParser.replacePathValues(path);
|
||||
};
|
||||
|
||||
|
||||
11
src/normalizeStatusCode.js
Normal file
11
src/normalizeStatusCode.js
Normal file
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function(res, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
if (typeof opts.formatStatusCode === 'function') {
|
||||
return opts.formatStatusCode(res, opts);
|
||||
}
|
||||
|
||||
return res.status_code;
|
||||
};
|
||||
Reference in New Issue
Block a user