Compare commits

...

116 Commits
4.2.0 ... 6.3.5

Author SHA1 Message Date
Konstantin Pogorelov
3599c32876 bump 6.3.5 2021-03-28 21:59:44 +02:00
Konstantin Pogorelov
59ec729cb2 Merge pull request #81 from matheuslc/patch-1
Describes httpDurationMetricName option on README
2021-03-28 22:09:32 +02:00
Matheus Lucca do Carmo
c95cd1834c doc: Describes httpDurationMetricName option
Changing the name of the metric, you can use an already existent name and see your application in the same metric space.
2021-03-16 17:48:28 -03:00
Konstantin Pogorelov
d18316fee8 bump 6.3.4 2021-02-22 15:21:48 +01:00
Konstantin Pogorelov
ccb74f99b0 add an a sample with actual RegExp in advanced-example.js, relates to #80 2021-02-22 15:15:37 +01:00
Konstantin Pogorelov
2e093bc14d fix codestyle in types to keep dtslint happy, relates to #80 2021-02-22 15:15:00 +01:00
Konstantin Pogorelov
3f6b2746b9 Merge pull request #80 from thewizarodofoz/patch-1
urlValueParser extra and replace masks can be regexp
2021-02-22 14:59:11 +01:00
Oz Weiss
83ee0ce06e urlValueParser extra and replace masks can be regexp
https://github.com/disjunction/url-value-parser/blob/master/src/ValueDetector.js#L39

`replaceMasks` and `extraMasks` which are passed down to `url-value-parser` can be either string or RegExp.
2021-02-22 10:48:58 +02:00
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
Konstantin Pogorelov
66ef4e3176 bump 5.0.2 2018-12-24 12:50:44 +01:00
Konstantin Pogorelov
f55d397a31 disable notifications in travis-ci 2018-12-24 12:50:25 +01:00
Konstantin Pogorelov
68ad108f77 correct sample usage 2018-12-24 12:49:41 +01:00
Konstantin Pogorelov
6000056f7a bump 5.0.1 2018-12-24 02:26:14 +01:00
Konstantin Pogorelov
48856afbe3 add .vscode and Makefile to .npmignore 2018-12-24 02:18:48 +01:00
Konstantin Pogorelov
e035e2b991 add before_install to .travis.yml for peer dependency 2018-12-24 02:12:03 +01:00
Konstantin Pogorelov
c6d5964768 add metricPath and includeUp options, make whitelist/blacklist obsolete, simnplify code, update unit tests and code, add node10 to .travis.yml 2018-12-24 02:06:16 +01:00
Konstantin Pogorelov
94722e908b make prom-client a peer dependency, upgrade all packages, bump 5.0.0 2018-12-23 21:39:00 +01:00
Konstantin Pogorelov
00b8369329 Merge branch 'develop' 2018-12-23 18:48:10 +01:00
Konstantin Pogorelov
59221f891b bump 4.3.0 2018-12-23 18:41:02 +01:00
Konstantin Pogorelov
3a0b2caf61 upgrade prom-cliet to 11.2.1 (latest) 2018-12-23 18:40:28 +01:00
Konstantin Pogorelov
e7d004f0cc rename metricsType -> metricType, move corresponding readme block 2 paragraphs lower 2018-12-23 16:45:38 +01:00
Chen Li
0dd3116f23 Feature/add metrics type summary (#24)
* add metric type summary

* add tests for percentile option

* throw errors for unknown metricType

* set histogram as default metrics type
2018-12-23 16:36:47 +01:00
Konstantin Pogorelov
0606bf5f4f remove a duplicate cluster docs piece, versio 4.2.1 2018-08-26 14:00:25 +02:00
Konstantin Pogorelov
6054824c67 Merge branch 'master' into develop 2018-08-25 14:43:58 +02:00
Konstantin Pogorelov
01c78bcc1d Merge branch 'master' into develop 2018-08-25 13:10:39 +02:00
Konstantin Pogorelov
bc65dc45cb #16 fix typo in docs 2018-08-25 12:57:15 +02:00
Konstantin Pogorelov
8cae5b6ef3 fix jasme path (again) 2018-08-25 12:52:38 +02:00
Konstantin Pogorelov
2003e7743f #16 fix unit test (cluster_metrics/metrics_cluster), cover the error branch 2018-08-25 12:44:24 +02:00
Konstantin Pogorelov
568c87216a #16 remove unnecessary check for existance after new, break on error with 500 2018-08-25 12:42:53 +02:00
Konstantin Pogorelov
98be36244e #16 make the cluster example runnable 2018-08-25 12:40:15 +02:00
Konstantin Pogorelov
6ff1204db4 revert way jasme was called, add npm run coverage 2018-08-25 09:49:28 +02:00
Konstantin Pogorelov
f71d837660 fix newlines in index.spec.js 2018-08-25 09:45:09 +02:00
Konstantin Pogorelov
4aa2bfa6ae update package-lock.json 2018-08-24 23:48:22 +02:00
Adam Yost
1fff877787 Add support for clusterMaster option re: #16 (#17)
* Add support for clusterMaster option re: #16
* Add Cluster instructions to README
* Use the approach recommended in PR
* use console.error for errors
* Update with new method signature
* add code coverage for new clusterMetrics middleware
2018-08-24 23:36:23 +02:00
15 changed files with 1607 additions and 863 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"],

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
.npmrc
node_modules
coverage
/.vscode

View File

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

View File

@@ -1,4 +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

@@ -4,17 +4,17 @@
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
Since version 5 it uses **prom-client** as a peer dependency. See: https://github.com/siimon/prom-client
Included metrics:
* `up`: normally is just 1
* `http_request_duration_seconds`: http latency histogram labeled with `status_code`, `method` and `path`
* `http_request_duration_seconds`: http latency histogram/summary labeled with `status_code`, `method` and `path`
## Install
```
npm install express-prom-bundle
npm install prom-client express-prom-bundle
```
## Sample Usage
@@ -40,39 +40,36 @@ The order in which the routes are registered is important, since
You can use this to your advantage to bypass some of the routes.
See the example below.
## Usage with Node Cluster
``` javascript
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'); // call localhost:9999/cluster_metrics for aggregated metrics
} 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**
* **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**
* **httpDurationMetricName**: Allows you change the name of HTTP duration metric, default: **`http_request_duration_seconds`**.
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`
@@ -84,13 +81,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**
Other options:
### 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](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
@@ -232,7 +229,7 @@ if (cluster.isMaster) {
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.
while replacing all HEX values starting from 5 characters and all IP addresses in the path as #val.
```json
{
@@ -249,13 +246,12 @@ while replacing all HEX values starting from 5 characters and all emails in the
"buckets": [0.1, 1, 5],
"promClient": {
"collectDefaultMetrics": {
"timeout": 2000
}
},
"urlValueParser": {
"minHexLength": 5,
"extraMasks": [
"^[0-9]+\\.[0-9]+\\.[0-9]+$"
"^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$"
]
}
}

View File

@@ -2,6 +2,7 @@
const express = require('express');
const app = express();
const promClient = require('prom-client');
const promBundle = require('express-prom-bundle');
const bundle = promBundle({
@@ -10,15 +11,16 @@ const bundle = promBundle({
includePath: true,
customLabels: {year: null},
transformLabels: labels => Object.assign(labels, {year: new Date().getFullYear()}),
metricsPath: '/prometheus',
promClient: {
collectDefaultMetrics: {
timeout: 1000
}
},
urlValueParser: {
minHexLength: 5,
extraMasks: [
"^[0-9]+\\.[0-9]+\\.[0-9]+$" // replace dot-separated dates with #val
"^[0-9]+\\.[0-9]+\\.[0-9]+$", // replace dot-separated dates with #val, (regex as string)
/^[0-9]+\-[0-9]+\-[0-9]+$/ // replace dash-separated dates with #val (actual regex)
]
},
normalizePath: [
@@ -29,7 +31,7 @@ const bundle = promBundle({
app.use(bundle);
// native prom-client metric (no prefix)
const c1 = new bundle.promClient.Counter({name: 'c1', help: 'c1 help'});
const c1 = new promClient.Counter({name: 'c1', help: 'c1 help'});
c1.inc(10);
app.get('/foo/:id', (req, res) => {
@@ -45,11 +47,13 @@ app.delete('/foo/:id', (req, res) => {
app.get('/bar', (req, res) => res.send('bar response\n'));
app.listen(3000, () => console.info( // eslint-disable-line
'listening on 3000\n'
+ 'test in shell console\n\n'
+ 'curl localhost:3000/foo/1234\n'
+ 'curl localhost:3000/foo/09.08.2018\n'
+ 'curl -X DELETE localhost:3000/foo/5432\n'
+ 'curl localhost:3000/bar\n'
+ 'curl localhost:3000/metrics\n'
`listening on 3000
test in shell console:
curl localhost:3000/foo/1234
curl localhost:3000/foo/09.08.2018
curl -X DELETE localhost:3000/foo/5432
curl localhost:3000/bar
curl localhost:3000/prometheus
`
));

1665
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": "4.2.0",
"version": "6.3.5",
"description": "express middleware with popular prometheus metrics in one bundle",
"main": "src/index.js",
"keywords": [
@@ -10,33 +10,47 @@
"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",
"dependencies": {
"on-finished": "^2.3.0",
"prom-client": "~11.1.1",
"url-value-parser": "^2.0.0"
},
"devDependencies": {
"@types/express": "^4.16.1",
"coveralls": "^3.0.2",
"eslint": "^5.3.0",
"express": "^4.16.3",
"dtslint": "^0.7.1",
"eslint": "^5.11.0",
"express": "^4.16.4",
"istanbul": "^0.4.5",
"jasme": "^5.2.0",
"koa": "^2.5.2",
"koa-connect": "^2.0.0",
"supertest": "^3.0.0",
"supertest-koa-agent": "^0.3.0"
"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",
"typescript": "^3.4.5"
},
"peerDependencies": {
"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

@@ -18,7 +18,7 @@ describe('index', () => {
it('metrics returns up=1', done => {
const app = express();
const bundled = bundle({
whitelist: ['up']
excludeRoutes: ['/irrelevant', /at.all/]
});
app.use(bundled);
app.use('/test', (req, res) => res.send('it worked'));
@@ -35,6 +35,64 @@ describe('index', () => {
});
});
it('"up"-metric can be excluded', done => {
const app = express();
const bundled = bundle({
includeUp: false
});
app.use(bundled);
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).not.toMatch(/up\s1/);
done();
});
});
});
it('metrics path can be defined with a regex', done => {
const app = express();
const bundled = bundle({
metricsPath: /^\/prometheus$/
});
app.use(bundled);
app.use('/test', (req, res) => res.send('it worked'));
const agent = supertest(app);
agent.get('/test').end(() => {
agent
.get('/prometheus')
.end((err, res) => {
expect(res.status).toBe(200);
expect(res.text).toMatch(/up\s1/);
done();
});
});
});
it('metrics path can be defined as regexp', done => {
const app = express();
const bundled = bundle();
app.use(bundled);
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(/up\s1/);
done();
});
});
});
it('httpDurationMetricName overrides histogram metric name', done => {
const app = express();
const bundled = bundle({
@@ -52,9 +110,7 @@ describe('index', () => {
it('metrics should be attached to /metrics by default', done => {
const app = express();
const bundled = bundle({
whitelist: ['up']
});
const bundled = bundle();
app.use(bundled);
const agent = supertest(app);
@@ -83,27 +139,6 @@ describe('index', () => {
});
});
it('metrics can be filtered using exect match', () => {
const instance = bundle({blacklist: ['up']});
expect(instance.metrics.up).not.toBeDefined();
expect(instance.metrics.http_request_duration_seconds).toBeDefined();
});
it('metrics can be filtered using regex', () => {
const instance = bundle({blacklist: [/http/]});
expect(instance.metrics.up).toBeDefined();
expect(instance.metrics.http_request_duration_seconds).not.toBeDefined();
});
it('metrics can be whitelisted', () => {
const instance = bundle({whitelist: [/^up$/]});
expect(instance.metrics.up).toBeDefined();
expect(instance.metrics.nodejs_memory_heap_total_bytes).not.toBeDefined();
expect(instance.metrics.http_request_duration_seconds).not.toBeDefined();
});
it('throws on both white and blacklist', () => {
expect(() => {
bundle({whitelist: [/up/], blacklist: [/up/]});
}).toThrow();
});
it('returns error 500 on incorrect middleware usage', done => {
const app = express();
app.use(bundle);
@@ -140,22 +175,67 @@ describe('index', () => {
it('filters out the excludeRoutes', done => {
const app = express();
const instance = bundle({
excludeRoutes: ['/test']
excludeRoutes: ['/test', /bad.word/]
});
app.use(instance);
app.use('/test', (req, res) => res.send('it worked'));
app.use('/some/bad-word', (req, res) => res.send('it worked too'));
const agent = supertest(app);
agent
.get('/test')
.end(() => {
const metricHashMap = instance.metrics.http_request_duration_seconds.hashMap;
expect(metricHashMap['status_code:200']).not.toBeDefined();
agent
.get('/metrics')
.end((err, res) => {
expect(res.status).toBe(200);
done();
.get('/some/bad-word')
.end(() => {
const metricHashMap = instance.metrics.http_request_duration_seconds.hashMap;
expect(metricHashMap['status_code:200']).not.toBeDefined();
agent
.get('/metrics')
.end((err, res) => {
expect(res.status).toBe(200);
done();
});
});
});
});
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();
});
});
});
});
});
@@ -186,6 +266,33 @@ describe('index', () => {
});
});
it('metric type histogram works', done => {
const app = express();
const bundled = bundle({
metricType: 'histogram',
buckets: [10, 100],
});
app.use(bundled);
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(/le="100"/);
done();
});
});
});
it('throws on unknown metricType ', () => {
expect(() => {
bundle({metricType: 'hello'});
}).toThrow();
});
describe('usage of normalizePath()', () => {
it('normalizePath can be replaced gloablly', done => {
@@ -302,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({
@@ -352,9 +480,7 @@ describe('index', () => {
it('Koa: metrics returns up=1', done => {
const app = new Koa();
const bundled = bundle({
whitelist: ['up']
});
const bundled = bundle();
app.use(c2k(bundled));
app.use(function(ctx, next) {
@@ -378,11 +504,10 @@ describe('index', () => {
bundle({
promClient: {
collectDefaultMetrics: {
timeout: 3000
}
}
});
expect(spy).toHaveBeenCalledWith({timeout: 3000});
expect(spy).toHaveBeenCalledWith({});
});
describe('usage of clusterMetrics()', () => {
@@ -407,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
@@ -420,4 +548,47 @@ describe('index', () => {
});
}, 6000);
});
describe('metricType: summary', () => {
it('metric type summary works', done => {
const app = express();
const bundled = bundle({
metricType: 'summary'
});
app.use(bundled);
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(/quantile="0.98"/);
done();
});
});
});
it('custom pecentiles work', done => {
const app = express();
const bundled = bundle({
metricType: 'summary',
percentiles: [0.5, 0.85, 0.99],
});
app.use(bundled);
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(/quantile="0.85"/);
done();
});
});
});
});
});

View File

@@ -1,11 +1,10 @@
'use strict';
const onFinished = require('on-finished');
const promClient = require('prom-client');
const normalizePath = require('./normalizePath');
const normalizeStatusCode = require('./normalizeStatusCode');
function matchVsRegExps(element, regexps) {
for (let regexp of regexps) {
for (const regexp of regexps) {
if (regexp instanceof RegExp) {
if (element.match(regexp)) {
return true;
@@ -17,55 +16,41 @@ function matchVsRegExps(element, regexps) {
return false;
}
function filterArrayByRegExps(array, regexps) {
return array.filter(element => {
return matchVsRegExps(element, regexps);
});
}
function prepareMetricNames(opts, metricTemplates) {
const names = Object.keys(metricTemplates);
if (opts.whitelist) {
if (opts.blacklist) {
throw new Error('you cannot have whitelist and blacklist at the same time');
}
return filterArrayByRegExps(names, opts.whitelist);
}
if (opts.blacklist) {
const blacklisted = filterArrayByRegExps(names, opts.blacklist);
return names.filter(name => blacklisted.indexOf(name) === -1);
}
return names;
}
function clusterMetrics() {
const aggregatorRegistry = new promClient.AggregatorRegistry();
const aggregatorRegistry = new promClient.AggregatorRegistry();
const metricsMiddleware = function(req, res, next) {
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) {
opts = Object.assign(
{
autoregister: true,
includeStatusCode: true,
normalizePath: main.normalizePath,
formatStatusCode: main.normalizeStatusCode,
promClient: {}
},
opts
);
if (arguments[2] && arguments[1] && arguments[1].send) {
arguments[1].status(500)
.send('<h1>500 Error</h1>\n'
@@ -75,12 +60,28 @@ function main(opts) {
return;
}
if (opts.prefix || opts.keepDefaultMetrics !== undefined) {
opts = Object.assign(
{
autoregister: true,
includeStatusCode: true,
normalizePath: main.normalizePath,
formatStatusCode: main.normalizeStatusCode,
metricType: 'histogram',
promClient: {},
promRegistry: promClient.register
}, opts
);
if (opts.prefix
|| opts.keepDefaultMetrics !== undefined
|| opts.whitelist !== undefined
|| opts.blacklist !== undefined
) {
throw new Error(
'express-prom-bundle detected obsolete options:'
+ 'prefix and/or keepDefaultMetrics. '
'express-prom-bundle detected one of the obsolete options: '
+ 'prefix, keepDefaultMetrics, whitelist, blacklist. '
+ 'Please refer to oficial docs. '
+ 'Most likely you upgraded the module without necessary code changes'
+ 'Most likely you upgraded the module without the necessary code changes'
);
}
@@ -90,96 +91,128 @@ function main(opts) {
const httpMetricName = opts.httpDurationMetricName || 'http_request_duration_seconds';
const metricTemplates = {
'up': () => new promClient.Gauge({
name: 'up',
help: '1 = up, 0 = not up'
}),
[httpMetricName]: () => {
const labels = ['status_code'];
if (opts.includeMethod) {
labels.push('method');
}
if (opts.includePath) {
labels.push('path');
}
if (opts.customLabels){
labels.push.apply(labels, Object.keys(opts.customLabels));
}
const metric = new promClient.Histogram({
function makeHttpMetric() {
const labels = ['status_code'];
if (opts.includeMethod) {
labels.push('method');
}
if (opts.includePath) {
labels.push('path');
}
if (opts.customLabels) {
labels.push.apply(labels, Object.keys(opts.customLabels));
}
if (opts.metricType === 'summary') {
return new promClient.Summary({
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],
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]
});
return metric;
} else {
throw new Error('metricType option must be histogram or summary');
}
};
const metrics = {};
const names = prepareMetricNames(opts, metricTemplates);
for (let name of names) {
metrics[name] = metricTemplates[name]();
}
if (metrics.up) {
const metrics = {
[httpMetricName]: makeHttpMetric()
};
if (opts.includeUp !== false) {
metrics.up = new promClient.Gauge({
name: '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
: new RegExp('^' + (opts.metricsPath || '/metrics') + '/?$');
const middleware = function (req, res, next) {
const path = req.originalUrl || req.url; // originalUrl gets lost in koa-connect?
let labels;
if (opts.autoregister && path.match(/^\/metrics\/?$/)) {
return metricsMiddleware(req, res);
if (opts.autoregister && path.match(metricsMatch)) {
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)) {
return next();
}
if (metrics[httpMetricName]) {
labels = {};
let timer = metrics[httpMetricName].startTimer(labels);
onFinished(res, () => {
if (opts.includeStatusCode) {
labels.status_code = opts.formatStatusCode(res, opts);
}
if (opts.includeMethod) {
labels.method = req.method;
}
if (opts.includePath) {
labels.path = typeof opts.normalizePath == 'function'
? opts.normalizePath(req, opts)
: main.normalizePath(req, opts);
}
if (opts.customLabels) {
Object.assign(labels, opts.customLabels);
}
if (opts.transformLabels) {
opts.transformLabels(labels, req, res);
}
timer();
});
}
const labels = {};
const timer = metrics[httpMetricName].startTimer(labels);
onFinished(res, () => {
if (opts.includeStatusCode) {
labels.status_code = opts.formatStatusCode(res, opts);
}
if (opts.includeMethod) {
labels.method = req.method;
}
if (opts.includePath) {
labels.path = opts.normalizePath instanceof Function
? opts.normalizePath(req, opts)
: main.normalizePath(req, opts);
}
if (opts.customLabels) {
Object.assign(labels, opts.customLabels);
}
if (opts.transformLabels) {
opts.transformLabels(labels, req, res);
}
timer();
});
next();
};
middleware.metricTemplates = metricTemplates;
middleware.metrics = metrics;
middleware.promClient = promClient;
middleware.metricsMiddleware = metricsMiddleware;
return middleware;
}
// this is kept only for compatibility with the code relying on older version
main.promClient = promClient;
main.normalizePath = normalizePath;
main.normalizeStatusCode = normalizeStatusCode;
main.clusterMetrics = clusterMetrics;

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?: Array<RegExp | string>;
extraMasks?: Array<RegExp | 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"
}