Compare commits

...

282 Commits
1.1.7 ... dev

Author SHA1 Message Date
Félix MARQUET
dcc9e1f893 chore(deps): Update qs to fix security issue 2026-01-04 09:18:26 +00:00
Félix MARQUET
36d1484d33 feat(security): Add security policy documentation 2025-12-17 09:01:43 +00:00
Félix MARQUET
8fc61cd1d8 chore(version): bump version to 8.0.7 2025-12-17 08:55:50 +00:00
Félix MARQUET
4fd7a797c5 feat(action): Add regular github action for audit security vulnerability 2025-12-17 08:53:44 +00:00
Félix MARQUET
30d9c3d473 Merge pull request #21 from BreizhHardware/dependabot/npm_and_yarn/dev/eslint/js-9.39.2
chore(deps-dev): bump @eslint/js from 9.39.1 to 9.39.2
2025-12-17 09:33:16 +01:00
Félix MARQUET
10fe1cac8f Merge pull request #22 from BreizhHardware/dependabot/npm_and_yarn/dev/eslint-9.39.2
chore(deps-dev): bump eslint from 9.39.1 to 9.39.2
2025-12-17 09:32:50 +01:00
dependabot[bot]
9b4e0eb163 chore(deps-dev): bump eslint from 9.39.1 to 9.39.2
Bumps [eslint](https://github.com/eslint/eslint) from 9.39.1 to 9.39.2.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.39.1...v9.39.2)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.39.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 15:37:01 +00:00
dependabot[bot]
475b7a8f6d chore(deps-dev): bump @eslint/js from 9.39.1 to 9.39.2
Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.39.1 to 9.39.2.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.39.2/packages/js)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.39.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 15:36:51 +00:00
Félix MARQUET
c1482220c2 Merge pull request #20 from BreizhHardware/dependabot/npm_and_yarn/dev/multi-b251156d90
chore(deps): bump express and @types/express
2025-12-15 08:51:57 +01:00
dependabot[bot]
66643411b8 chore(deps): bump express and @types/express
Bumps [express](https://github.com/expressjs/express) and [@types/express](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/express). These dependencies needed to be updated together.

Updates `express` from 5.2.0 to 5.2.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/v5.2.0...v5.2.1)

Updates `@types/express` from 5.0.5 to 5.0.6
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/express)

---
updated-dependencies:
- dependency-name: express
  dependency-version: 5.2.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
- dependency-name: "@types/express"
  dependency-version: 5.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 15:41:39 +00:00
Félix MARQUET
97b2f425e6 chore: bump version to 8.0.6 2025-12-03 13:08:29 +00:00
Félix MARQUET
a8d4fa8116 Merge pull request #16 from BreizhHardware/dependabot/npm_and_yarn/dev/eslint/eslintrc-3.3.3
chore(deps-dev): bump @eslint/eslintrc from 3.3.1 to 3.3.3
2025-12-03 14:04:57 +01:00
Félix MARQUET
0eb0178622 chore(deps-dev): update body-parser to 2.2.1 2025-12-03 13:02:08 +00:00
Félix MARQUET
5c1f68a482 Merge pull request #17 from BreizhHardware/dependabot/npm_and_yarn/dev/express-5.2.0
chore(deps-dev): bump express from 5.1.0 to 5.2.0
2025-12-03 13:58:46 +01:00
dependabot[bot]
12ff62478f chore(deps-dev): bump express from 5.1.0 to 5.2.0
Bumps [express](https://github.com/expressjs/express) from 5.1.0 to 5.2.0.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/v5.1.0...v5.2.0)

---
updated-dependencies:
- dependency-name: express
  dependency-version: 5.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 17:05:50 +00:00
dependabot[bot]
2efc5189d0 chore(deps-dev): bump @eslint/eslintrc from 3.3.1 to 3.3.3
Bumps [@eslint/eslintrc](https://github.com/eslint/eslintrc) from 3.3.1 to 3.3.3.
- [Release notes](https://github.com/eslint/eslintrc/releases)
- [Changelog](https://github.com/eslint/eslintrc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslintrc/compare/v3.3.1...eslintrc-v3.3.3)

---
updated-dependencies:
- dependency-name: "@eslint/eslintrc"
  dependency-version: 3.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 17:05:40 +00:00
Félix MARQUET
c28b736c6e Merge pull request #15 from BreizhHardware/dependabot/github_actions/dot-github/workflows/dev/actions/checkout-6
chore(deps): bump actions/checkout from 5 to 6 in /.github/workflows
2025-11-27 15:01:25 +01:00
dependabot[bot]
e84b96bf5e chore(deps): bump actions/checkout from 5 to 6 in /.github/workflows
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 18:00:38 +00:00
Félix MARQUET
e49f95ab0e chore: bump to version to 8.0.5 2025-11-18 09:57:23 +00:00
Félix MARQUET
cf862fca31 docs: update package name in readme 2025-11-18 08:52:45 +01:00
Félix MARQUET
0f6eab8d98 Merge pull request #11 from BreizhHardware/docs/update-readme-badge
docs: update README badges and add coverage upload step in release workflow
2025-11-18 08:46:03 +01:00
Félix MARQUET
b586bd9515 fix: Fix security issue in js-yaml 2025-11-18 07:43:48 +00:00
Félix MARQUET
1a5f6df7f9 docs: update README badges and add coverage upload step in release workflow 2025-11-17 13:55:47 +00:00
Félix MARQUET
b2f5283303 Merge pull request #8 from BreizhHardware/dependabot/npm_and_yarn/dev/eslint-9.39.1
Bump eslint from 9.39.0 to 9.39.1
2025-11-13 16:18:27 +01:00
dependabot[bot]
cdc558a8d8 Bump eslint from 9.39.0 to 9.39.1
Bumps [eslint](https://github.com/eslint/eslint) from 9.39.0 to 9.39.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.39.0...v9.39.1)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.39.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-13 15:17:03 +00:00
Félix MARQUET
af8a3097fc Merge pull request #7 from BreizhHardware/dependabot/npm_and_yarn/dev/eslint/js-9.39.1
Bump @eslint/js from 9.39.0 to 9.39.1
2025-11-13 16:15:35 +01:00
Félix MARQUET
c5cce9f8f5 Merge pull request #1 from BreizhHardware/dependabot/npm_and_yarn/tsd-0.33.0
Bump tsd from 0.30.7 to 0.33.0
2025-11-13 16:15:22 +01:00
Félix MARQUET
36789b54ae fix: bump version to 8.0.4 2025-11-13 15:14:21 +00:00
dependabot[bot]
e1b09bca43 Bump tsd from 0.30.7 to 0.33.0
Bumps [tsd](https://github.com/tsdjs/tsd) from 0.30.7 to 0.33.0.
- [Release notes](https://github.com/tsdjs/tsd/releases)
- [Commits](https://github.com/tsdjs/tsd/compare/v0.30.7...v0.33.0)

---
updated-dependencies:
- dependency-name: tsd
  dependency-version: 0.33.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-13 15:11:20 +00:00
dependabot[bot]
c3284c6ce6 Bump @eslint/js from 9.39.0 to 9.39.1
Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.39.0 to 9.39.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.39.1/packages/js)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.39.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-13 15:11:16 +00:00
Félix MARQUET
8dc2f9c171 feat: add a release workflow 2025-11-13 15:06:18 +00:00
Félix MARQUET
a9eb496d11 Merge pull request #2 from BreizhHardware/dependabot/npm_and_yarn/typescript-5.9.3
Bump typescript from 3.9.10 to 5.9.3
2025-11-13 15:57:41 +01:00
Félix MARQUET
3353686379 Merge pull request #3 from BreizhHardware/dependabot/npm_and_yarn/supertest-7.1.4
Bump supertest from 3.4.2 to 7.1.4
2025-11-13 15:57:06 +01:00
Félix MARQUET
489ce17366 Merge pull request #4 from BreizhHardware/dependabot/npm_and_yarn/eslint-9.39.0
Bump eslint from 9.38.0 to 9.39.0
2025-11-13 15:56:09 +01:00
Félix MARQUET
4e07a7db7a Merge pull request #5 from BreizhHardware/dependabot/npm_and_yarn/globals-16.5.0
Bump globals from 16.4.0 to 16.5.0
2025-11-13 15:54:31 +01:00
dependabot[bot]
59f6ac0afa Bump globals from 16.4.0 to 16.5.0
Bumps [globals](https://github.com/sindresorhus/globals) from 16.4.0 to 16.5.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v16.4.0...v16.5.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-version: 16.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-03 16:42:43 +00:00
dependabot[bot]
597fdda556 Bump eslint from 9.38.0 to 9.39.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.38.0 to 9.39.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.38.0...v9.39.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.39.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-03 16:42:35 +00:00
dependabot[bot]
21e90237e4 Bump supertest from 3.4.2 to 7.1.4
Bumps [supertest](https://github.com/ladjs/supertest) from 3.4.2 to 7.1.4.
- [Release notes](https://github.com/ladjs/supertest/releases)
- [Commits](https://github.com/ladjs/supertest/compare/v3.4.2...v7.1.4)

---
updated-dependencies:
- dependency-name: supertest
  dependency-version: 7.1.4
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-31 10:03:37 +00:00
dependabot[bot]
2de73f7526 Bump typescript from 3.9.10 to 5.9.3
Bumps [typescript](https://github.com/microsoft/TypeScript) from 3.9.10 to 5.9.3.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v3.9.10...v5.9.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 5.9.3
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-31 10:03:29 +00:00
Félix MARQUET
e886d7cb05 chore: add dependabot configuration and CI workflow for tests 2025-10-31 10:02:19 +00:00
Félix MARQUET
815ba9819a chore: clean package-lock 2025-10-29 13:28:17 +00:00
Félix MARQUET
f7805d301b chore: remove Istanbul and add NYC for code coverage in package.json 2025-10-29 13:27:45 +00:00
Félix MARQUET
6b23a109b1 chore: update koa dependency to version 3.0.1 2025-10-29 13:24:22 +00:00
Félix MARQUET
39788a1ff0 chore: update express dependency to version 5.1.0 2025-10-29 13:23:33 +00:00
Félix MARQUET
f7474e3ea7 chore: bump version to 8.0.3 and update dependencies
- Updated version from 8.0.2 to 8.0.3 in package.json
- Upgraded eslint from 5.11.0 to 9.38.0
- Added @eslint/eslintrc and @eslint/js as devDependencies
- Added globals as a devDependency
2025-10-29 13:21:53 +00:00
Félix MARQUET
6da4bece87 feat: update package metadata and version to 8.0.2 2025-10-29 13:18:11 +00:00
Félix MARQUET
522e9ad64d bump version to 8.0.1 2025-10-29 12:55:01 +00:00
Félix MARQUET
db8710d5d0 feat: add upMetricName params to allow for up metric name customization 2025-04-25 06:47:26 +00:00
Konstantin Pogorelov
f9a0a7622a bump version to 8.0.0, update express and @types/express to 5 2024-10-12 15:42:42 +02:00
Konstantin Pogorelov
fd33d98c15 bump v7.0.2 2024-10-12 13:54:58 +02:00
Konstantin Pogorelov
5978ea7b73 Merge remote-tracking branch 'remotes/origin/dependabot/npm_and_yarn/multi-092c445592' 2024-10-12 13:51:57 +02:00
Konstantin Pogorelov
71467e6a17 Merge remote-tracking branch 'remotes/origin/dependabot/npm_and_yarn/multi-6b7e5c81f3' 2024-10-12 13:51:45 +02:00
Konstantin Pogorelov
c6b24f6eca Merge remote-tracking branch 'remotes/origin/dependabot/npm_and_yarn/multi-9f37c16f8f' 2024-10-12 13:51:31 +02:00
Konstantin Pogorelov
1ac5fba5c4 Merge remote-tracking branch 'remotes/origin/dependabot/npm_and_yarn/multi-41ee8087b2' 2024-10-12 13:51:11 +02:00
dependabot[bot]
ca8b0ba1e0 Bump body-parser and express
Bumps [body-parser](https://github.com/expressjs/body-parser) to 1.20.3 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together.


Updates `body-parser` from 1.20.2 to 1.20.3
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.20.2...1.20.3)

Updates `express` from 4.19.2 to 4.21.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.1)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-type: indirect
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-12 11:48:55 +00:00
dependabot[bot]
2ebc2618de Bump path-to-regexp and express
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) to 0.1.10 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together.


Updates `path-to-regexp` from 0.1.7 to 0.1.10
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.7...v0.1.10)

Updates `express` from 4.19.2 to 4.21.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.1)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-type: indirect
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-12 11:48:51 +00:00
dependabot[bot]
df46ecaa9a Bump cookie and express
Bumps [cookie](https://github.com/jshttp/cookie) to 0.7.1 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together.


Updates `cookie` from 0.6.0 to 0.7.1
- [Release notes](https://github.com/jshttp/cookie/releases)
- [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.1)

Updates `express` from 4.19.2 to 4.21.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.1)

---
updated-dependencies:
- dependency-name: cookie
  dependency-type: indirect
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-12 11:48:01 +00:00
dependabot[bot]
abdfe2d93a Bump serve-static and express
Bumps [serve-static](https://github.com/expressjs/serve-static) to 1.16.2 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together.


Updates `serve-static` from 1.15.0 to 1.16.2
- [Release notes](https://github.com/expressjs/serve-static/releases)
- [Changelog](https://github.com/expressjs/serve-static/blob/v1.16.2/HISTORY.md)
- [Commits](https://github.com/expressjs/serve-static/compare/v1.15.0...v1.16.2)

Updates `express` from 4.19.2 to 4.21.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.1)

---
updated-dependencies:
- dependency-name: serve-static
  dependency-type: indirect
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-12 11:48:00 +00:00
dependabot[bot]
33ab388106 Bump send and express
Bumps [send](https://github.com/pillarjs/send) to 0.19.0 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together.


Updates `send` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0)

Updates `express` from 4.19.2 to 4.21.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.1)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-12 11:47:58 +00:00
Konstantin Pogorelov
02865e531d bump v7.0.1 2024-10-12 13:44:03 +02:00
Konstantin Pogorelov
1a1d8e0b54 Merge pull request #126 from jochen-schweizer/dependabot/npm_and_yarn/express-4.19.2
Bump express from 4.18.2 to 4.19.2
2024-10-12 13:38:00 +02:00
Konstantin Pogorelov
8371c551d5 Merge pull request #127 from jochen-schweizer/dependabot/npm_and_yarn/braces-3.0.3
Bump braces from 3.0.2 to 3.0.3
2024-10-12 13:37:44 +02:00
dependabot[bot]
10a58635e1 Bump braces from 3.0.2 to 3.0.3
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-21 06:23:20 +00:00
dependabot[bot]
5f44228f69 Bump express from 4.18.2 to 4.19.2
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-29 15:28:24 +00:00
Timm Stelzer
fd5ff1cfe0 feat: respect pruneAgedBuckets, update README 2024-01-06 11:31:26 +01:00
Konstantin Pogorelov
edfb9992ed update README, add .env to gitignore 2024-01-06 11:17:51 +01:00
Konstantin Pogorelov
0ce44722a5 use collectDefaultMetrics.prefix for up metric 2024-01-06 10:48:57 +01:00
Konstantin Pogorelov
407ea4b0d7 fix unittest for prom-client 15 2024-01-06 10:26:33 +01:00
Steve
63b6d89caa Update prom-client to v15 2024-01-05 23:58:13 +01:00
Konstantin Pogorelov
5e2b284903 Merge pull request #102 from Cellaryllis/normalize-path-example-3
Add another example for normalizePath
2024-01-05 23:46:43 +01:00
Konstantin Pogorelov
c693affcac Merge pull request #120 from petuomin/statuscode-for-request-closed-prematurely
Use statuscode 499 for requests that are closed before response is sent
2024-01-05 23:45:10 +01:00
Konstantin Pogorelov
a0eef5d0e2 switch from dtslint to tsd, update required node to >=18, bump version to 7.0.0 2024-01-05 22:04:31 +01:00
Pasi Tuominen
efbab7dcdb Use statuscode 499 for requests that are closed before response is sent
This happens, for example, when a http proxy in front of the application is configured with a timeout and the node server is too slow to respond.

Currently such timeouts are counted as 200s by express-prom-bundle. This PR changes that to 499 "Client Closed Request". This way it's possible to tell them apart.
2023-05-05 14:49:02 +03:00
Konstantin Pogorelov
0bda9934c1 bump 6.6.0 2022-12-15 09:18:12 +01:00
Konstantin Pogorelov
5bf6b153e6 Merge pull request #110 from ineentho/bypass-on-finish
Add new option bypassOnFinish, for when response is required in bypass callback
2022-12-15 09:14:12 +01:00
Henrik Karlsson
1171fb5be1 Combined bypass and bypassOnFinish into one option 2022-12-14 17:02:54 +01:00
Henrik Karlsson
ea9f34aa3e Add new option bypassOnFinish, for when response is required in bypass callback 2022-12-09 17:45:27 +01:00
Cellaryllis
c541824657 Move details below example 2 2022-07-12 10:18:11 +01:00
Cellaryllis
7eae5d4b7f Add another example for normalizePath 2022-07-12 10:15:47 +01:00
Konstantin Pogorelov
93b0e8fafe bump 6.5.0 2022-06-18 12:24:50 +02:00
Konstantin Pogorelov
2d2b253567 minor codestyle fixes, turn async/await in metricsApp into a conventional promise 2022-06-18 12:23:59 +02:00
Konstantin Pogorelov
1b52c6f46e Merge pull request #101 from derangeddk/add-metricsapp-option
Add metricsapp option
2022-06-18 12:08:49 +02:00
Asbjørn Dyhrberg Thegler
2293d1cd40 Remove exclusive test running
Silly me
2022-06-17 21:19:53 +02:00
Asbjørn Dyhrberg Thegler
1ee094eb04 Add jasmine test 2022-06-17 21:01:14 +02:00
Asbjørn Dyhrberg Thegler
a1284ec3e5 Add test for typings 2022-06-17 21:01:03 +02:00
Asbjørn Dyhrberg Thegler
5a1efd7fe6 Add typings 2022-06-17 20:42:35 +02:00
Asbjørn Dyhrberg Thegler
bcac9f523a Add option to README 2022-06-17 13:42:54 +02:00
Asbjørn Dyhrberg Thegler
c4be86b651 Add metricsApp option
Lets you supply an additional express app on which to mount the metrics 
endpoint
2022-06-17 13:41:36 +02:00
Konstantin Pogorelov
5e5a47a500 Merge pull request #97 from diegoximenes/fix/cluster_example
Fix cluster example
2021-10-26 09:23:45 +02:00
Diego Ximenes
426e4d4556 Fix cluster example 2021-10-26 01:47:19 -03:00
Konstantin Pogorelov
78ebb06a6d bump 6.4.1 2021-08-17 10:49:06 +03:00
Konstantin Pogorelov
c6d2210062 add urlPathReplacement to types, see #75 2021-08-17 10:37:12 +03:00
Konstantin Pogorelov
c436fa3c88 bump 6.4.0 2021-08-17 10:11:59 +03:00
Konstantin Pogorelov
77b02c7302 add README docs for urlPathReplacement, see #75 2021-08-17 10:11:10 +03:00
Konstantin Pogorelov
068ed93a61 Merge pull request #87 from prabhumarappan/add-url-value-replacement
Added replacement option for path
2021-08-17 10:03:20 +03:00
Konstantin Pogorelov
c5aa3f7388 Merge pull request #89 from jochen-schweizer/dependabot/npm_and_yarn/path-parse-1.0.7
Bump path-parse from 1.0.6 to 1.0.7
2021-08-17 10:01:56 +03:00
Konstantin Pogorelov
ca28ff4fe1 Merge pull request #85 from jochen-schweizer/dependabot/npm_and_yarn/lodash-4.17.21
Bump lodash from 4.17.19 to 4.17.21
2021-08-17 10:01:10 +03:00
Konstantin Pogorelov
52f47ed9f4 Merge pull request #84 from jochen-schweizer/dependabot/npm_and_yarn/handlebars-4.7.7
Bump handlebars from 4.7.6 to 4.7.7
2021-08-17 10:00:53 +03:00
Konstantin Pogorelov
a60996ebc3 Merge pull request #93 from RyanBard/update_types_with_exclude_routes
closes #92: Add excludeRoutes to types
2021-08-16 08:58:00 +03:00
Ryan Bard
e7c94ff307 closes #92: Add excludeRoutes to types
Signed-off-by: Ryan Bard <john.ryan.bard@gmail.com>
2021-08-15 20:04:26 -04:00
dependabot[bot]
884617bffe Bump path-parse from 1.0.6 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-10 17:34:10 +00:00
Prabhu Marappan
32b76d0970 Added replacement option for path 2021-06-06 16:51:53 +05:30
dependabot[bot]
845cd6ee46 Bump lodash from 4.17.19 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-06 21:16:33 +00:00
dependabot[bot]
0c3c949425 Bump handlebars from 4.7.6 to 4.7.7
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.7.6 to 4.7.7.
- [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.7.6...v4.7.7)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-06 17:31:28 +00:00
Konstantin Pogorelov
1ec800cde7 bump 6.3.6 2021-04-03 10:46:38 +02:00
Konstantin Pogorelov
661e7d5092 add types for metricType: summary 2021-04-03 10:39:46 +02:00
Konstantin Pogorelov
b569a71130 Merge pull request #83 from jochen-schweizer/dependabot/npm_and_yarn/y18n-4.0.1
Bump y18n from 4.0.0 to 4.0.1
2021-04-03 10:50:23 +02:00
dependabot[bot]
10c372c438 Bump y18n from 4.0.0 to 4.0.1
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-30 15:50:49 +00:00
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
ec9835270f version 4.2.0 2018-08-25 13:11:31 +02:00
Konstantin Pogorelov
01c78bcc1d Merge branch 'master' into develop 2018-08-25 13:10:39 +02:00
Konstantin Pogorelov
1cf8c86acb Squashed commit of the following:
commit bc65dc45cb
Author: Konstantin Pogorelov <or@pluseq.com>
Date:   Sat Aug 25 12:57:15 2018 +0200

    #16 fix typo in docs

commit 2003e7743f
Author: Konstantin Pogorelov <or@pluseq.com>
Date:   Sat Aug 25 12:44:24 2018 +0200

    #16 fix unit test (cluster_metrics/metrics_cluster), cover the error branch

commit 568c87216a
Author: Konstantin Pogorelov <or@pluseq.com>
Date:   Sat Aug 25 12:42:53 2018 +0200

    #16 remove unnecessary check for existance after new, break on error with 500

commit 98be36244e
Author: Konstantin Pogorelov <or@pluseq.com>
Date:   Sat Aug 25 12:40:15 2018 +0200

    #16 make the cluster example runnable
2018-08-25 13:07:36 +02:00
Konstantin Pogorelov
43b51d4ab1 fix jasme path (again) 2018-08-25 12:58:44 +02:00
Konstantin Pogorelov
f6e87b7697 revert way jasme was called, add npm run coverage 2018-08-25 12:58:34 +02:00
Konstantin Pogorelov
3be8d53a27 fix newlines in index.spec.js 2018-08-25 12:58:24 +02:00
Konstantin Pogorelov
34e6a21be1 update package-lock.json 2018-08-25 12:58:20 +02:00
Adam Yost
02fcda4721 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-25 12:58:09 +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
Konstantin Pogorelov
5e0cd75673 add normalizePath option as tuple array, improve docs and advanced example, version to 4.1.0 2018-08-09 11:27:04 +02:00
Konstantin Pogorelov
d292dcab33 remove changelog from readme as it duplicates github releases function 2018-08-08 12:00:29 +02:00
Konstantin Pogorelov
84f99cc49c add urlValueParser option, update docs accordingly 2018-08-08 11:23:27 +02:00
Konstantin Pogorelov
fffe35ce5e update dependencies, use url-value-parser v2 2018-08-08 11:22:03 +02:00
Konstantin Pogorelov
06341c227a wip: 4.0.0, prom-client to v11, url-value-parser to v2 2018-08-07 17:55:25 +02:00
Konstantin Pogorelov
45b8f373be include collectDefaultMetrics in kraken example 2018-01-26 17:40:05 +01:00
Konstantin Pogorelov
4ee269faee make test more specific 2018-01-23 16:58:49 +01:00
Konstantin Pogorelov
e4d6113ff2 replace node 7 with node 8 in .travis.yml 2018-01-23 16:52:12 +01:00
Konstantin Pogorelov
06f55c9ab8 add promClient option for smuggling collectDefaultMetrics, upgrade prom-client to ~10.2.2, version 3.3.0 2018-01-23 16:43:38 +01:00
Konstantin Pogorelov
c8996a7730 version 3.2.1 2017-10-19 20:26:56 +02:00
Konstantin Pogorelov
aed7edc684 remove fixed version of prom-client from readme 2017-10-19 20:25:40 +02:00
Muhammad Saiful Islam
4a840cfce6 minor typo (#13) 2017-10-19 20:23:36 +02:00
Konstantin Pogorelov
d12248dcaf upgrade prom-client, update package-lock.json 2017-09-23 12:31:12 +02:00
Konstantin Pogorelov
68eb617f88 version 3.2.0 2017-09-23 12:20:12 +02:00
Konstantin Pogorelov
bb0e453078 extends docs in regards of: customLabels, transformLabels, httpDurationMetricName 2017-09-23 12:19:56 +02:00
Konstantin Pogorelov
b0928b1b94 make template name consistent with httpDurationMetricName, fix typo in variable name 2017-09-23 12:18:00 +02:00
Konstantin Pogorelov
26f9f5ca10 add customLabels and transformLabels to advanced example 2017-09-23 12:13:14 +02:00
Konstantin Pogorelov
a261ab76a3 split includeCustomLabels into customLabels and transformLabels 2017-09-23 12:03:23 +02:00
Konstantin Pogorelov
8318e0ec1d fix typo 2017-09-23 11:49:13 +02:00
Konstantin Pogorelov
d58b434f0a fix codestyle 2017-09-23 11:35:30 +02:00
Eric Uldall
47fd051aba added customLabels feature to main() 2017-09-22 15:21:32 -07:00
Konstantin Pogorelov
313673f16f inc minor version, update README 2017-07-11 13:21:06 +02:00
Jonathan Creamer
bbd62e34d6 Upgrade prom-client... again (#9)
* Update package.json

* Update call to Histogram

* Update package.json

* Update index.js

* Update index.js
2017-07-11 11:41:40 +02:00
Konstantin Pogorelov
b8ba87009e extend unittests, simplify API, update README, use npm5 and package-lock.json, add node7 to travis config 2017-06-04 16:31:26 +02:00
Konstantin Pogorelov
1cc588c2da stop supporting node v4 and v5 2017-05-31 16:32:45 +02:00
Konstantin Pogorelov
5b1517ca91 #7 upgrade dependencies, workaround for koa-connect, update docs 2017-05-31 15:27:08 +02:00
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
Konstantin Pogorelov
b0aa05d42b fixes, polishing, docs update, bump to 2.0.0 2016-12-11 20:22:55 +01:00
Konstantin Pogorelov
1e9300ebf3 niterim state for 2.0 development 2016-12-08 18:13:13 +01:00
Konstantin Pogorelov
190bf0be31 When trying to register a second instance with the same name, then just take the old one. Upgrade prom-client to 6.2.x. Bump 1.2.3 2016-12-06 10:10:14 +01:00
Konstantin Pogorelov
c918b44c2b minor dep. upgrade, do not remove initial metrics if they match the requested prefix (i.e. recognized as own metrics), bump 1.2.2 2016-12-01 00:28:45 +01:00
Konstantin Pogorelov
b374e7f54c fix includePath in combination with router 2016-11-28 11:28:05 +01:00
Konstantin Pogorelov
08d98b450c upgrade prom-client to v6, add options: includeMethod, includePath, keepDefaultMetrics, bump 1.2.0 2016-11-26 21:00:58 +01:00
Konstantin Pogorelov
3675c516a9 upgrade jasme, cosmetics/whitespace fixes 2016-10-14 15:07:39 +02:00
Konstantin Pogorelov
42197ac478 Merge pull request #2 from daniyel/koa-compatibility
Add compatibility with Koa v1, fix tests and edit README
2016-10-14 14:50:29 +02:00
Danijel Hrvacanin
66e1fca8dd Add compatibility with Koa v1, fix tests and edit README 2016-10-14 14:32:39 +02:00
Konstantin Pogorelov
36c40daa5d Merge branch 'master' of github.com:jochen-schweizer/express-prom-bundle 2016-10-10 13:51:24 +02:00
Konstantin Pogorelov
8beadcb434 add npm badge 2016-10-10 13:50:50 +02:00
27 changed files with 7189 additions and 484 deletions

View File

@@ -0,0 +1,12 @@
{
"name": "Node.js 24",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"nodeGypDependencies": true,
"version": "lts",
"nvmVersion": "latest"
},
"ghcr.io/devcontainers/features/git-lfs:1": {}
}
}

View File

@@ -1,42 +0,0 @@
{
"env": {
"node": true,
"es6": true
},
"extends": "eslint:recommended",
"rules": {
"no-cond-assign": 0,
"no-constant-condition": 0,
"no-empty": 0,
"no-fallthrough": 0,
"no-unused-vars": 1,
"no-console": 1,
"semi": 2,
"curly": 2,
"consistent-this": [2, "self"],
"indent": [ 2, 4, { "SwitchCase": 1 } ],
"linebreak-style": [2, "unix"],
"no-nested-ternary": 2,
"new-parens": 2,
"no-dupe-class-members": 2,
"require-yield": 2,
"arrow-spacing": 1,
"no-var": 2,
"no-multi-spaces": 1,
"space-return-throw-case": 0,
"space-infix-ops": [1, {"int32Hint": false}],
"brace-style": 1,
"space-before-blocks": 1,
"operator-linebreak": [1, "before"],
"no-unneeded-ternary": 1,
"no-lonely-if": 1,
"key-spacing": 1,
"quotes": [1, "double", "avoid-escape"],
"no-trailing-spaces": [1, { "skipBlankLines": true }]
}
}

12
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
target-branch: "dev"
- package-ecosystem: "github-actions"
directory: "/.github/workflows"
schedule:
interval: "weekly"
target-branch: "dev"

42
.github/workflows/audit.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Security Audit
on:
push:
branches: [main, dev]
pull_request:
branches:
- '**'
schedule:
- cron: '0 8 * * *'
workflow_dispatch:
jobs:
audit:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Install dependencies
run: npm install
- name: Run security audit
id: audit
run: npm audit --audit-level moderate
continue-on-error: true
- name: Create issue on failure
if: steps.audit.outcome == 'failure'
uses: actions/github-script@v8
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Security Audit Failed',
body: 'The daily security audit has failed. Please check the workflow run for details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}',
labels: ['security', 'audit']
});

72
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
name: Release
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22, 24, latest]
steps:
- uses: actions/checkout@v6
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- run: npm install prom-client
- run: npm install
- run: npm run lint
- run: npm test
- run: npm run test-types
- name: Generate coverage
run: make coverage
- name: Upload coverage to Coveralls
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
file: coverage/lcov.info
publish:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Use Node.js 18
uses: actions/setup-node@v6
with:
node-version: 18
registry-url: 'https://registry.npmjs.org'
- run: npm install
- run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
release:
needs: publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Get version
id: get_version
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
- name: Generate Changelog
run: |
# Simple changelog generation
echo "# Changelog" > CHANGELOG.md
git log --oneline --pretty=format:"- %s" $(git describe --tags --abbrev=0 2>/dev/null || echo "HEAD~1")..HEAD >> CHANGELOG.md
- name: Create Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ steps.get_version.outputs.version }}
release_name: Release v${{ steps.get_version.outputs.version }}
body_path: CHANGELOG.md
draft: false
prerelease: false

54
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
name: CI-Tests
on:
push:
branches:
- main
- dev
pull_request:
branches:
- main
- dev
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22, 24, latest]
steps:
- uses: actions/checkout@v6
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- run: npm install prom-client
- run: npm install
- run: npm run lint
- run: npm test
- run: npm run test-types
codeql:
name: CodeQL Analysis
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: javascript
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
audit:
name: Audit Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Use Node.js 18
uses: actions/setup-node@v6
with:
node-version: 18
- run: npm install
- name: Audit Dependencies
run: npm audit --audit-level=moderate

2
.gitignore vendored
View File

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

View File

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

View File

@@ -1,5 +1,11 @@
language: node_js
node_js:
- "6"
- "5"
- "4"
- "18"
notifications:
email: false
before_install:
- npm install prom-client
script:
- npm run lint
- npm test
- npm run test-types

View File

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

272
README.md
View File

@@ -1,28 +1,31 @@
[![build status](https://travis-ci.org/jochen-schweizer/express-prom-bundle.png)](https://travis-ci.org/jochen-schweizer/express-prom-bundle) [![Coverage Status](https://coveralls.io/repos/github/jochen-schweizer/express-prom-bundle/badge.svg?branch=master)](https://coveralls.io/github/jochen-schweizer/express-prom-bundle?branch=master) [![license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)](https://www.tldrlegal.com/l/mit)
[![CI](https://github.com/BreizhHardware/express-prom-bundle/actions/workflows/release.yml/badge.svg)](https://github.com/BreizhHardware/express-prom-bundle/actions/workflows/release.yml) [![Coverage Status](https://coveralls.io/repos/github/BreizhHardware/express-prom-bundle/badge.svg?branch=main)](https://coveralls.io/github/BreizhHardware/express-prom-bundle?branch=main) [![license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)](https://www.tldrlegal.com/l/mit) [![NPM version](https://badge.fury.io/js/%40breizhhardware%2Fexpress-prom-bundle.png)](http://badge.fury.io/js/%40breizhhardware%2Fexpress-prom-bundle)
# express prometheus bundle
express middleware with popular prometheus metrics in one bundle.
Express middleware with popular prometheus metrics in one bundle. It's also compatible with koa v1 and v2 (see below). This package is a fork from the one from
[Jochen Schweizer](https://github.com/jochen-schweizer/express-prom-bundle).
Internally it uses **prom-client**. See: https://github.com/siimon/prom-client
This library uses **prom-client v15+** as a peer dependency. See: https://github.com/siimon/prom-client
If you need a support for older versions of prom-client (v12-v14), downgrade to express-prom-bundle v6.6.0
Included metrics:
* `up`: normally is just 1
* `nodejs_memory_heap_total_bytes` and `nodejs_memory_heap_used_bytes`
* `http_request_seconds`: http latency histogram labeled with `status_code`
* `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 @breizhhardware/express-prom-bundle
```
## Usage
## Sample Usage
```javascript
const promBundle = require("express-prom-bundle"),
const metricsMiddleware = promBundle({/* options */ });
const promBundle = require("@breizhhardware/express-prom-bundle");
const app = require("express")();
const metricsMiddleware = promBundle({includeMethod: true});
app.use(metricsMiddleware);
app.use(/* your middleware */);
@@ -34,7 +37,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.
@@ -42,34 +45,155 @@ See the example below.
## Options
* **prefix**: prefix added to every metric name
* **whitelist**, **blacklist**: array of strings or regexp specifying which metrics to include/exclude
* **buckets**: buckets used for `http_request_seconds` histogram
* **excludeRoutes**: array of strings or regexp specifying which routes should be skipped for `http_request_seconds` metric. It uses `req.path` as subject when checking
* **autoregister**: boolean. If `/metrics` endpoint should be registered. It is **true** by default
Which labels to include in `http_request_duration_seconds` metric:
## Example
* **includeStatusCode**: HTTP status code (200, 400, 404 etc.), default: **true**
* **includeMethod**: HTTP method (GET, PUT, ...), 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
* **httpDurationMetricName**: Allows you change the name of HTTP duration metric, default: **`http_request_duration_seconds`**.
* **upMetricName**: Allows you change the name of up metric, default: **`up`**.
### 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
* **pruneAgedBuckets**: When enabled, timed out buckets will be removed entirely. By default, buckets are reset to 0.
### 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).
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.
* **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**
* **urlPathReplacement**: replacement string for the values (default: "#val")
### Other options ###
* **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`.
* **metricsApp**: Allows you to attach the metrics endpoint to a different express app. You probably want to use it in combination with `autoregister: false`.
* **bypass**: An object that takes onRequest and onFinish callbacks that determines whether the given request should be excluded in the metrics. Default:
```js
{
onRequest: (req) => false,
onFinish: (req, res) => false
}
```
`onRequest` is run directly in the middleware chain, before the request is processed. `onFinish` is run after the request has been processed, and has access to the express response object in addition to the request object. Both callbacks are optional, and if one or both returns true the request is excluded.
As a shorthand, just the onRequest callback can be used instead of the object.
### More details on includePath option
Let's say you want to have latency statistics by URL path,
e.g. separate metrics for `/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.
These conversions are handled by `normalizePath` function.
You can extend this magical behavior by providing
additional RegExp rules to be performed,
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
app.use(promBundle(/* options? */));
// let's reuse the existing one and just add some
// functionality on top
const originalNormalize = promBundle.normalizePath;
promBundle.normalizePath = (req, opts) => {
const path = originalNormalize(req, opts);
// count all docs as one path, but /docs/login as a separate one
return (path.match(/^\/docs/) && !path.match(/^\/login/)) ? '/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
#### Example 3 (return express route definition):
```javascript
app.use(promBundle(/* options? */));
promBundle.normalizePath = (req, opts) => {
// Return the path of the express route (i.e. /v1/user/:id or /v1/timer/automated/:userid/:timerid")
return req.route?.path ?? "NULL";
};
```
## express example
setup std. metrics but exclude `up`-metric:
```javascript
"use strict";
const express = require("express"),
app = express(),
promBundle = require("express-prom-bundle");
const express = require("express");
const app = express();
const promBundle = require("@breizhhardware/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({
prefix: "demo_app:something",
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
@@ -80,6 +204,96 @@ app.listen(3000);
See an [advanced example on github](https://github.com/jochen-schweizer/express-prom-bundle/blob/master/advanced-example.js)
## koa v2 example
```javascript
const promBundle = require("@breizhhardware/express-prom-bundle");
const Koa = require("koa");
const c2k = require("koa-connect");
const metricsMiddleware = promBundle({/* options */ });
const app = new Koa();
app.use(c2k(metricsMiddleware));
app.use(/* your middleware */);
app.listen(3000);
```
## using with cluster
You'll need to use an additional **clusterMetrics()** middleware.
In the example below the master process will expose an API with a single endpoint `/metrics`
which returns an aggregate of all metrics from all the workers.
``` javascript
const cluster = require('cluster');
const promBundle = require('@breizhhardware/express-prom-bundle');
const promClient = require('prom-client');
const numCPUs = Math.max(2, require('os').cpus().length);
const express = require('express');
if (cluster.isMaster) {
for (let i = 1; i < numCPUs; i++) {
cluster.fork();
}
const metricsApp = express();
metricsApp.use('/metrics', promBundle.clusterMetrics());
metricsApp.listen(9999);
console.log('cluster metrics listening on 9999');
console.log('call localhost:9999/metrics for aggregated metrics');
} else {
new promClient.AggregatorRegistry();
const app = express();
app.use(promBundle({
autoregister: false, // disable /metrics for single workers
includeMethod: true
}));
app.use((req, res) => res.send(`hello from pid ${process.pid}\n`));
app.listen(3000);
console.log(`worker ${process.pid} listening on 3000`);
}
```
## using with kraken.js
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 IP addresses in the path as #val.
```json
{
"middleware": {
"expressPromBundle": {
"route": "/((?!status|favicon.ico|robots.txt))*",
"priority": 0,
"module": {
"name": "express-prom-bundle",
"arguments": [
{
"includeMethod": true,
"includePath": true,
"buckets": [0.1, 1, 5],
"promClient": {
"collectDefaultMetrics": {
}
},
"urlValueParser": {
"minHexLength": 5,
"extraMasks": [
"^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$"
]
}
}
]
}
}
}
}
```
## License
MIT
MIT

12
SECURITY.md Normal file
View File

@@ -0,0 +1,12 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| >= 8.0.x| :white_check_mark: |
| < 8.0.0 | :x: |
## Reporting a Vulnerability
Please use [GitHub's private vulnerability reporting](https://github.com/breizhhardware/express-prom-bundle/security/advisories/new) to report a vulnerability.

View File

@@ -1,38 +1,61 @@
"use strict";
'use strict';
const express = require("express");
const express = require('express');
const app = express();
const promBundle = require("express-prom-bundle");
const promClient = require('prom-client');
// replace this with require('.') when running from library code
const promBundle = require('express-prom-bundle');
const bundle = promBundle({
prefix: "demo_app:something:",
blacklist: [/up/],
buckets: [0.1, 0.4, 0.7]
buckets: [0.1, 0.4, 0.7],
includeMethod: true,
includePath: true,
customLabels: {year: null},
transformLabels: labels => Object.assign(labels, {year: new Date().getFullYear()}),
metricsPath: '/prometheus',
promClient: {
collectDefaultMetrics: {
}
},
urlValueParser: {
minHexLength: 5,
extraMasks: [
"^[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: [
['^/foo', '/example'] // replace /foo with /example
]
});
app.use(bundle);
// native prom-client metric (no prefix)
const c1 = new bundle.promClient.Counter("c1", "c1 help");
const c1 = new promClient.Counter({name: 'c1', help: 'c1 help'});
c1.inc(10);
// create metric using factory (w/ prefix)
const c2 = bundle.factory.newCounter("c2", "c2 help");
c2.inc(20);
app.get("/foo", (req, res) => {
setTimeout(() => {
res.send("foo response\n");
}, 500);
app.get('/foo/:id', (req, res) => {
setTimeout(() => {
res.send('foo response\n');
}, 500);
});
app.get("/bar", (req, res) => res.send("bar response\n"));
app.delete('/foo/:id', (req, res) => {
setTimeout(() => {
res.send('foo deleted\n');
}, 300);
});
app.get('/bar', (req, res) => res.send('bar response\n'));
app.listen(3000, () => console.log("listening on 3000")); // eslint-disable-line
/*
app.listen(3000, () => console.info( // eslint-disable-line
`listening on 3000
test in shell console:
curl localhost:3000/foo
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/metrics
*/
curl localhost:3000/prometheus
`
));

77
eslint.config.mjs Normal file
View File

@@ -0,0 +1,77 @@
import { defineConfig } from "eslint/config";
import globals from "globals";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
export default defineConfig([{
extends: compat.extends("eslint:recommended"),
languageOptions: {
globals: {
...globals.node,
app: true,
fetch: true,
},
ecmaVersion: 6,
sourceType: "module",
},
rules: {
indent: [1, 2],
"array-bracket-spacing": [2, "never"],
"block-scoped-var": 2,
"brace-style": [2, "1tbs"],
"computed-property-spacing": [2, "never"],
curly: 2,
"eol-last": 2,
eqeqeq: [2, "smart"],
"max-depth": [1, 3],
"new-cap": 1,
"no-extend-native": 2,
"no-mixed-spaces-and-tabs": 2,
"no-trailing-spaces": 1,
"no-unused-vars": 1,
"no-use-before-define": [2, "nofunc"],
"object-curly-spacing": [2, "never"],
quotes: [1, "single", "avoid-escape"],
semi: [2, "always"],
"keyword-spacing": [2, {
before: true,
after: true,
}],
"space-unary-ops": 2,
"no-console": [1, {
allow: ["info", "warn", "error"],
}],
"max-len": [1, 120],
"max-statements": [1, 50],
"consistent-this": [2, "self"],
"no-var": 2,
"no-dupe-class-members": 2,
"operator-linebreak": [1, "before"],
"no-unneeded-ternary": [1, {
defaultAssignment: false,
}],
"no-lonely-if": 1,
"linebreak-style": [2, "unix"],
"no-nested-ternary": 2,
"require-yield": 2,
},
}]);

5347
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,33 +1,58 @@
{
"name": "express-prom-bundle",
"version": "1.1.7",
"name": "@breizhhardware/express-prom-bundle",
"version": "8.0.7",
"description": "express middleware with popular prometheus metrics in one bundle",
"main": "src/index.js",
"keywords": [
"prometheus",
"metrics",
"express",
"bundle"
"path",
"method"
],
"files": [
"src",
"types/index.d.ts"
],
"types": "types/index.d.ts",
"scripts": {
"test": "node_modules/jasme/run.js"
"test": "NODE_ENV=test node_modules/jasme/run.js",
"lint": "eslint src",
"coverage": "make coverage",
"test-types": "tsd"
},
"author": "Konstantin Pogorelov <or@pluseq.com>",
"author": "BreizhHardware <felix.marquet@horoquartz.com>",
"license": "MIT",
"dependencies": {
"@types/express": "^5.0.0",
"on-finished": "^2.3.0",
"prom-client": "^3.4.6"
"url-value-parser": "^2.0.0"
},
"devDependencies": {
"coveralls": "^2.11.12",
"eslint": "^2.13.1",
"express": "^4.13.4",
"istanbul": "^0.4.4",
"jasme": "^4.1.2",
"supertest": "^1.2.0"
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.38.0",
"dts": "^0.1.1",
"eslint": "^9.38.0",
"express": "^5.1.0",
"globals": "^16.4.0",
"jasme": "^6.0.0",
"koa": "^3.0.1",
"koa-connect": "^2.0.1",
"prom-client": "^15.0.0",
"supertest": "^7.1.4",
"supertest-koa-agent": "^0.3.0",
"tsd": "^0.33.0",
"typescript": "^5.9.3",
"nyc": "^17.1.0"
},
"peerDependencies": {
"prom-client": ">=15.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/jochen-schweizer/express-prom-bundle.git"
"url": "git+https://github.com/breizhhardware/express-prom-bundle.git"
},
"engines": {
"node": ">=18"
}
}

View File

@@ -1,57 +0,0 @@
"use strict";
/* eslint-env jasmine */
const PromFactory = require("../src/PromFactory");
describe("PromFactory", () => {
let factory;
beforeEach(() => {
factory = new PromFactory();
});
it("creates Counter", () => {
const metric = factory.newCounter(
"test1",
"help for test1",
["label1", "label2"]
);
expect(metric.name).toBe("test1");
expect(metric.help).toBe("help for test1");
expect(metric.labelNames).toEqual(["label1", "label2"]);
});
it("throws on duplicate names", () => {
factory.newCounter("n","h");
expect(() => factory.newCounter("n","h2")).toThrow();
});
it("creates Gauge", () => {
const metric = factory.newGauge(
"test2",
"help for test2",
["label1", "label2"]
);
expect(metric.name).toBe("test2");
expect(metric.help).toBe("help for test2");
expect(metric.labelNames).toEqual(["label1", "label2"]);
});
it("creates Histogram with labels", () => {
const metric = factory.newHistogram(
"test3",
"help for test3",
["label1", "label2"],
{buckets: [1, 2, 3]}
);
expect(metric.name).toBe("test3");
expect(metric.help).toBe("help for test3");
expect(metric.labelNames).toEqual(["label1", "label2"]);
expect(metric.bucketValues).toEqual({"1": 0, "2": 0, "3": 0});
});
it("creates Summary without labels", () => {
const metric = factory.newSummary(
"test4",
"help for test4",
{percentiles: [0.1, 0.5]}
);
expect(metric.name).toBe("test4");
expect(metric.help).toBe("help for test4");
expect(metric.percentiles).toEqual([0.1, 0.5]);
});
});

719
spec/index.spec.js Normal file
View File

@@ -0,0 +1,719 @@
'use strict';
/* eslint-env jasmine */
const express = require('express');
const supertest = require('supertest');
const bundle = require('../');
const Koa = require('koa');
const c2k = require('koa-connect');
const supertestKoa = require('supertest-koa-agent');
const promClient = require('prom-client');
const cluster = require('cluster');
// for some reason in prom-client 15 the hashmap has a trailing comma
function extractBucket (instance, key) {
const hashmap = instance.metrics.http_request_duration_seconds.hashMap;
if (hashmap[key]) {
return hashmap[key];
} else {
return hashmap[key + ','];
}
}
function getMetricCount (s) {
return s.replace(/^#.*$\n|^$\n/gm, '').trim().split('\n').length;
}
describe('index', () => {
beforeEach(() => {
promClient.register.clear();
});
it('metrics returns up=1', done => {
const app = express();
const bundled = bundle({
excludeRoutes: ['/irrelevant', /at.all/]
});
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('"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({
httpDurationMetricName: 'my_http_duration'
});
app.use(bundled);
const agent = supertest(app);
agent.get('/metrics')
.end((err, res) => {
expect(res.text).toMatch(/my_http_duration/m);
done();
});
});
it('metrics should be attached to /metrics by default', done => {
const app = express();
const bundled = bundle();
app.use(bundled);
const agent = supertest(app);
agent.get('/metrics')
.end((err, res) => {
expect(res.status).toBe(200);
done();
});
});
it('metrics can be attached to /metrics programatically', done => {
const app = express();
const bundled = bundle({
autoregister: false
});
app.use(bundled.metricsMiddleware);
app.use(bundled);
app.use('/test', (req, res) => res.send('it worked'));
const agent = supertest(app);
agent.get('/metrics')
.end((err, res) => {
expect(res.status).toBe(200);
done();
});
});
it('returns error 500 on incorrect middleware usage', done => {
const app = express();
app.use(bundle);
supertest(app)
.get('/metrics')
.end((err, res) => {
expect(res.status).toBe(500);
done();
});
});
it('http latency gets counted', done => {
const app = express();
const instance = bundle();
app.use(instance);
app.use('/test', (req, res) => res.send('it worked'));
const agent = supertest(app);
agent
.get('/test')
.end(() => {
const bucket = extractBucket(instance, 'status_code:200');
expect(bucket).toBeDefined();
expect(bucket.count).toBe(1);
agent
.get('/metrics')
.end((err, res) => {
expect(res.status).toBe(200);
done();
});
});
});
it('filters out the excludeRoutes', done => {
const app = express();
const instance = bundle({
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(() => {
agent
.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 bucket = extractBucket(instance, 'status_code:200');
expect(bucket).toBeDefined();
// only /good-word should be counted
expect(bucket.count).toBe(1);
agent
.get('/metrics')
.end((err, res) => {
expect(res.status).toBe(200);
done();
});
});
});
});
});
it('bypass requests, checking res', done => {
const app = express();
const instance = bundle({
bypass: {
onFinish: (req, res) => res.statusCode === 404
}
});
app.use(instance);
app.use('/200', (req, res) => res.send(''));
app.use('/404', (req, res) => res.status(404).send(''));
app.use('/500', (req, res) => res.status(500).send(''));
const agent = supertest(app);
agent.get('/200')
.expect(200)
.then(() => {
return agent
.get('/404')
.expect(404);
})
.then(() => {
return agent
.get('/500')
.expect(500);
})
.then(() => {
// only /200 and /500 should be counted
expect(extractBucket(instance, 'status_code:200').count).toBe(1);
expect(extractBucket(instance, 'status_code:404')).not.toBeDefined();
expect(extractBucket(instance, 'status_code:500').count).toBe(1);
return agent
.get('/metrics')
.expect(200);
})
.then(done);
});
it('complains about deprecated options', () => {
expect(() => bundle({prefix: 'hello'})).toThrow();
});
it('tolerates includePath, includeMethod, includeCustomLabels', done => {
const app = express();
const instance = bundle({
includePath: true,
includeMethod: true,
includeCustomLabels: {foo: 'bar'}
});
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);
done();
});
});
});
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 globally', 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('respects pruneAgedBuckets', done => {
const app = express();
const metricsApp = express();
const bundled = bundle({
metricsApp,
metricType: 'summary',
includePath: true,
maxAgeSeconds: 1,
percentiles: [0.5],
ageBuckets: 1,
pruneAgedBuckets: true,
});
app.use(bundled);
const agent = supertest(app);
const metricsAgent = supertest(metricsApp);
agent.get('/foo')
.then(() => metricsAgent.get('/metrics'))
.then(response => {
expect(response.status).toBe(200);
// up + bucket, sum, count
expect(getMetricCount(response.text)).toBe(4);
})
.then(() => new Promise(r => setTimeout(r, 1010)))
.then(() => metricsAgent.get('/metrics'))
.then(response => {
expect(response.status).toBe(200);
// only up
expect(getMetricCount(response.text)).toBe(1);
})
.finally(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 => {
const app = express();
const instance = bundle({
formatStatusCode: () => 555
});
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(/555/);
done();
});
});
});
it('includeStatusCode=false removes status_code label from metrics', done => {
const app = express();
const instance = bundle({
includeStatusCode: false
});
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).not.toMatch(/="200"/);
done();
});
});
});
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({
customLabels: {foo: 'bar'}
});
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(/foo="bar"/);
done();
});
});
});
it('tarnsformLabels can set label values', done => {
const app = express();
const instance = bundle({
includePath: true,
customLabels: {foo: 'bar'},
transformLabels: labels => {
labels.foo = 'baz';
labels.path += '/ok';
}
});
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(/foo="baz"/);
expect(res.text).toMatch(/path="\/test\/ok"/);
done();
});
});
});
it('Koa: metrics returns up=1', done => {
const app = new Koa();
const bundled = bundle();
app.use(c2k(bundled));
app.use(function(ctx, next) {
return next().then(() => ctx.body = 'it worked');
});
const agent = supertestKoa(app);
agent.get('/test').end(() => {
agent
.get('/metrics')
.end((err, res) => {
expect(res.status).toBe(200);
expect(res.text).toMatch(/^up\s1/m);
done();
});
});
});
describe('option collectDefaultMetrics', () => {
it('it gets called', () => {
const spy = spyOn(promClient, 'collectDefaultMetrics');
bundle({
promClient: {
collectDefaultMetrics: {
}
}
});
expect(spy).toHaveBeenCalledWith({});
});
it('prefix is used for up metric', (done) => {
const instance = bundle({
promClient: {
collectDefaultMetrics: {
prefix: 'hello_'
}
}
});
const app = express();
app.use(instance);
const agent = supertest(app);
agent
.get('/metrics')
.end((err, res) => {
expect(res.status).toBe(200);
expect(res.text).toMatch(/^hello_up\s1/m);
done();
});
});
});
describe('usage of clusterMetrics()', () => {
it('clusterMetrics returns 200 even without a cluster', (done) => {
const app = express();
cluster.workers = [];
app.use('/cluster_metrics', bundle.clusterMetrics());
const agent = supertest(app);
agent
.get('/cluster_metrics')
.end((err, res) => {
expect(res.status).toBe(200);
done();
});
});
it('clusterMetrics returns 500 in case of an error', (done) => {
const app = express();
app.use('/cluster_metrics', bundle.clusterMetrics());
const agent = supertest(app);
// create a fake worker, which would not respond in time
cluster.workers = [{
isConnected: () => true,
send: () => {}
}];
const errorSpy = spyOn(console, 'error'); // mute console.error
agent
.get('/cluster_metrics')
.end((err, res) => {
expect(res.status).toBe(500);
expect(errorSpy).toHaveBeenCalled();
done();
});
}, 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();
});
});
});
it('additional metricsApp can be used', done => {
const app = express();
const metricsApp = express();
const bundled = bundle({metricsApp});
app.use(bundled);
const agent = supertest(app);
const metricsAgent = supertest(metricsApp);
agent.get('/').end(() => {
metricsAgent.get('/metrics').end((err, res) => {
expect(res.status).toBe(200);
expect(res.text).toMatch(/status_code="404"/);
done();
});
});
});
});
});

View File

@@ -1,140 +0,0 @@
"use strict";
/* eslint-env jasmine */
let express = require("express"),
supertest = require("supertest"),
bundle = require("../");
describe("index", () => {
it("metrics returns up=1", done => {
const app = express();
const bundled = bundle({
prefix: "hello:",
whitelist: ["up"]
});
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(/hello:up\s1/);
done();
});
});
});
it("metrics should be attached to /metrics by default", done => {
const app = express();
const bundled = bundle({
prefix: "hello:",
whitelist: ["up"]
});
app.use(bundled);
const agent = supertest(app);
agent.get("/metrics")
.end((err, res) => {
expect(res.status).toBe(200);
done();
});
});
it("metrics can be attached to /metrics programatically", done => {
const app = express();
const bundled = bundle({
autoregister: false
});
app.use(bundled.metricsMiddleware);
app.use(bundled);
app.use("/test", (req, res) => res.send("it worked"));
const agent = supertest(app);
agent.get("/metrics")
.end((err, res) => {
expect(res.status).toBe(200);
done();
});
});
it("metrics can be filtered using exect match", () => {
const instance = bundle({blacklist: ["up"]});
expect(instance.metrics.up).not.toBeDefined();
expect(instance.metrics.nodejs_memory_heap_total_bytes).toBeDefined();
});
it("metrics can be filtered using regex", () => {
const instance = bundle({blacklist: [/memory/]});
expect(instance.metrics.up).toBeDefined();
expect(instance.metrics.nodejs_memory_heap_total_bytes).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_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);
supertest(app)
.get("/metrics")
.end((err, res) => {
expect(res.status).toBe(500);
done();
});
});
it("http latency gets counted", done => {
const app = express();
const instance = bundle();
app.use(instance);
app.use("/test", (req, res) => res.send("it worked"));
const agent = supertest(app);
agent
.get("/test")
.end(() => {
const metricHashMap = instance.metrics.http_request_seconds.hashMap;
expect(metricHashMap["status_code:200"]).toBeDefined();
const labeled = metricHashMap["status_code:200"];
expect(labeled.count).toBe(1);
agent
.get("/metrics")
.end((err, res) => {
expect(res.status).toBe(200);
done();
});
});
});
it("filters out the excludeRoutes", done => {
const app = express();
const instance = bundle({
excludeRoutes: ["/test"]
});
app.use(instance);
app.use("/test", (req, res) => res.send("it worked"));
const agent = supertest(app);
agent
.get("/test")
.end(() => {
const metricHashMap = instance.metrics.http_request_seconds.hashMap;
expect(metricHashMap["status_code:200"]).not.toBeDefined();
agent
.get("/metrics")
.end((err, res) => {
expect(res.status).toBe(200);
done();
});
});
});
});

View File

@@ -0,0 +1,36 @@
'use strict';
/* eslint-env jasmine */
const normalizePath = require('../src/normalizePath');
describe('normalizePath', () => {
it('uses UrlValueParser by default', () => {
expect(normalizePath({url: '/a/12345'}))
.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();
});
it('uses urlPathReplacement when passed to transform the path', () => {
expect(normalizePath({url: '/a/12345'}, {urlPathReplacement: ':id'}))
.toBe('/a/:id');
});
});

View File

@@ -0,0 +1,16 @@
'use strict';
/* eslint-env jasmine */
const normalizeStatusCode = require('../src/normalizeStatusCode');
describe('normalizeStatusCode', () => {
it('returns run callback if configured', () => {
expect(
normalizeStatusCode({status_code: 500, headersSent: true})
).toBe(500);
});
it('returns 499 if headers are not sent', () => {
expect(normalizeStatusCode({statusCode: 200, headersSent: false})).toBe(499);
});
});

View File

@@ -1,53 +0,0 @@
"use strict";
module.exports = class {
constructor(opts) {
this.opts = opts || {};
this.promClient = this.opts.promClient || require("prom-client");
this.metrics = {};
}
metricExists(name) {
return !!this.metrics[name];
}
checkDuplicate(name) {
if (this.metricExists(name)) {
throw new Error("trying to add already existing metric: " + name);
}
}
makeRealName(name) {
return (this.opts.prefix || "") + name;
}
makeMetric(TheClass, args) {
// convert pseudo-array
const applyParams = Array.prototype.slice.call(args);
const name = applyParams[0];
this.checkDuplicate(name);
const realName = this.makeRealName(name);
applyParams[0] = realName;
applyParams.unshift(null); // add some dummy context for apply
// call constructor with variable params
this.metrics[name] = new (Function.prototype.bind.apply(TheClass, applyParams));
return this.metrics[name];
}
newCounter() {
return this.makeMetric(this.promClient.Counter, arguments);
}
newGauge() {
return this.makeMetric(this.promClient.Gauge, arguments);
}
newHistogram() {
return this.makeMetric(this.promClient.Histogram, arguments);
}
newSummary() {
return this.makeMetric(this.promClient.Summary, arguments);
}
};

View File

@@ -1,137 +1,246 @@
"use strict";
const
PromFactory = require("./PromFactory"),
onFinished = require("on-finished");
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) {
if (regexp instanceof RegExp) {
if (element.match(regexp)) {
return true;
}
} else if (element == regexp) {
return true;
}
for (const regexp of regexps) {
if (regexp instanceof RegExp) {
if (element.match(regexp)) {
return true;
}
} else if (element === regexp) {
return true;
}
return false;
}
return false;
}
function clusterMetrics() {
const aggregatorRegistry = new promClient.AggregatorRegistry();
function filterArrayByRegExps(array, regexps) {
return array.filter(element => {
return matchVsRegExps(element, regexps);
const metricsMiddleware = function(req, res) {
function sendClusterMetrics(clusterMetrics) {
res.set('Content-Type', aggregatorRegistry.contentType);
res.send(clusterMetrics);
}
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);
});
}
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 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));
}
if (opts.blacklist) {
const blacklisted = filterArrayByRegExps(names, opts.blacklist);
return names.filter(name => blacklisted.indexOf(name) === -1);
}
return names;
};
return metricsMiddleware;
}
function main(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"
+ "<p>Unexpected 3rd param in express-prom-bundle.\n"
+ "<p>Did you just put express-prom-bundle into app.use "
+ "without calling it as a function first?");
if (arguments[2] && arguments[1] && arguments[1].send) {
arguments[1].status(500)
.send('<h1>500 Error</h1>\n'
+ '<p>Unexpected 3rd param in express-prom-bundle.\n'
+ '<p>Did you just put express-prom-bundle into app.use '
+ 'without calling it as a function first?');
return;
}
opts = Object.assign(
{
autoregister: true,
includeStatusCode: true,
normalizePath: main.normalizePath,
formatStatusCode: main.normalizeStatusCode,
metricType: 'histogram',
promClient: {},
promRegistry: promClient.register,
metricsApp: null,
}, opts
);
if (opts.prefix
|| opts.keepDefaultMetrics !== undefined
|| opts.whitelist !== undefined
|| opts.blacklist !== undefined
) {
throw new Error(
'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 the necessary code changes'
);
}
if (opts.promClient.collectDefaultMetrics) {
promClient.collectDefaultMetrics(opts.promClient.collectDefaultMetrics);
}
const httpMetricName = opts.httpDurationMetricName || 'http_request_duration_seconds';
const upMetricName = opts.upMetricName || 'up';
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],
pruneAgedBuckets: opts.pruneAgedBuckets
});
} 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],
registers: [opts.promRegistry]
});
} else {
throw new Error('metricType option must be histogram or summary');
}
}
const metrics = {
[httpMetricName]: makeHttpMetric()
};
if (opts.includeUp !== false) {
let prefix = '';
if (opts.promClient && opts.promClient.collectDefaultMetrics) {
prefix = opts.promClient.collectDefaultMetrics.prefix || '';
}
metrics.up = new promClient.Gauge({
name: prefix + upMetricName,
help: '1 = up, 0 = not up',
registers: [opts.promRegistry]
});
metrics.up.set(1);
}
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') + '/?$');
if (typeof opts.bypass === 'function') {
opts.bypass = {
onRequest: opts.bypass
};
} else if (!opts.bypass) {
opts.bypass = {};
}
const middleware = function (req, res, next) {
const path = req.originalUrl || req.url; // originalUrl gets lost in koa-connect?
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.onRequest && opts.bypass.onRequest(req)) {
return next();
}
if (opts.excludeRoutes && matchVsRegExps(path, opts.excludeRoutes)) {
return next();
}
const labels = {};
const timer = metrics[httpMetricName].startTimer(labels);
onFinished(res, () => {
if (opts.bypass.onFinish && opts.bypass.onFinish(req, res)) {
return;
}
}
const factory = new PromFactory(opts);
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();
});
const metricTemplates = {
"up": () => factory.newGauge(
"up",
"1 = up, 0 = not up"
),
"nodejs_memory_heap_total_bytes": () => factory.newGauge(
"nodejs_memory_heap_total_bytes",
"value of process.memoryUsage().heapTotal"
),
"nodejs_memory_heap_used_bytes": () => factory.newGauge(
"nodejs_memory_heap_used_bytes",
"value of process.memoryUsage().heapUsed"
),
"http_request_seconds": () => {
const metric = factory.newHistogram(
"http_request_seconds",
"number of http responses labeled with status code",
["status_code"],
{
buckets: opts.buckets || [0.003, 0.03, 0.1, 0.3, 1.5, 10]
}
);
return metric;
}
};
next();
};
const
metrics = {},
names = prepareMetricNames(opts, metricTemplates);
if (opts.metricsApp) {
opts.metricsApp.get(opts.metricsPath || '/metrics', (req, res) => {
res.set('Content-Type', opts.promRegistry.contentType);
opts.promRegistry.metrics()
.then(metrics => res.end(metrics));
});
}
for (let name of names) {
metrics[name] = metricTemplates[name]();
}
if (metrics.up) {
metrics.up.set(1);
}
const metricsMiddleware = function(req,res) {
let memoryUsage = process.memoryUsage();
if (metrics["nodejs_memory_heap_total_bytes"]) {
metrics["nodejs_memory_heap_total_bytes"].set(memoryUsage.heapTotal);
}
if (metrics["nodejs_memory_heap_used_bytes"]) {
metrics["nodejs_memory_heap_used_bytes"].set(memoryUsage.heapUsed);
}
res.contentType("text/plain").send(factory.promClient.register.metrics());
return;
};
const middleware = function (req, res, next) {
if (opts.autoregister && req.path == "/metrics") {
return metricsMiddleware(req,res);
}
if (opts.excludeRoutes && matchVsRegExps(req.path, opts.excludeRoutes)) {
return next();
}
let labels;
if (metrics["http_request_seconds"]) {
labels = {"status_code": 0};
let timer = metrics["http_request_seconds"].startTimer(labels);
onFinished(res, () => {
labels["status_code"] = res.statusCode;
timer();
});
}
next();
};
middleware.factory = factory;
middleware.metricTemplates = metricTemplates;
middleware.metrics = metrics;
middleware.promClient = factory.promClient;
middleware.metricsMiddleware = metricsMiddleware;
return middleware;
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;
module.exports = main;

31
src/normalizePath.js Normal file
View File

@@ -0,0 +1,31 @@
'use strict';
const UrlValueParser = require('url-value-parser');
const url = require('url');
// ATTENTION! urlValueParser is a singleton!
let urlValueParser;
module.exports = function(req, opts) {
// originalUrl is taken, because url and path can be changed
// by middlewares such as 'router'. Note: this function is called onFinish
/// i.e. always in the tail of the middleware chain
let path = url.parse(req.originalUrl || req.url).pathname;
const urlPathReplacement = opts ? opts.urlPathReplacement : '#val';
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) {
urlValueParser = new UrlValueParser(opts && opts.urlValueParser);
}
return urlValueParser.replacePathValues(path, urlPathReplacement);
};

View File

@@ -0,0 +1,11 @@
'use strict';
const CLIENT_CLOSED_REQUEST_CODE = 499;
module.exports = function(res) {
if (res.headersSent) {
return res.status_code || res.statusCode;
} else {
return CLIENT_CLOSED_REQUEST_CODE;
}
};

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

@@ -0,0 +1,91 @@
// TypeScript Version: 2.8
import { Request, RequestHandler, Response, Express } from 'express';
import { DefaultMetricsCollectorConfiguration, Registry, RegistryContentType } 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 BaseOptions {
autoregister?: boolean;
customLabels?: { [key: string]: any };
includeStatusCode?: boolean;
includeMethod?: boolean;
includePath?: boolean;
includeUp?: boolean;
bypass?:
| ((req: Request) => boolean)
| {
onRequest?: (req: Request) => boolean;
onFinish?: (req: Request, res: Response) => boolean;
};
excludeRoutes?: Array<string | RegExp>;
metricsPath?: string;
httpDurationMetricName?: string;
upMetricName?: string;
promClient?: { collectDefaultMetrics?: DefaultMetricsCollectorConfiguration<RegistryContentType> };
promRegistry?: Registry;
normalizePath?: NormalizePathEntry[] | NormalizePathFn;
formatStatusCode?: NormalizeStatusCodeFn;
transformLabels?: TransformLabelsFn;
urlPathReplacement?: string;
metricsApp?: Express;
// https://github.com/disjunction/url-value-parser#options
urlValueParser?: {
minHexLength?: number;
minBase64Length?: number;
replaceMasks?: Array<RegExp | string>;
extraMasks?: Array<RegExp | string>;
};
}
/** @see https://github.com/siimon/prom-client#summary */
type SummaryOptions = BaseOptions & {
metricType?: 'summary';
percentiles?: number[];
maxAgeSeconds?: number;
ageBuckets?: number;
pruneAgedBuckets?: boolean;
}
/** @see https://github.com/siimon/prom-client#histogram */
type HistogramOptions = BaseOptions & {
metricType?: 'histogram';
buckets?: number[];
}
type Opts = SummaryOptions | HistogramOptions;
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;

91
types/index.test-d.ts Normal file
View File

@@ -0,0 +1,91 @@
import express, { RequestHandler } from 'express';
import { expectType } from 'tsd'
import * as promClient from 'prom-client';
import promBundle, {
type Middleware
} from '..';
const middleware: express.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,
excludeRoutes: ['/foo', /^\/bar\/?$/],
customLabels: { year: null },
transformLabels: (labels: promBundle.Labels) => ({
...labels,
year: new Date().getFullYear()
}),
metricType: 'histogram',
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: express.Response) => res.statusCode + 100,
metricsApp: express()
}));
promClient.register.clear();
promBundle({
metricType: 'summary',
maxAgeSeconds: 600,
ageBuckets: 5
});
promClient.register.clear();
promBundle({
metricType: 'summary',
percentiles: [0.01, 0.1, 0.9, 0.99]
});
// 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: express.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: express.Response) => res.statusCode.toString();
expectType<RequestHandler>(promBundle.clusterMetrics());