Compare commits

...

14 Commits
2.0.0 ... 2.2.0

Author SHA1 Message Date
Konstantin Pogorelov
5b1aa494cb version 2.2.0 - normalizeStatus draft 2017-03-28 17:14:33 +02:00
The Experimentalist
65549a769b Merge pull request #4 from PauloPaquielli/feature/normalizeStatusCode 2017-03-28 17:08:57 +02:00
Paulo Duarte
de83ac09a0 Remove if unnecessary 2017-03-27 17:35:13 -03:00
Paulo Duarte
52865dfb02 Back version to prom-client 2017-03-27 17:17:59 -03:00
Paulo Duarte
5c6ed64a31 Fix lint error 2017-03-27 17:16:51 -03:00
Paulo Duarte
d8c6492163 Implement test 2017-03-27 12:03:53 -03:00
Paulo Duarte
c92b85ae96 Update prom-client 2017-03-24 11:08:29 -03:00
Paulo Duarte
48f8b992fd Make normalizeStatusCode generic 2017-03-24 02:18:41 -03:00
Paulo Duarte
61e4343a8c Implements group in status code metrics 2017-03-23 14:12:29 -03:00
Konstantin Pogorelov
7b89690d3b add missing spaces to readme 2017-01-19 17:17:08 +01:00
Konstantin Pogorelov
40db5cacbd Merge branch 'master' of github.com:jochen-schweizer/express-prom-bundle
Conflicts:
	README.md
	package.json
2017-01-19 16:54:58 +01:00
Konstantin Pogorelov
43334b923f deprecate excludeRoutes, use originalUrl when matching own /metrics path, readme adjustments, version 2.1.0 2017-01-19 16:52:15 +01:00
Konstantin Pogorelov
20eb668e36 export and make replaceable normalizePath(), fix/extend readme, version 2.0.2 2017-01-04 23:41:09 +01:00
Konstantin Pogorelov
53c4505378 fix typo in the readme 2016-12-11 20:41:40 +01:00
7 changed files with 134 additions and 26 deletions

View File

@@ -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/

View File

@@ -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"

View File

@@ -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({

View 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');
});
});

View File

@@ -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;

View File

@@ -24,4 +24,3 @@ module.exports = function(req, opts) {
}
return urlValueParser.replacePathValues(path);
};

View 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;
};