Compare commits

...

83 Commits
5.0.2 ... 6.3.3

Author SHA1 Message Date
Konstantin Pogorelov
5300d0ef82 bump 6.3.3 2021-02-19 12:46:56 +01:00
Konstantin Pogorelov
62abb62772 suppress error logging by providing NODE_ENV=test, relates to #78 2021-02-19 12:42:27 +01:00
Konstantin Pogorelov
bef92b77e1 Merge pull request #78 from FauxFaux/fix/handle-errors
fix: handle errors from prom-client
2021-02-19 12:17:05 +01:00
Chris West (Faux)
99d8fc1ea9 fix: handle errors from prom-client 2021-02-19 08:32:48 +00:00
Konstantin Pogorelov
2478e617bb bump 6.3.2 2021-02-18 15:31:34 +01:00
Konstantin Pogorelov
92db62dc90 run dtslint on travis with --onlyTestTsNext as a workaround for failing prom-client typing with older TS versions 2021-02-18 15:05:08 +01:00
Konstantin Pogorelov
3ffdeef8ae add linting (eslint) to travis 2021-02-18 13:04:39 +01:00
Konstantin Pogorelov
7c35f1beb6 add indentation rule for eslint 2021-02-18 13:03:25 +01:00
Konstantin Pogorelov
c44d157cfe codestyle: fix indentation 2021-02-18 13:01:26 +01:00
Konstantin Pogorelov
731fd3ec01 replace node_js v13 with v14 in travis config 2021-02-18 12:52:00 +01:00
Konstantin Pogorelov
3f587fb760 reduce minimal peer prom-client version to 12, allow any higher major version (let's be optimistic), see #76 2021-02-18 12:12:06 +01:00
Konstantin Pogorelov
3c2779d0d1 bump 6.3.1 2020-12-24 12:23:39 +01:00
Konstantin Pogorelov
a3c15b1645 support Promise response from .metrics() and .clusterMetrics(), see #71 2020-12-24 12:23:20 +01:00
Konstantin Pogorelov
f4677ce6c6 update dev. prom-client to v13, see #71 2020-12-24 12:17:02 +01:00
Konstantin Pogorelov
958453eb91 bump 6.3.0 2020-11-29 15:48:17 +01:00
Konstantin Pogorelov
0205d4cfc8 fix bypass test, minor change to docs, move bypass logic after serving the /metrics route, see MR #70 2020-11-29 15:47:28 +01:00
yacine
bffb4cf16e changed wording + added doc + unit test 2020-11-29 15:13:01 +01:00
yacine
52fdbf030f ability to exclude incoming request from metrics 2020-11-28 21:42:52 +01:00
Konstantin Pogorelov
c55d5f4862 fix duplicate httpDurationMetricName in types 2020-10-06 13:05:26 +02:00
Konstantin Pogorelov
75b1e2cafa bump 6.2.0 2020-10-06 12:54:37 +02:00
Konstantin Pogorelov
e2a8869b7f Merge pull request #43 from GabrielCastro/patch-1
update types to correct transformLabels
2020-10-06 12:51:26 +02:00
Konstantin Pogorelov
c64772dfc9 Merge pull request #55 from dpc/dpc-patch-1
Add a missing option to `Opts`
2020-10-06 12:49:33 +02:00
Konstantin Pogorelov
c1b1022fb5 Merge pull request #56 from Collaborne/pr/middleware-result
Declare that the metricsMiddleware is accessible on the bundle as well
2020-10-06 12:48:39 +02:00
Konstantin Pogorelov
16dfa7f926 bump version to 6.1.0 2020-07-22 18:24:43 +02:00
Konstantin Pogorelov
7e58bd3d06 Merge pull request #47 from jochen-schweizer/dependabot/npm_and_yarn/eslint-utils-1.4.3
Bump eslint-utils from 1.3.1 to 1.4.3
2020-07-22 18:19:46 +02:00
Konstantin Pogorelov
adf15f03f4 Merge pull request #52 from jochen-schweizer/dependabot/npm_and_yarn/acorn-6.4.1
Bump acorn from 6.0.4 to 6.4.1
2020-07-22 18:19:28 +02:00
Konstantin Pogorelov
6629c09dfa Merge pull request #60 from anxolin/add-missing-metric-name-opt
Add missing metric name parameter
2020-07-22 18:16:32 +02:00
Konstantin Pogorelov
fe2d1d25ec Merge pull request #64 from jochen-schweizer/dependabot/npm_and_yarn/handlebars-4.7.6
Bump handlebars from 4.0.12 to 4.7.6
2020-07-22 18:16:03 +02:00
Konstantin Pogorelov
a5d63925ce Merge pull request #62 from jonaskello/patch-1
Fix spelling
2020-07-22 18:15:30 +02:00
dependabot[bot]
43ba2091d3 Bump handlebars from 4.0.12 to 4.7.6
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.0.12 to 4.7.6.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.12...v4.7.6)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-22 16:13:09 +00:00
dependabot[bot]
1909018c17 Bump acorn from 6.0.4 to 6.4.1
Bumps [acorn](https://github.com/acornjs/acorn) from 6.0.4 to 6.4.1.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/6.0.4...6.4.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-22 16:13:05 +00:00
dependabot[bot]
52f3a955df Bump eslint-utils from 1.3.1 to 1.4.3
Bumps [eslint-utils](https://github.com/mysticatea/eslint-utils) from 1.3.1 to 1.4.3.
- [Release notes](https://github.com/mysticatea/eslint-utils/releases)
- [Commits](https://github.com/mysticatea/eslint-utils/compare/v1.3.1...v1.4.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-22 16:12:59 +00:00
Konstantin Pogorelov
e2800e7d30 Merge pull request #53 from jochen-schweizer/dependabot/npm_and_yarn/js-yaml-3.13.1
Bump js-yaml from 3.12.0 to 3.13.1
2020-07-22 18:12:31 +02:00
Konstantin Pogorelov
fff0384605 Merge pull request #63 from jochen-schweizer/dependabot/npm_and_yarn/lodash-4.17.19
Bump lodash from 4.17.11 to 4.17.19
2020-07-22 18:12:11 +02:00
Konstantin Pogorelov
514745b23a Merge pull request #46 from aaronleesmith/feature/customizable-prom-registry
Allows for customizing promRegistry when creating prom bundle.
2020-07-22 18:10:08 +02:00
dependabot[bot]
77730b9df5 Bump lodash from 4.17.11 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.11 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.11...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-16 00:02:18 +00:00
Jonas Kello
6d9da12d97 Fix spelling 2020-06-06 18:59:44 +02:00
Anxo Rodriguez
f1f36f0fb7 Add missing metric name parameter 2020-04-27 19:36:43 +02:00
Andreas Kohn
d2f0088f3c Declare that the metricsMiddleware is accessible on the bundle as well
Related-to: #49
2020-04-09 21:09:38 +02:00
Dawid Ciężarkiewicz
4f89424c79 Add a missing option to Opts 2020-04-09 09:28:22 -07:00
dependabot[bot]
33765b3654 Bump js-yaml from 3.12.0 to 3.13.1
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 3.12.0 to 3.13.1.
- [Release notes](https://github.com/nodeca/js-yaml/releases)
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/3.12.0...3.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-21 18:08:10 +00:00
Konstantin Pogorelov
1a8f38c983 Merge pull request #51 from sam-github/update-prom-client
Update to prom-client v12, requiring Node >=10
2020-03-21 19:07:39 +01:00
Konstantin Pogorelov
23cedb9f05 bump to 6.0.0 2020-03-21 18:49:56 +01:00
Sam Roberts
20c6a7de24 Update to prom-client v12, requiring Node >=10
v12 drops support for versions of Node.js that are no longer supported
(as-of January, 2020), and also removes the timeout option from
collectDefaultMetrics().

Remove 6 and 8 from Travis, and add 12 and 13 (so all the non-EOL versions
of Node.js are tested).
2020-03-06 16:39:47 -08:00
Aaron Smith
0a1fda31de Fixes lint issue. 2019-09-19 11:39:11 -06:00
Aaron Smith
7654a4ec79 Allows for customizing promRegistry when creating prom bundle. 2019-09-19 11:10:43 -06:00
Gabriel Castro
faeeb7b57b update types to correct transformLabels returning
`transformLabels` should return void as the library expects
the labels object to be modified in place and does not use the return value
2019-07-22 16:38:14 -04:00
Konstantin Pogorelov
a21bd439cf bump 5.1.5 2019-05-01 14:53:47 +02:00
Konstantin Pogorelov
90f2d90e46 add dtslint to the main lint target 2019-05-01 14:53:38 +02:00
Konstantin Pogorelov
71c0e52020 Merge pull request #38 from raszi/fix-types
fix(types): fix type definitions and add tests
2019-05-01 14:50:34 +02:00
KARASZI István
78e25d6587 Add back index.d.ts 2019-05-01 14:15:45 +02:00
KARASZI István
02f3fe9008 Simplify type definition 2019-04-30 22:54:46 +02:00
KARASZI István
2a5e25dd77 Fix line ending 2019-04-30 22:46:49 +02:00
KARASZI István
f770ab800d Try to fix Node 6 issue 2019-04-30 22:46:03 +02:00
KARASZI István
6041bee4dd Run dtslint on Travis 2019-04-30 22:40:33 +02:00
KARASZI István
a92b4cfbe6 Remove unnecessary newline 2019-04-30 22:36:23 +02:00
KARASZI István
8df2778337 Fix types and add tests 2019-04-30 22:26:39 +02:00
Konstantin Pogorelov
b00d88c4a7 bump 5.1.4 2019-04-16 13:13:12 +02:00
Konstantin Pogorelov
11988cb025 Merge pull request #37 from cerodriguezl/fix-ts-definition
Fix the definition of the Opts interface
2019-04-16 13:12:39 +02:00
Konstantin Pogorelov
df6854ee04 Merge pull request #36 from gabordobrei/master
make transformLabels optional in index.d.ts
2019-04-16 13:08:14 +02:00
Carlos Rodriguez
48d883b7f2 Fix the definition of the Opts interface
The `buckets` property was defined as a `Tuple` (`[number]`) instead of an Array
(`number[]`).
2019-04-15 18:21:35 -05:00
Gábor Döbrei
b835e90560 make transformLabels optional in index.d.ts 2019-04-15 15:53:11 +02:00
Konstantin Pogorelov
088ff5a063 make customLabels optional in index.d.ts 2019-04-09 22:15:36 +02:00
Konstantin Pogorelov
fd12de6ea3 bump 5.1.3 2019-04-09 22:10:00 +02:00
Konstantin Pogorelov
0b8706dd42 remove trailing spaces 2019-04-09 22:09:44 +02:00
Konstantin Pogorelov
982ff86543 Merge pull request #35 from aaronleesmith/master
add customLabels to index.d.ts
2019-04-09 22:05:51 +02:00
Aaron Smith
5f39d8f357 Update index.d.ts
Custom labels was added as an option but never added to the typescript definition file, breaking the usage during typescript compilation.
2019-04-09 13:54:17 -06:00
Konstantin Pogorelov
d405e3f584 bump 5.1.2 2019-04-08 12:02:45 +02:00
Konstantin Pogorelov
ff147d8fc4 Merge pull request #34 from cliedeman/fix-typedef
typedefs: normalizePath should return a string
2019-04-08 12:01:47 +02:00
Ciaran Liedeman
43f5a2b04f typedefs: normalizePath should return a string 2019-04-08 10:21:53 +02:00
Konstantin Pogorelov
09ee5d954a bump 5.1.1 2019-04-05 18:10:33 +02:00
Konstantin Pogorelov
33cca0d2cf remove .npmignore because files are now whitelisted 2019-04-05 18:09:56 +02:00
Konstantin Pogorelov
2040c043fd Merge branch 'cliedeman-feat/typescript-definitions' 2019-04-05 18:02:10 +02:00
Konstantin Pogorelov
f6466d007b Merge pull request #33 from cliedeman/feat/typescript-definitions
Added Typescript definitions
2019-04-05 18:01:33 +02:00
Konstantin Pogorelov
e68466d7af experimental workaround for testing cluster on travis 2019-04-05 17:56:22 +02:00
Ciaran Liedeman
6b49ffab08 Added Typescript definitions 2019-04-04 11:31:41 +02:00
Konstantin Pogorelov
cb128c7520 bump 5.1.0 2019-03-14 13:37:08 +01:00
Konstantin Pogorelov
d336165848 extract metricType as a seprate odc section 2019-03-14 13:36:54 +01:00
Konstantin Pogorelov
cfe0065146 minor wording changes 2019-03-14 13:17:47 +01:00
Konstantin Pogorelov
963a66a25e Merge pull request #30 from caraboides/master
pass maxAgeSeconds & ageBuckets to summary metrics
2019-03-14 13:06:33 +01:00
Christian Hennig
389684b426 Documentation for sliding window for summary metrics 2019-03-14 11:58:10 +01:00
Konstantin Pogorelov
846ccfc641 Update src/index.js
Co-Authored-By: caraboides <caraboides@googlemail.com>
2019-03-14 12:47:08 +02:00
Christian Hennig
17fe5f4c70 pass maxAgeSeconds & ageBuckets to summary metrics 2019-03-14 10:18:55 +01:00
14 changed files with 1039 additions and 111 deletions

View File

@@ -17,6 +17,7 @@
"extends": "eslint:recommended",
"rules": {
"indent": [1, 2],
"array-bracket-spacing": [2, "never"],
"block-scoped-var": 2,
"brace-style": [2, "1tbs"],

View File

@@ -1,8 +0,0 @@
advanced-example.js
docker-compose.yml
spec
.travis.yml
.eslintrc
coverage
.vscode
Makefile

View File

@@ -1,9 +1,13 @@
language: node_js
node_js:
- "6"
- "8"
- "10"
- "12"
- "14"
notifications:
email: false
before_install:
- npm install prom-client
script:
- npm run lint
- npm test
- npm run dtslint-next

View File

@@ -1,9 +1,10 @@
.PHONY: coverage
test:
./node_modules/jasme/run.js
npm test
lint:
node_modules/eslint/bin/eslint.js src
node_modules/.bin/dtslint types
coverage:
node_modules/istanbul/lib/cli.js cover \
-i 'src/*' \

View File

@@ -46,13 +46,29 @@ 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**
* **includePath**: URL path (see important 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.
* **includeUp**: include an auxiliary "up"-metric which always returns 1, default: **true**
* **metricsPath**: replace the `/metrics` route with a **regex** or exact **string**. Note: it is highly recommended to just stick to the default
* **metricType**: histogram/summary selection. See more details below
* **bypass**: function taking express request as an argument and determines whether the given request should be excluded in the metrics, default: **() => false**
Extra transformation callbacks:
### metricType option ###
Two metric types are supported for `http_request_duration_seconds` metric:
* [histogram](https://prometheus.io/docs/concepts/metric_types/#histogram) (default)
* [summary](https://prometheus.io/docs/concepts/metric_types/#summary)
Additional options for **histogram**:
* **buckets**: buckets used for the `http_request_duration_seconds` histogram
Additional options for **summary**:
* **percentiles**: percentiles used for `http_request_duration_seconds` summary
* **ageBuckets**: ageBuckets configures how many buckets we have in our sliding window for the summary
* **maxAgeSeconds**: the maxAgeSeconds will tell how old a bucket can be before it is reset
### Transformation callbacks ###
* **normalizePath**: `function(req)` or `Array`
* if function is provided, then it should generate path value from express `req`
@@ -64,18 +80,13 @@ Extra transformation callbacks:
* **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**
Metric type:
### Other options ###
* **metricType**: two metric types are supported for `http_request_duration_seconds` metric: [histogram](https://prometheus.io/docs/concepts/metric_types/#histogram) and [summary](https://prometheus.io/docs/concepts/metric_types/#summary), default: **histogram**
Other options:
* **buckets**: buckets used for `http_request_duration_seconds` histogram
* **percentiles**: percentiles used for `http_request_duration_seconds` summary
* **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](https://github.com/jochen-schweizer/express-prom-bundle/blob/master/advanced-example.js)
* **promRegistry**: Optional `promClient.Registry` instance to attach metrics to. Defaults to global `promClient.register`.
### More details on includePath option
@@ -234,7 +245,6 @@ while replacing all HEX values starting from 5 characters and all IP addresses i
"buckets": [0.1, 1, 5],
"promClient": {
"collectDefaultMetrics": {
"timeout": 2000
}
},
"urlValueParser": {

View File

@@ -14,7 +14,6 @@ const bundle = promBundle({
metricsPath: '/prometheus',
promClient: {
collectDefaultMetrics: {
timeout: 1000
}
},
urlValueParser: {

762
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "express-prom-bundle",
"version": "5.0.2",
"version": "6.3.3",
"description": "express middleware with popular prometheus metrics in one bundle",
"main": "src/index.js",
"keywords": [
@@ -10,9 +10,17 @@
"path",
"method"
],
"files": [
"src",
"types/index.d.ts"
],
"types": "types",
"scripts": {
"test": "node_modules/jasme/run.js",
"coverage": "make coverage"
"test": "NODE_ENV=test node_modules/jasme/run.js",
"lint": "eslint src",
"coverage": "make coverage",
"dtslint": "dtslint types",
"dtslint-next": "dtslint --onlyTestTsNext types"
},
"author": "Konstantin Pogorelov <or@pluseq.com>",
"license": "MIT",
@@ -21,24 +29,28 @@
"url-value-parser": "^2.0.0"
},
"devDependencies": {
"@types/express": "^4.16.1",
"coveralls": "^3.0.2",
"dtslint": "^0.7.1",
"eslint": "^5.11.0",
"express": "^4.16.4",
"istanbul": "^0.4.5",
"jasme": "^6.0.0",
"koa": "^2.6.2",
"koa-connect": "^2.0.1",
"prom-client": "^13.0.0",
"supertest": "^3.3.0",
"supertest-koa-agent": "^0.3.0"
"supertest-koa-agent": "^0.3.0",
"typescript": "^3.4.5"
},
"peerDependencies": {
"prom-client": "^11.1.2"
"prom-client": ">=12.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/jochen-schweizer/express-prom-bundle.git"
},
"engines": {
"node": ">=6.0.0"
"node": ">=10"
}
}

View File

@@ -200,6 +200,46 @@ describe('index', () => {
});
});
it('bypass requests', done => {
const app = express();
const instance = bundle({
bypass: (req)=> {
// metrics added here to attempt skipping /metrics
// this should fail though, because serving /metrics preceeds bypassing
return !!req.url.match(/test|bad.word|metrics/)
}
});
app.use(instance);
app.use('/test', (req, res) => res.send('it worked'));
app.use('/some/bad-word', (req, res) => res.send('it worked too'));
app.use('/good-word', (req, res) => res.send('this will be counted'));
const agent = supertest(app);
agent
.get('/test')
.end(() => {
agent
.get('/some/bad-word')
.end(() => {
agent
.get('/good-word')
.end(() => {
const metricHashMap = instance.metrics.http_request_duration_seconds.hashMap;
expect(metricHashMap['status_code:200']).toBeDefined();
// only /good-word should be counted
expect(metricHashMap['status_code:200'].count).toBe(1);
agent
.get('/metrics')
.end((err, res) => {
expect(res.status).toBe(200);
done();
});
});
});
});
});
it('complains about deprecated options', () => {
expect(() => bundle({prefix: 'hello'})).toThrow();
});
@@ -369,6 +409,27 @@ describe('index', () => {
});
});
it('handles errors in collectors', done => {
const app = express();
const instance = bundle({});
app.use(instance);
new promClient.Gauge({
name: 'kaboom',
help: 'this metric explodes',
collect() {
throw new Error('kaboom!');
}
});
// the error will NOT be displayed if NODE_ENV=test (as defined in package.json)
supertest(app)
.get('/metrics')
.expect(500)
.end((err) => done(err));
});
it('customLabels={foo: "bar"} adds foo="bar" label to metrics', done => {
const app = express();
const instance = bundle({
@@ -443,11 +504,10 @@ describe('index', () => {
bundle({
promClient: {
collectDefaultMetrics: {
timeout: 3000
}
}
});
expect(spy).toHaveBeenCalledWith({timeout: 3000});
expect(spy).toHaveBeenCalledWith({});
});
describe('usage of clusterMetrics()', () => {
@@ -472,7 +532,10 @@ describe('index', () => {
const agent = supertest(app);
// create a fake worker, which would not respond in time
cluster.workers = [{send: () => {}}];
cluster.workers = [{
isConnected: () => true,
send: () => {}
}];
const errorSpy = spyOn(console, 'error'); // mute console.error

View File

@@ -17,20 +17,37 @@ function matchVsRegExps(element, regexps) {
}
function clusterMetrics() {
const aggregatorRegistry = new promClient.AggregatorRegistry();
const aggregatorRegistry = new promClient.AggregatorRegistry();
const metricsMiddleware = function(req, res) {
aggregatorRegistry.clusterMetrics((err, clusterMetrics) => {
if (err) {
console.error(err);
return res.sendStatus(500);
}
res.set('Content-Type', aggregatorRegistry.contentType);
res.send(clusterMetrics);
});
};
const metricsMiddleware = function(req, res) {
function sendClusterMetrics(clusterMetrics) {
res.set('Content-Type', aggregatorRegistry.contentType);
res.send(clusterMetrics);
}
return metricsMiddleware;
function sendClusterMetricsError(err) {
console.error(err);
return res.sendStatus(500);
}
// since prom-client@13 clusterMetrics() method doesn't take cb param,
// but we provide it anyway, as at this stage it's unknown which version of prom-client is used
const response = aggregatorRegistry.clusterMetrics((err, clusterMetrics) => {
if (err) {
return sendClusterMetricsError(err);
}
sendClusterMetrics(clusterMetrics);
});
// if we find out that it was a promise and our cb was useless...
if (response && response.then) {
response
.then(result => sendClusterMetrics(result))
.catch(err => sendClusterMetricsError(err));
}
};
return metricsMiddleware;
}
function main(opts) {
@@ -50,7 +67,8 @@ function main(opts) {
normalizePath: main.normalizePath,
formatStatusCode: main.normalizeStatusCode,
metricType: 'histogram',
promClient: {}
promClient: {},
promRegistry: promClient.register
}, opts
);
@@ -90,14 +108,18 @@ function main(opts) {
name: httpMetricName,
help: 'duration summary of http responses labeled with: ' + labels.join(', '),
labelNames: labels,
percentiles: opts.percentiles || [0.5, 0.75, 0.95, 0.98, 0.99, 0.999]
percentiles: opts.percentiles || [0.5, 0.75, 0.95, 0.98, 0.99, 0.999],
maxAgeSeconds: opts.maxAgeSeconds,
ageBuckets: opts.ageBuckets,
registers: [opts.promRegistry]
});
} else if (opts.metricType === 'histogram' || !opts.metricType) {
return new promClient.Histogram({
name: httpMetricName,
help: 'duration histogram of http responses labeled with: ' + labels.join(', '),
labelNames: labels,
buckets: opts.buckets || [0.003, 0.03, 0.1, 0.3, 1.5, 10]
buckets: opts.buckets || [0.003, 0.03, 0.1, 0.3, 1.5, 10],
registers: [opts.promRegistry]
});
} else {
throw new Error('metricType option must be histogram or summary');
@@ -111,14 +133,28 @@ function main(opts) {
if (opts.includeUp !== false) {
metrics.up = new promClient.Gauge({
name: 'up',
help: '1 = up, 0 = not up'
help: '1 = up, 0 = not up',
registers: [opts.promRegistry]
});
metrics.up.set(1);
}
const metricsMiddleware = function(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(promClient.register.metrics());
const metricsMiddleware = function(req, res, next) {
const sendSuccesss = (output) => {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(output);
};
const metricsResponse = opts.promRegistry.metrics();
// starting from prom-client@13 .metrics() returns a Promise
if (metricsResponse.then) {
metricsResponse
.then(output => sendSuccesss(output))
.catch(err => next(err));
} else {
// compatibility fallback for previous versions of prom-client@<=12
sendSuccesss(metricsResponse);
}
};
const metricsMatch = opts.metricsPath instanceof RegExp ? opts.metricsPath
@@ -128,7 +164,13 @@ function main(opts) {
const path = req.originalUrl || req.url; // originalUrl gets lost in koa-connect?
if (opts.autoregister && path.match(metricsMatch)) {
return metricsMiddleware(req, res);
return metricsMiddleware(req, res, next);
}
// bypass() is checked only after /metrics was processed
// if you wish to disable /metrics use autoregister:false instead
if (opts.bypass && opts.bypass(req)) {
return next();
}
if (opts.excludeRoutes && matchVsRegExps(path, opts.excludeRoutes)) {

66
types/index.d.ts vendored Normal file
View File

@@ -0,0 +1,66 @@
// TypeScript Version: 2.8
import { Request, RequestHandler, Response } from 'express';
import { DefaultMetricsCollectorConfiguration, Registry } from 'prom-client';
export {};
export = express_prom_bundle;
declare namespace express_prom_bundle {
interface Labels {
[key: string]: string | number;
}
type NormalizePathEntry = [string | RegExp, string];
type NormalizePathFn = (req: Request, opts: Opts) => string;
type NormalizeStatusCodeFn = (res: Response) => number | string;
type TransformLabelsFn = (labels: Labels, req: Request, res: Response) => void;
interface Opts {
autoregister?: boolean;
buckets?: number[];
customLabels?: { [key: string]: any };
includeStatusCode?: boolean;
includeMethod?: boolean;
includePath?: boolean;
includeUp?: boolean;
bypass?: (req: Request) => boolean;
metricType?: 'summary' | 'histogram';
metricsPath?: string;
httpDurationMetricName?: string;
promClient?: { collectDefaultMetrics?: DefaultMetricsCollectorConfiguration };
promRegistry?: Registry;
normalizePath?: NormalizePathEntry[] | NormalizePathFn;
formatStatusCode?: NormalizeStatusCodeFn;
transformLabels?: TransformLabelsFn;
// https://github.com/disjunction/url-value-parser#options
urlValueParser?: {
minHexLength?: number;
minBase64Length?: number;
replaceMasks?: string[];
extraMasks?: string[];
};
}
interface Middleware extends RequestHandler {
metricsMiddleware: RequestHandler;
}
const normalizePath: NormalizePathFn;
const normalizeStatusCode: NormalizeStatusCodeFn;
function clusterMetrics(): RequestHandler;
}
interface express_prom_bundle {
normalizePath: express_prom_bundle.NormalizePathFn;
normalizeStatusCode: express_prom_bundle.NormalizeStatusCodeFn;
}
declare function express_prom_bundle(opts: express_prom_bundle.Opts): express_prom_bundle.Middleware;

79
types/test.ts Normal file
View File

@@ -0,0 +1,79 @@
import { Request, RequestHandler, Response } from 'express';
import * as promClient from 'prom-client';
import * as promBundle from 'express-prom-bundle';
// $ExpectType Middleware
const middleware: RequestHandler = promBundle({ includeMethod: true });
// $ExpectType: string
middleware.name;
promClient.register.clear();
// $ExpectType Middleware
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
]
}
});
promClient.register.clear();
// $ExpectType Middleware
promBundle({
buckets: [0.1, 0.4, 0.7],
includeMethod: true,
includePath: true,
customLabels: { year: null },
transformLabels: (labels: promBundle.Labels) => ({
...labels,
year: new Date().getFullYear()
}),
metricType: 'summary',
metricsPath: '/prometheus',
promClient: {
collectDefaultMetrics: {
}
},
promRegistry: new promClient.Registry(),
urlValueParser: {
minHexLength: 5,
extraMasks: [
'^[0-9]+\\.[0-9]+\\.[0-9]+$' // replace dot-separated dates with #val
]
},
normalizePath: [
['^/foo', '/example'] // replace /foo with /example
],
formatStatusCode: (res: Response) => res.statusCode + 100
});
// TypeScript workaround to write a readonly field
type Writable<T> = { -readonly [K in keyof T]: T[K] };
const wPromBundle: Writable<promBundle> = promBundle;
wPromBundle.normalizePath = (req: Request, opts: promBundle.Opts) => {
const path = promBundle.normalizePath(req, opts);
// count all docs as one path, but /docs/login as a separate one
return path.match(/^\/docs/) && !path.match(/^\/login/) ? '/docs/*' : path;
};
wPromBundle.normalizeStatusCode = (res: Response) => res.statusCode.toString();
// $ExpectType RequestHandler
promBundle.clusterMetrics();
// Missing test
// const stringReturn: string = promBundle.normalizePath({}, {});

14
types/tsconfig.json Normal file
View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": ["es6"],
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"baseUrl": ".",
"paths": { "express-prom-bundle": ["."] },
"noEmit": true,
"forceConsistentCasingInFileNames": true
}
}

3
types/tslint.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "dtslint/dtslint.json"
}