mirror of
https://github.com/BreizhHardware/express-prom-bundle.git
synced 2026-03-18 21:30:38 +01:00
add normalizePath option as tuple array, improve docs and advanced example, version to 4.1.0
This commit is contained in:
47
README.md
47
README.md
@@ -52,10 +52,13 @@ Which labels to include in `http_request_duration_seconds` metric:
|
|||||||
|
|
||||||
Extra transformation callbacks:
|
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](https://github.com/disjunction/url-value-parser).
|
* **urlValueParser**: options passed when instantiating [url-value-parser](https://github.com/disjunction/url-value-parser).
|
||||||
This is the easiest way to customize which parts of the URL should be replaced with "#val".
|
This is the easiest way to customize which parts of the URL should be replaced with "#val".
|
||||||
See the [docs](https://github.com/disjunction/url-value-parser) of url-value-parser module for details.
|
See the [docs](https://github.com/disjunction/url-value-parser) of url-value-parser module for details.
|
||||||
* **normalizePath**: `function(req)` generates path values from express `req` (see details below)
|
|
||||||
* **formatStatusCode**: `function(res)` producing final status code from express `res` object, e.g. you can combine `200`, `201` and `204` to just `2xx`.
|
* **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**
|
* **transformLabels**: `function(labels, req, res)` transforms the **labels** object, e.g. setting dynamic values to **customLabels**
|
||||||
|
|
||||||
@@ -67,12 +70,6 @@ Other options:
|
|||||||
to keep `express-prom-bundle` runnable using confit (e.g. with kraken.js) without writing any JS code,
|
to keep `express-prom-bundle` runnable using confit (e.g. with kraken.js) without writing any JS code,
|
||||||
see [advanced example](https://github.com/jochen-schweizer/express-prom-bundle/blob/master/advanced-example.js)
|
see [advanced example](https://github.com/jochen-schweizer/express-prom-bundle/blob/master/advanced-example.js)
|
||||||
|
|
||||||
Deprecated:
|
|
||||||
|
|
||||||
* **whitelist**, **blacklist**: array of strings or regexp specifying which metrics to include/exclude (there are only 2 metrics)
|
|
||||||
* **excludeRoutes**: 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 to use express or meddleware features instead of this option.
|
|
||||||
* **httpDurationMetricName**: name of the request duration histogram metric. (Default: `http_request_duration_seconds`)
|
|
||||||
|
|
||||||
### More details on includePath option
|
### More details on includePath option
|
||||||
|
|
||||||
Let's say you want to have latency statistics by URL path,
|
Let's say you want to have latency statistics by URL path,
|
||||||
@@ -83,10 +80,33 @@ 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,
|
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
|
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.
|
normalized to `/user/#val/profile` and that will become the value for the label.
|
||||||
|
These conversions are handled by `normalizePath` function.
|
||||||
|
|
||||||
You can override this magical behavior and define your own function by
|
You can extend this magical behavior by providing
|
||||||
providing an optional callback using **normalizePath** option.
|
additional RegExp rules to be performed,
|
||||||
You can also replace the default **normalizePath** function globally.
|
or override `normalizePath` with your own function.
|
||||||
|
|
||||||
|
#### Example 1 (add custom RegExp):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
app.use(promBundle({
|
||||||
|
normalizePath: [
|
||||||
|
// collect paths like "/customer/johnbobson" as just one "/custom/#name"
|
||||||
|
['^/customer/.*', '/customer/#name'],
|
||||||
|
|
||||||
|
// collect paths like "/bobjohnson/order-list" as just one "/#name/order-list"
|
||||||
|
['^.*/order-list', '/#name/order-list']
|
||||||
|
],
|
||||||
|
urlValueParser: {
|
||||||
|
minHexLength: 5,
|
||||||
|
extraMasks: [
|
||||||
|
'ORD[0-9]{5,}' // replace strings like ORD1243423, ORD673562 as #val
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example 2 (override normalizePath function):
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
app.use(promBundle(/* options? */));
|
app.use(promBundle(/* options? */));
|
||||||
@@ -96,8 +116,8 @@ app.use(promBundle(/* options? */));
|
|||||||
const originalNormalize = promBundle.normalizePath;
|
const originalNormalize = promBundle.normalizePath;
|
||||||
promBundle.normalizePath = (req, opts) => {
|
promBundle.normalizePath = (req, opts) => {
|
||||||
const path = originalNormalize(req, opts);
|
const path = originalNormalize(req, opts);
|
||||||
// count all docs (no matter which file) as a single path
|
// count all docs as one path, but /docs/login as a separate one
|
||||||
return path.match(/^\/docs/) ? '/docs/*' : path;
|
return (path.match(/^\/docs/) && !path.match(/^\/login/)) ? '/docs/*' : path;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -106,7 +126,6 @@ For more details:
|
|||||||
* [normalizePath.js](https://github.com/jochen-schweizer/express-prom-bundle/blob/master/src/normalizePath.js) - source code for path processing
|
* [normalizePath.js](https://github.com/jochen-schweizer/express-prom-bundle/blob/master/src/normalizePath.js) - source code for path processing
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## express example
|
## express example
|
||||||
|
|
||||||
setup std. metrics but exclude `up`-metric:
|
setup std. metrics but exclude `up`-metric:
|
||||||
@@ -178,7 +197,7 @@ while replacing all HEX values starting from 5 characters and all emails in the
|
|||||||
"urlValueParser": {
|
"urlValueParser": {
|
||||||
"minHexLength": 5,
|
"minHexLength": 5,
|
||||||
"extraMasks": [
|
"extraMasks": [
|
||||||
"^[^@]+@[^@]+\\.[^@]+$"
|
"^[0-9]+\\.[0-9]+\\.[0-9]+$"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ const app = express();
|
|||||||
const promBundle = require('express-prom-bundle');
|
const promBundle = require('express-prom-bundle');
|
||||||
|
|
||||||
const bundle = promBundle({
|
const bundle = promBundle({
|
||||||
blacklist: [/up/],
|
|
||||||
buckets: [0.1, 0.4, 0.7],
|
buckets: [0.1, 0.4, 0.7],
|
||||||
includeMethod: true,
|
includeMethod: true,
|
||||||
includePath: true,
|
includePath: true,
|
||||||
@@ -19,9 +18,12 @@ const bundle = promBundle({
|
|||||||
urlValueParser: {
|
urlValueParser: {
|
||||||
minHexLength: 5,
|
minHexLength: 5,
|
||||||
extraMasks: [
|
extraMasks: [
|
||||||
"^[^@]+@[^@]+\\.[^@]+$"
|
"^[0-9]+\\.[0-9]+\\.[0-9]+$" // replace dot-separated dates with #val
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
normalizePath: [
|
||||||
|
['^/foo', '/example'] // replace /foo with /example
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(bundle);
|
app.use(bundle);
|
||||||
@@ -46,7 +48,7 @@ app.listen(3000, () => console.info( // eslint-disable-line
|
|||||||
'listening on 3000\n'
|
'listening on 3000\n'
|
||||||
+ 'test in shell console\n\n'
|
+ 'test in shell console\n\n'
|
||||||
+ 'curl localhost:3000/foo/1234\n'
|
+ 'curl localhost:3000/foo/1234\n'
|
||||||
+ 'curl localhost:3000/foo/john%40example.com\n'
|
+ 'curl localhost:3000/foo/09.08.2018\n'
|
||||||
+ 'curl -X DELETE localhost:3000/foo/5432\n'
|
+ 'curl -X DELETE localhost:3000/foo/5432\n'
|
||||||
+ 'curl localhost:3000/bar\n'
|
+ 'curl localhost:3000/bar\n'
|
||||||
+ 'curl localhost:3000/metrics\n'
|
+ 'curl localhost:3000/metrics\n'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "express-prom-bundle",
|
"name": "express-prom-bundle",
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"description": "express middleware with popular prometheus metrics in one bundle",
|
"description": "express middleware with popular prometheus metrics in one bundle",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -185,50 +185,78 @@ describe('index', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('normalizePath can be replaced gloablly', done => {
|
describe('usage of normalizePath()', () => {
|
||||||
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('normalizePath can be overridden', done => {
|
it('normalizePath can be replaced gloablly', done => {
|
||||||
const app = express();
|
const app = express();
|
||||||
const instance = bundle({
|
const original = bundle.normalizePath;
|
||||||
includePath: true,
|
bundle.normalizePath = () => 'dummy';
|
||||||
normalizePath: req => req.originalUrl + '-suffixed'
|
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(/"\/test-suffixed"/m);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
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('normalizePath function can be overridden', done => {
|
||||||
|
const app = express();
|
||||||
|
const instance = bundle({
|
||||||
|
includePath: true,
|
||||||
|
normalizePath: req => req.originalUrl + '-suffixed'
|
||||||
|
});
|
||||||
|
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(/"\/test-suffixed"/m);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalizePath can be passed as an array of [regex, replacement]', done => {
|
||||||
|
const app = express();
|
||||||
|
const instance = bundle({
|
||||||
|
includePath: true,
|
||||||
|
normalizePath: [
|
||||||
|
['^/docs/whatever/.*$', '/docs'],
|
||||||
|
[/^\/docs$/, '/mocks']
|
||||||
|
]
|
||||||
|
});
|
||||||
|
app.use(instance);
|
||||||
|
app.use('/docs/whatever/:andmore', (req, res) => res.send('it worked'));
|
||||||
|
const agent = supertest(app);
|
||||||
|
agent
|
||||||
|
.get('/docs/whatever/unimportant')
|
||||||
|
.end(() => {
|
||||||
|
agent
|
||||||
|
.get('/metrics')
|
||||||
|
.end((err, res) => {
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(res.text).toMatch(/"\/mocks"/m);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('formatStatusCode can be overridden', done => {
|
it('formatStatusCode can be overridden', done => {
|
||||||
|
|||||||
@@ -8,4 +8,24 @@ describe('normalizePath', () => {
|
|||||||
expect(normalizePath({url: '/a/12345'}))
|
expect(normalizePath({url: '/a/12345'}))
|
||||||
.toBe('/a/#val');
|
.toBe('/a/#val');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses normalizePath option', () => {
|
||||||
|
const url = '/hello/world/i/am/finally/free!!!';
|
||||||
|
const result = normalizePath({url}, {
|
||||||
|
normalizePath: [
|
||||||
|
['/hello','/goodbye'],
|
||||||
|
['[^/]+$','happy'],
|
||||||
|
]
|
||||||
|
});
|
||||||
|
expect(result).toBe('/goodbye/world/i/am/finally/happy');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws error is bad tuples provided as normalizePath', () => {
|
||||||
|
const subject = () => normalizePath({url: '/test'}, {
|
||||||
|
normalizePath: [
|
||||||
|
['/hello','/goodbye', 'test']
|
||||||
|
]
|
||||||
|
});
|
||||||
|
expect(subject).toThrow();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -138,7 +138,9 @@ function main(opts) {
|
|||||||
labels.method = req.method;
|
labels.method = req.method;
|
||||||
}
|
}
|
||||||
if (opts.includePath) {
|
if (opts.includePath) {
|
||||||
labels.path = opts.normalizePath(req, opts);
|
labels.path = typeof opts.normalizePath == 'function'
|
||||||
|
? opts.normalizePath(req, opts)
|
||||||
|
: main.normalizePath(req, opts);
|
||||||
}
|
}
|
||||||
if (opts.customLabels) {
|
if (opts.customLabels) {
|
||||||
Object.assign(labels, opts.customLabels);
|
Object.assign(labels, opts.customLabels);
|
||||||
|
|||||||
@@ -10,7 +10,18 @@ module.exports = function(req, opts) {
|
|||||||
// originalUrl is taken, because url and path can be changed
|
// originalUrl is taken, because url and path can be changed
|
||||||
// by middlewares such as 'router'. Note: this function is called onFinish
|
// by middlewares such as 'router'. Note: this function is called onFinish
|
||||||
/// i.e. always in the tail of the middleware chain
|
/// i.e. always in the tail of the middleware chain
|
||||||
const path = url.parse(req.originalUrl || req.url).pathname;
|
let path = url.parse(req.originalUrl || req.url).pathname;
|
||||||
|
|
||||||
|
const normalizePath = opts && opts.normalizePath;
|
||||||
|
if (Array.isArray(normalizePath)) {
|
||||||
|
for (const tuple of normalizePath) {
|
||||||
|
if (!Array.isArray(tuple) || tuple.length !== 2) {
|
||||||
|
throw new Error('Bad tuple provided in normalizePath option, expected: [regex, replacement]');
|
||||||
|
}
|
||||||
|
const regex = typeof tuple[0] === 'string' ? RegExp(tuple[0]) : tuple[0];
|
||||||
|
path = path.replace(regex, tuple[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!urlValueParser) {
|
if (!urlValueParser) {
|
||||||
urlValueParser = new UrlValueParser(opts && opts.urlValueParser);
|
urlValueParser = new UrlValueParser(opts && opts.urlValueParser);
|
||||||
|
|||||||
Reference in New Issue
Block a user