mirror of
https://github.com/BreizhHardware/express-prom-bundle.git
synced 2026-01-19 16:57:29 +01:00
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef4fe2c5b5 | ||
|
|
dcc9e1f893 | ||
|
|
02a5cb67c3 | ||
|
|
36d1484d33 | ||
|
|
8fc61cd1d8 | ||
|
|
4fd7a797c5 | ||
|
|
30d9c3d473 | ||
|
|
10fe1cac8f | ||
|
|
9b4e0eb163 | ||
|
|
475b7a8f6d | ||
|
|
c1482220c2 | ||
|
|
66643411b8 | ||
|
|
38abc2b1a5 | ||
|
|
97b2f425e6 | ||
|
|
a8d4fa8116 | ||
|
|
0eb0178622 | ||
|
|
5c1f68a482 | ||
|
|
12ff62478f | ||
|
|
2efc5189d0 | ||
|
|
c28b736c6e | ||
|
|
e84b96bf5e | ||
|
|
8a88d14dcd | ||
|
|
eb1a8f08ab | ||
|
|
648d88ab7c | ||
|
|
8982831816 | ||
|
|
e49f95ab0e | ||
|
|
b451ab0f97 | ||
|
|
cf862fca31 | ||
|
|
0f6eab8d98 | ||
|
|
b586bd9515 | ||
|
|
1a5f6df7f9 | ||
|
|
d1d980136f | ||
|
|
b2f5283303 | ||
|
|
cdc558a8d8 | ||
|
|
af8a3097fc | ||
|
|
c5cce9f8f5 | ||
|
|
36789b54ae | ||
|
|
e1b09bca43 | ||
|
|
c3284c6ce6 | ||
|
|
1c7935b377 | ||
|
|
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 | ||
|
|
c541824657 | ||
|
|
7eae5d4b7f |
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
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,4 +2,5 @@
|
||||
node_modules
|
||||
coverage
|
||||
/.vscode
|
||||
|
||||
.env
|
||||
.nyc_output
|
||||
@@ -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
|
||||
|
||||
11
Makefile
11
Makefile
@@ -6,14 +6,7 @@ lint:
|
||||
npx eslint src
|
||||
npm run dtslint-next
|
||||
coverage:
|
||||
node_modules/istanbul/lib/cli.js cover \
|
||||
-i 'src/*' \
|
||||
--include-all-sources \
|
||||
--dir coverage \
|
||||
node_modules/jasme/run.js
|
||||
npx nyc --include src --reporter=lcov --reporter=text node_modules/jasme/run.js
|
||||
|
||||
coveralls: coverage
|
||||
ifndef COVERALLS_REPO_TOKEN
|
||||
$(error COVERALLS_REPO_TOKEN is undefined)
|
||||
endif
|
||||
node_modules/coveralls/bin/coveralls.js < coverage/lcov.info
|
||||
npx nyc report --reporter=text-lcov | npx coveralls
|
||||
|
||||
32
README.md
32
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});
|
||||
|
||||
@@ -53,6 +56,7 @@ Which labels to include in `http_request_duration_seconds` metric:
|
||||
* **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 ###
|
||||
|
||||
@@ -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 ###
|
||||
|
||||
@@ -159,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:
|
||||
@@ -166,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
|
||||
@@ -191,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 */ });
|
||||
@@ -212,7 +228,7 @@ which returns an aggregate of all metrics from all the workers.
|
||||
|
||||
``` javascript
|
||||
const cluster = require('cluster');
|
||||
const promBundle = require('express-prom-bundle');
|
||||
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');
|
||||
|
||||
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({
|
||||
|
||||
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,
|
||||
},
|
||||
}]);
|
||||
7190
package-lock.json
generated
7190
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
40
package.json
40
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "express-prom-bundle",
|
||||
"version": "6.6.0",
|
||||
"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_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')
|
||||
@@ -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')
|
||||
@@ -266,12 +279,10 @@ describe('index', () => {
|
||||
.expect(500);
|
||||
})
|
||||
.then(() => {
|
||||
const metricHashMap = instance.metrics.http_request_duration_seconds.hashMap;
|
||||
|
||||
// only /200 and /500 should be counted
|
||||
expect(metricHashMap['status_code:200'].count).toBe(1);
|
||||
expect(metricHashMap['status_code:404']).not.toBeDefined();
|
||||
expect(metricHashMap['status_code:500'].count).toBe(1);
|
||||
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')
|
||||
@@ -335,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';
|
||||
@@ -359,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({
|
||||
@@ -539,15 +584,37 @@ 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()', () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
10
src/index.js
10
src/index.js
@@ -91,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'];
|
||||
@@ -112,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({
|
||||
@@ -132,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]
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
34
types/index.d.ts
vendored
34
types/index.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
// TypeScript Version: 2.8
|
||||
|
||||
import { Request, RequestHandler, Response, Express } from 'express';
|
||||
import { DefaultMetricsCollectorConfiguration, Registry } from 'prom-client';
|
||||
import { DefaultMetricsCollectorConfiguration, Registry, RegistryContentType } from 'prom-client';
|
||||
|
||||
export {};
|
||||
|
||||
@@ -17,7 +17,7 @@ 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;
|
||||
|
||||
customLabels?: { [key: string]: any };
|
||||
@@ -36,19 +36,10 @@ declare namespace express_prom_bundle {
|
||||
|
||||
excludeRoutes?: Array<string | RegExp>;
|
||||
|
||||
metricType?: 'summary' | 'histogram';
|
||||
|
||||
// https://github.com/siimon/prom-client#histogram
|
||||
buckets?: number[];
|
||||
|
||||
// https://github.com/siimon/prom-client#summary
|
||||
percentiles?: number[];
|
||||
maxAgeSeconds?: number;
|
||||
ageBuckets?: number;
|
||||
|
||||
metricsPath?: string;
|
||||
httpDurationMetricName?: string;
|
||||
promClient?: { collectDefaultMetrics?: DefaultMetricsCollectorConfiguration };
|
||||
upMetricName?: string;
|
||||
promClient?: { collectDefaultMetrics?: DefaultMetricsCollectorConfiguration<RegistryContentType> };
|
||||
promRegistry?: Registry;
|
||||
normalizePath?: NormalizePathEntry[] | NormalizePathFn;
|
||||
formatStatusCode?: NormalizeStatusCodeFn;
|
||||
@@ -65,6 +56,23 @@ declare namespace express_prom_bundle {
|
||||
};
|
||||
}
|
||||
|
||||
/** @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,19 +1,17 @@
|
||||
import * as express 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';
|
||||
|
||||
// $ExpectType Middleware
|
||||
const middleware: express.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'],
|
||||
@@ -27,12 +25,11 @@ 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,
|
||||
@@ -60,7 +57,7 @@ promBundle({
|
||||
],
|
||||
formatStatusCode: (res: express.Response) => res.statusCode + 100,
|
||||
metricsApp: express()
|
||||
});
|
||||
}));
|
||||
|
||||
promClient.register.clear();
|
||||
|
||||
@@ -90,8 +87,5 @@ wPromBundle.normalizePath = (req: express.Request, opts: promBundle.Opts) => {
|
||||
|
||||
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