mirror of
https://github.com/BreizhHardware/express-prom-bundle.git
synced 2026-01-19 08:47:26 +01:00
Compare commits
120 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcc9e1f893 | ||
|
|
36d1484d33 | ||
|
|
8fc61cd1d8 | ||
|
|
4fd7a797c5 | ||
|
|
30d9c3d473 | ||
|
|
10fe1cac8f | ||
|
|
9b4e0eb163 | ||
|
|
475b7a8f6d | ||
|
|
c1482220c2 | ||
|
|
66643411b8 | ||
|
|
97b2f425e6 | ||
|
|
a8d4fa8116 | ||
|
|
0eb0178622 | ||
|
|
5c1f68a482 | ||
|
|
12ff62478f | ||
|
|
2efc5189d0 | ||
|
|
c28b736c6e | ||
|
|
e84b96bf5e | ||
|
|
e49f95ab0e | ||
|
|
cf862fca31 | ||
|
|
0f6eab8d98 | ||
|
|
b586bd9515 | ||
|
|
1a5f6df7f9 | ||
|
|
b2f5283303 | ||
|
|
cdc558a8d8 | ||
|
|
af8a3097fc | ||
|
|
c5cce9f8f5 | ||
|
|
36789b54ae | ||
|
|
e1b09bca43 | ||
|
|
c3284c6ce6 | ||
|
|
8dc2f9c171 | ||
|
|
a9eb496d11 | ||
|
|
3353686379 | ||
|
|
489ce17366 | ||
|
|
4e07a7db7a | ||
|
|
59f6ac0afa | ||
|
|
597fdda556 | ||
|
|
21e90237e4 | ||
|
|
2de73f7526 | ||
|
|
e886d7cb05 | ||
|
|
815ba9819a | ||
|
|
f7805d301b | ||
|
|
6b23a109b1 | ||
|
|
39788a1ff0 | ||
|
|
f7474e3ea7 | ||
|
|
6da4bece87 | ||
|
|
522e9ad64d | ||
|
|
db8710d5d0 | ||
|
|
f9a0a7622a | ||
|
|
fd33d98c15 | ||
|
|
5978ea7b73 | ||
|
|
71467e6a17 | ||
|
|
c6b24f6eca | ||
|
|
1ac5fba5c4 | ||
|
|
ca8b0ba1e0 | ||
|
|
2ebc2618de | ||
|
|
df46ecaa9a | ||
|
|
abdfe2d93a | ||
|
|
33ab388106 | ||
|
|
02865e531d | ||
|
|
1a1d8e0b54 | ||
|
|
8371c551d5 | ||
|
|
10a58635e1 | ||
|
|
5f44228f69 | ||
|
|
fd5ff1cfe0 | ||
|
|
edfb9992ed | ||
|
|
0ce44722a5 | ||
|
|
407ea4b0d7 | ||
|
|
63b6d89caa | ||
|
|
5e2b284903 | ||
|
|
c693affcac | ||
|
|
a0eef5d0e2 | ||
|
|
efbab7dcdb | ||
|
|
0bda9934c1 | ||
|
|
5bf6b153e6 | ||
|
|
1171fb5be1 | ||
|
|
ea9f34aa3e | ||
|
|
c541824657 | ||
|
|
7eae5d4b7f | ||
|
|
93b0e8fafe | ||
|
|
2d2b253567 | ||
|
|
1b52c6f46e | ||
|
|
2293d1cd40 | ||
|
|
1ee094eb04 | ||
|
|
a1284ec3e5 | ||
|
|
5a1efd7fe6 | ||
|
|
bcac9f523a | ||
|
|
c4be86b651 | ||
|
|
5e5a47a500 | ||
|
|
426e4d4556 | ||
|
|
78ebb06a6d | ||
|
|
c6d2210062 | ||
|
|
c436fa3c88 | ||
|
|
77b02c7302 | ||
|
|
068ed93a61 | ||
|
|
c5aa3f7388 | ||
|
|
ca28ff4fe1 | ||
|
|
52f47ed9f4 | ||
|
|
a60996ebc3 | ||
|
|
e7c94ff307 | ||
|
|
884617bffe | ||
|
|
32b76d0970 | ||
|
|
845cd6ee46 | ||
|
|
0c3c949425 | ||
|
|
1ec800cde7 | ||
|
|
661e7d5092 | ||
|
|
b569a71130 | ||
|
|
10c372c438 | ||
|
|
3599c32876 | ||
|
|
59ec729cb2 | ||
|
|
c95cd1834c | ||
|
|
d18316fee8 | ||
|
|
ccb74f99b0 | ||
|
|
2e093bc14d | ||
|
|
3f6b2746b9 | ||
|
|
83ee0ce06e | ||
|
|
5300d0ef82 | ||
|
|
62abb62772 | ||
|
|
bef92b77e1 | ||
|
|
99d8fc1ea9 |
12
.devcontainer/devcontainer.json
Normal file
12
.devcontainer/devcontainer.json
Normal 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": {}
|
||||
}
|
||||
}
|
||||
55
.eslintrc
55
.eslintrc
@@ -1,55 +0,0 @@
|
||||
{
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
|
||||
"globals": {
|
||||
"app": true,
|
||||
"fetch": true
|
||||
},
|
||||
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
|
||||
"extends": "eslint:recommended",
|
||||
|
||||
"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
|
||||
}
|
||||
}
|
||||
12
.github/dependabot.yml
vendored
Normal file
12
.github/dependabot.yml
vendored
Normal 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
42
.github/workflows/audit.yml
vendored
Normal 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
72
.github/workflows/release.yml
vendored
Normal 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
54
.github/workflows/test.yml
vendored
Normal 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
2
.gitignore
vendored
@@ -2,4 +2,4 @@
|
||||
node_modules
|
||||
coverage
|
||||
/.vscode
|
||||
|
||||
.env
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "10"
|
||||
- "12"
|
||||
- "14"
|
||||
- "18"
|
||||
notifications:
|
||||
email: false
|
||||
before_install:
|
||||
@@ -10,4 +8,4 @@ before_install:
|
||||
script:
|
||||
- npm run lint
|
||||
- npm test
|
||||
- npm run dtslint-next
|
||||
- npm run test-types
|
||||
|
||||
6
Makefile
6
Makefile
@@ -1,10 +1,10 @@
|
||||
.PHONY: coverage
|
||||
|
||||
test:
|
||||
./node_modules/jasme/run.js
|
||||
npm test
|
||||
lint:
|
||||
node_modules/eslint/bin/eslint.js src
|
||||
node_modules/.bin/dtslint types
|
||||
npx eslint src
|
||||
npm run dtslint-next
|
||||
coverage:
|
||||
node_modules/istanbul/lib/cli.js cover \
|
||||
-i 'src/*' \
|
||||
|
||||
53
README.md
53
README.md
@@ -1,10 +1,13 @@
|
||||
[](https://travis-ci.org/jochen-schweizer/express-prom-bundle) [](https://coveralls.io/github/jochen-schweizer/express-prom-bundle?branch=master) [](https://www.tldrlegal.com/l/mit) [](http://badge.fury.io/js/express-prom-bundle)
|
||||
[](https://github.com/BreizhHardware/express-prom-bundle/actions/workflows/release.yml) [](https://coveralls.io/github/BreizhHardware/express-prom-bundle?branch=main) [](https://www.tldrlegal.com/l/mit) [](http://badge.fury.io/js/%40breizhhardware%2Fexpress-prom-bundle)
|
||||
|
||||
# express prometheus bundle
|
||||
|
||||
Express middleware with popular prometheus metrics in one bundle. It's also compatible with koa v1 and v2 (see below).
|
||||
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).
|
||||
|
||||
Since version 5 it uses **prom-client** as a peer dependency. 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:
|
||||
|
||||
@@ -14,13 +17,13 @@ Included metrics:
|
||||
## Install
|
||||
|
||||
```
|
||||
npm install prom-client express-prom-bundle
|
||||
npm install prom-client @breizhhardware/express-prom-bundle
|
||||
```
|
||||
|
||||
## Sample Usage
|
||||
|
||||
```javascript
|
||||
const promBundle = require("express-prom-bundle");
|
||||
const promBundle = require("@breizhhardware/express-prom-bundle");
|
||||
const app = require("express")();
|
||||
const metricsMiddleware = promBundle({includeMethod: true});
|
||||
|
||||
@@ -52,7 +55,8 @@ Which labels to include in `http_request_duration_seconds` metric:
|
||||
* **includeUp**: include an auxiliary "up"-metric which always returns 1, default: **true**
|
||||
* **metricsPath**: replace the `/metrics` route with a **regex** or exact **string**. Note: it is highly recommended to just stick to the default
|
||||
* **metricType**: histogram/summary selection. See more details below
|
||||
* **bypass**: function taking express request as an argument and determines whether the given request should be excluded in the metrics, default: **() => false**
|
||||
* **httpDurationMetricName**: Allows you change the name of HTTP duration metric, default: **`http_request_duration_seconds`**.
|
||||
* **upMetricName**: Allows you change the name of up metric, default: **`up`**.
|
||||
|
||||
### metricType option ###
|
||||
|
||||
@@ -67,6 +71,7 @@ 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 ###
|
||||
|
||||
@@ -79,14 +84,29 @@ Additional options for **summary**:
|
||||
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**)
|
||||
* **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
|
||||
|
||||
@@ -144,6 +164,17 @@ For more details:
|
||||
* [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:
|
||||
@@ -151,7 +182,7 @@ setup std. metrics but exclude `up`-metric:
|
||||
```javascript
|
||||
const express = require("express");
|
||||
const app = express();
|
||||
const promBundle = require("express-prom-bundle");
|
||||
const promBundle = require("@breizhhardware/express-prom-bundle");
|
||||
|
||||
// calls to this route will not appear in metrics
|
||||
// because it's applied before promBundle
|
||||
@@ -176,7 +207,7 @@ See an [advanced example on github](https://github.com/jochen-schweizer/express-
|
||||
## koa v2 example
|
||||
|
||||
```javascript
|
||||
const promBundle = require("express-prom-bundle");
|
||||
const promBundle = require("@breizhhardware/express-prom-bundle");
|
||||
const Koa = require("koa");
|
||||
const c2k = require("koa-connect");
|
||||
const metricsMiddleware = promBundle({/* options */ });
|
||||
@@ -197,7 +228,8 @@ which returns an aggregate of all metrics from all the workers.
|
||||
|
||||
``` javascript
|
||||
const cluster = require('cluster');
|
||||
const promBundle = require('./src/index');
|
||||
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');
|
||||
|
||||
@@ -213,6 +245,7 @@ if (cluster.isMaster) {
|
||||
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
|
||||
|
||||
12
SECURITY.md
Normal file
12
SECURITY.md
Normal 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.
|
||||
@@ -3,6 +3,8 @@
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const promClient = require('prom-client');
|
||||
|
||||
// replace this with require('.') when running from library code
|
||||
const promBundle = require('express-prom-bundle');
|
||||
|
||||
const bundle = promBundle({
|
||||
@@ -19,7 +21,8 @@ const bundle = promBundle({
|
||||
urlValueParser: {
|
||||
minHexLength: 5,
|
||||
extraMasks: [
|
||||
"^[0-9]+\\.[0-9]+\\.[0-9]+$" // replace dot-separated dates with #val
|
||||
"^[0-9]+\\.[0-9]+\\.[0-9]+$", // replace dot-separated dates with #val, (regex as string)
|
||||
/^[0-9]+\-[0-9]+\-[0-9]+$/ // replace dash-separated dates with #val (actual regex)
|
||||
]
|
||||
},
|
||||
normalizePath: [
|
||||
|
||||
77
eslint.config.mjs
Normal file
77
eslint.config.mjs
Normal 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,
|
||||
},
|
||||
}]);
|
||||
7220
package-lock.json
generated
7220
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
42
package.json
42
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "express-prom-bundle",
|
||||
"version": "6.3.2",
|
||||
"name": "@breizhhardware/express-prom-bundle",
|
||||
"version": "8.0.7",
|
||||
"description": "express middleware with popular prometheus metrics in one bundle",
|
||||
"main": "src/index.js",
|
||||
"keywords": [
|
||||
@@ -14,43 +14,45 @@
|
||||
"src",
|
||||
"types/index.d.ts"
|
||||
],
|
||||
"types": "types",
|
||||
"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",
|
||||
"dtslint": "dtslint types",
|
||||
"dtslint-next": "dtslint --onlyTestTsNext types"
|
||||
"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",
|
||||
"url-value-parser": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.16.1",
|
||||
"coveralls": "^3.0.2",
|
||||
"dtslint": "^0.7.1",
|
||||
"eslint": "^5.11.0",
|
||||
"express": "^4.16.4",
|
||||
"istanbul": "^0.4.5",
|
||||
"@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": "^2.6.2",
|
||||
"koa": "^3.0.1",
|
||||
"koa-connect": "^2.0.1",
|
||||
"prom-client": "^13.0.0",
|
||||
"supertest": "^3.3.0",
|
||||
"prom-client": "^15.0.0",
|
||||
"supertest": "^7.1.4",
|
||||
"supertest-koa-agent": "^0.3.0",
|
||||
"typescript": "^3.4.5"
|
||||
"tsd": "^0.33.0",
|
||||
"typescript": "^5.9.3",
|
||||
"nyc": "^17.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prom-client": ">=12.0.0"
|
||||
"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": ">=10"
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,20 @@ 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();
|
||||
@@ -158,10 +172,9 @@ describe('index', () => {
|
||||
agent
|
||||
.get('/test')
|
||||
.end(() => {
|
||||
const metricHashMap = instance.metrics.http_request_duration_seconds.hashMap;
|
||||
expect(metricHashMap['status_code:200']).toBeDefined();
|
||||
const labeled = metricHashMap['status_code:200'];
|
||||
expect(labeled.count).toBe(1);
|
||||
const bucket = extractBucket(instance, 'status_code:200');
|
||||
expect(bucket).toBeDefined();
|
||||
expect(bucket.count).toBe(1);
|
||||
|
||||
agent
|
||||
.get('/metrics')
|
||||
@@ -206,7 +219,7 @@ describe('index', () => {
|
||||
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/)
|
||||
return !!req.url.match(/test|bad.word|metrics/);
|
||||
}
|
||||
});
|
||||
app.use(instance);
|
||||
@@ -223,11 +236,11 @@ describe('index', () => {
|
||||
agent
|
||||
.get('/good-word')
|
||||
.end(() => {
|
||||
const metricHashMap = instance.metrics.http_request_duration_seconds.hashMap;
|
||||
expect(metricHashMap['status_code:200']).toBeDefined();
|
||||
const bucket = extractBucket(instance, 'status_code:200');
|
||||
expect(bucket).toBeDefined();
|
||||
|
||||
// only /good-word should be counted
|
||||
expect(metricHashMap['status_code:200'].count).toBe(1);
|
||||
expect(bucket.count).toBe(1);
|
||||
|
||||
agent
|
||||
.get('/metrics')
|
||||
@@ -240,6 +253,44 @@ describe('index', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
@@ -295,7 +346,7 @@ describe('index', () => {
|
||||
|
||||
describe('usage of normalizePath()', () => {
|
||||
|
||||
it('normalizePath can be replaced gloablly', done => {
|
||||
it('normalizePath can be replaced globally', done => {
|
||||
const app = express();
|
||||
const original = bundle.normalizePath;
|
||||
bundle.normalizePath = () => 'dummy';
|
||||
@@ -319,6 +370,40 @@ describe('index', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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({
|
||||
@@ -409,6 +494,27 @@ describe('index', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('handles errors in collectors', done => {
|
||||
const app = express();
|
||||
const instance = bundle({});
|
||||
app.use(instance);
|
||||
|
||||
new promClient.Gauge({
|
||||
name: 'kaboom',
|
||||
help: 'this metric explodes',
|
||||
collect() {
|
||||
throw new Error('kaboom!');
|
||||
}
|
||||
});
|
||||
|
||||
// the error will NOT be displayed if NODE_ENV=test (as defined in package.json)
|
||||
|
||||
supertest(app)
|
||||
.get('/metrics')
|
||||
.expect(500)
|
||||
.end((err) => done(err));
|
||||
});
|
||||
|
||||
it('customLabels={foo: "bar"} adds foo="bar" label to metrics', done => {
|
||||
const app = express();
|
||||
const instance = bundle({
|
||||
@@ -478,31 +584,53 @@ describe('index', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('calls promClient.collectDefaultMetrics', () => {
|
||||
const spy = spyOn(promClient, 'collectDefaultMetrics');
|
||||
bundle({
|
||||
promClient: {
|
||||
collectDefaultMetrics: {
|
||||
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();
|
||||
});
|
||||
});
|
||||
expect(spy).toHaveBeenCalledWith({});
|
||||
});
|
||||
|
||||
describe('usage of clusterMetrics()', () => {
|
||||
it('clusterMetrics returns 200 even without a cluster', (done) => {
|
||||
const app = express();
|
||||
const app = express();
|
||||
|
||||
cluster.workers = [];
|
||||
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();
|
||||
});
|
||||
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) => {
|
||||
@@ -569,5 +697,23 @@ describe('index', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,4 +28,9 @@ describe('normalizePath', () => {
|
||||
});
|
||||
expect(subject).toThrow();
|
||||
});
|
||||
|
||||
it('uses urlPathReplacement when passed to transform the path', () => {
|
||||
expect(normalizePath({url: '/a/12345'}, {urlPathReplacement: ':id'}))
|
||||
.toBe('/a/:id');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,11 @@ const normalizeStatusCode = require('../src/normalizeStatusCode');
|
||||
describe('normalizeStatusCode', () => {
|
||||
it('returns run callback if configured', () => {
|
||||
expect(
|
||||
normalizeStatusCode({status_code: 500})
|
||||
normalizeStatusCode({status_code: 500, headersSent: true})
|
||||
).toBe(500);
|
||||
});
|
||||
|
||||
it('returns 499 if headers are not sent', () => {
|
||||
expect(normalizeStatusCode({statusCode: 200, headersSent: false})).toBe(499);
|
||||
});
|
||||
});
|
||||
|
||||
46
src/index.js
46
src/index.js
@@ -68,7 +68,8 @@ function main(opts) {
|
||||
formatStatusCode: main.normalizeStatusCode,
|
||||
metricType: 'histogram',
|
||||
promClient: {},
|
||||
promRegistry: promClient.register
|
||||
promRegistry: promClient.register,
|
||||
metricsApp: null,
|
||||
}, opts
|
||||
);
|
||||
|
||||
@@ -90,6 +91,7 @@ function main(opts) {
|
||||
}
|
||||
|
||||
const httpMetricName = opts.httpDurationMetricName || 'http_request_duration_seconds';
|
||||
const upMetricName = opts.upMetricName || 'up';
|
||||
|
||||
function makeHttpMetric() {
|
||||
const labels = ['status_code'];
|
||||
@@ -111,7 +113,8 @@ function main(opts) {
|
||||
percentiles: opts.percentiles || [0.5, 0.75, 0.95, 0.98, 0.99, 0.999],
|
||||
maxAgeSeconds: opts.maxAgeSeconds,
|
||||
ageBuckets: opts.ageBuckets,
|
||||
registers: [opts.promRegistry]
|
||||
registers: [opts.promRegistry],
|
||||
pruneAgedBuckets: opts.pruneAgedBuckets
|
||||
});
|
||||
} else if (opts.metricType === 'histogram' || !opts.metricType) {
|
||||
return new promClient.Histogram({
|
||||
@@ -131,8 +134,12 @@ function main(opts) {
|
||||
};
|
||||
|
||||
if (opts.includeUp !== false) {
|
||||
let prefix = '';
|
||||
if (opts.promClient && opts.promClient.collectDefaultMetrics) {
|
||||
prefix = opts.promClient.collectDefaultMetrics.prefix || '';
|
||||
}
|
||||
metrics.up = new promClient.Gauge({
|
||||
name: 'up',
|
||||
name: prefix + upMetricName,
|
||||
help: '1 = up, 0 = not up',
|
||||
registers: [opts.promRegistry]
|
||||
});
|
||||
@@ -140,33 +147,44 @@ function main(opts) {
|
||||
}
|
||||
|
||||
const metricsMiddleware = function(req, res, next) {
|
||||
res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||
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 => res.end(output))
|
||||
.then(output => sendSuccesss(output))
|
||||
.catch(err => next(err));
|
||||
} else {
|
||||
// compatibility fallback for previous versions of prom-client@<=12
|
||||
res.end(metricsResponse);
|
||||
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);
|
||||
return metricsMiddleware(req, res, next);
|
||||
}
|
||||
|
||||
// bypass() is checked only after /metrics was processed
|
||||
// if you wish to disable /metrics use autoregister:false instead
|
||||
if (opts.bypass && opts.bypass(req)) {
|
||||
if (opts.bypass.onRequest && opts.bypass.onRequest(req)) {
|
||||
return next();
|
||||
}
|
||||
|
||||
@@ -178,6 +196,10 @@ function main(opts) {
|
||||
const timer = metrics[httpMetricName].startTimer(labels);
|
||||
|
||||
onFinished(res, () => {
|
||||
if (opts.bypass.onFinish && opts.bypass.onFinish(req, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.includeStatusCode) {
|
||||
labels.status_code = opts.formatStatusCode(res, opts);
|
||||
}
|
||||
@@ -201,6 +223,14 @@ function main(opts) {
|
||||
next();
|
||||
};
|
||||
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
||||
middleware.metrics = metrics;
|
||||
middleware.promClient = promClient;
|
||||
middleware.metricsMiddleware = metricsMiddleware;
|
||||
|
||||
@@ -11,6 +11,7 @@ module.exports = function(req, opts) {
|
||||
// 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)) {
|
||||
@@ -26,5 +27,5 @@ module.exports = function(req, opts) {
|
||||
if (!urlValueParser) {
|
||||
urlValueParser = new UrlValueParser(opts && opts.urlValueParser);
|
||||
}
|
||||
return urlValueParser.replacePathValues(path);
|
||||
return urlValueParser.replacePathValues(path, urlPathReplacement);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const CLIENT_CLOSED_REQUEST_CODE = 499;
|
||||
|
||||
module.exports = function(res) {
|
||||
return res.status_code || res.statusCode;
|
||||
if (res.headersSent) {
|
||||
return res.status_code || res.statusCode;
|
||||
} else {
|
||||
return CLIENT_CLOSED_REQUEST_CODE;
|
||||
}
|
||||
};
|
||||
|
||||
43
types/index.d.ts
vendored
43
types/index.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
// TypeScript Version: 2.8
|
||||
|
||||
import { Request, RequestHandler, Response } from 'express';
|
||||
import { DefaultMetricsCollectorConfiguration, Registry } from 'prom-client';
|
||||
import { Request, RequestHandler, Response, Express } from 'express';
|
||||
import { DefaultMetricsCollectorConfiguration, Registry, RegistryContentType } from 'prom-client';
|
||||
|
||||
export {};
|
||||
|
||||
@@ -17,9 +17,8 @@ declare namespace express_prom_bundle {
|
||||
type NormalizeStatusCodeFn = (res: Response) => number | string;
|
||||
type TransformLabelsFn = (labels: Labels, req: Request, res: Response) => void;
|
||||
|
||||
interface Opts {
|
||||
interface BaseOptions {
|
||||
autoregister?: boolean;
|
||||
buckets?: number[];
|
||||
|
||||
customLabels?: { [key: string]: any };
|
||||
|
||||
@@ -28,26 +27,52 @@ declare namespace express_prom_bundle {
|
||||
includePath?: boolean;
|
||||
includeUp?: boolean;
|
||||
|
||||
bypass?: (req: Request) => boolean;
|
||||
bypass?:
|
||||
| ((req: Request) => boolean)
|
||||
| {
|
||||
onRequest?: (req: Request) => boolean;
|
||||
onFinish?: (req: Request, res: Response) => boolean;
|
||||
};
|
||||
|
||||
excludeRoutes?: Array<string | RegExp>;
|
||||
|
||||
metricType?: 'summary' | 'histogram';
|
||||
metricsPath?: string;
|
||||
httpDurationMetricName?: string;
|
||||
promClient?: { collectDefaultMetrics?: DefaultMetricsCollectorConfiguration };
|
||||
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?: string[];
|
||||
extraMasks?: string[];
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { Request, RequestHandler, Response } from 'express';
|
||||
import express, { RequestHandler } from 'express';
|
||||
import { expectType } from 'tsd'
|
||||
import * as promClient from 'prom-client';
|
||||
import promBundle, {
|
||||
type Middleware
|
||||
} from '..';
|
||||
|
||||
import * as promBundle from 'express-prom-bundle';
|
||||
const middleware: express.RequestHandler = promBundle({ includeMethod: true });
|
||||
|
||||
// $ExpectType Middleware
|
||||
const middleware: RequestHandler = promBundle({ includeMethod: true });
|
||||
|
||||
// $ExpectType: string
|
||||
middleware.name;
|
||||
expectType<string>(middleware.name);
|
||||
|
||||
promClient.register.clear();
|
||||
|
||||
// $ExpectType Middleware
|
||||
promBundle({
|
||||
expectType<Middleware>(promBundle({
|
||||
normalizePath: [
|
||||
// collect paths like "/customer/johnbobson" as just one "/custom/#name"
|
||||
['^/customer/.*', '/customer/#name'],
|
||||
@@ -26,21 +25,21 @@ promBundle({
|
||||
'ORD[0-9]{5,}' // replace strings like ORD1243423, ORD673562 as #val
|
||||
]
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
promClient.register.clear();
|
||||
|
||||
// $ExpectType Middleware
|
||||
promBundle({
|
||||
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: 'summary',
|
||||
metricType: 'histogram',
|
||||
metricsPath: '/prometheus',
|
||||
promClient: {
|
||||
collectDefaultMetrics: {
|
||||
@@ -56,24 +55,37 @@ promBundle({
|
||||
normalizePath: [
|
||||
['^/foo', '/example'] // replace /foo with /example
|
||||
],
|
||||
formatStatusCode: (res: Response) => res.statusCode + 100
|
||||
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: Request, opts: promBundle.Opts) => {
|
||||
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: Response) => res.statusCode.toString();
|
||||
wPromBundle.normalizeStatusCode = (res: express.Response) => res.statusCode.toString();
|
||||
|
||||
// $ExpectType RequestHandler
|
||||
promBundle.clusterMetrics();
|
||||
expectType<RequestHandler>(promBundle.clusterMetrics());
|
||||
|
||||
// Missing test
|
||||
// const stringReturn: string = promBundle.normalizePath({}, {});
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"lib": ["es6"],
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"baseUrl": ".",
|
||||
"paths": { "express-prom-bundle": ["."] },
|
||||
"noEmit": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "dtslint/dtslint.json"
|
||||
}
|
||||
Reference in New Issue
Block a user