@apio/express-prometheus
Advanced tools
Comparing version 1.0.0 to 2.0.0
@@ -1,5 +0,5 @@ | ||
import * as promClient from "prom-client"; | ||
import * as express from "express"; | ||
import * as promClient from 'prom-client' | ||
import * as express from 'express' | ||
export const client: typeof promClient; | ||
export declare function instrument (server: express.Express): void; | ||
export const client: typeof promClient | ||
export declare function instrument (server: express.Express): void; |
@@ -1,4 +0,4 @@ | ||
const client = require("prom-client") | ||
const client = require('prom-client') | ||
client.collectDefaultMetrics(); | ||
client.collectDefaultMetrics() | ||
@@ -9,41 +9,35 @@ const metric = { | ||
clients: new client.Gauge({ | ||
name: "http_requests_processing", | ||
help: "Http requests in progress", | ||
labelNames: ["method", "path", "status"] | ||
name: 'http_requests_processing', | ||
help: 'Http requests in progress', | ||
labelNames: ['method', 'path', 'status'] | ||
}), | ||
throughput: new client.Counter({ | ||
name: "http_requests_total", | ||
help: "Http request throughput", | ||
labelNames: ["method", "path", "status"] | ||
name: 'http_requests_total', | ||
help: 'Http request throughput', | ||
labelNames: ['method', 'path', 'status'] | ||
}), | ||
duration: new client.Histogram({ | ||
name: "http_request_duration_seconds", | ||
help: "Request duration histogram in seconds", | ||
labelNames: ["method", "path", "status"] | ||
name: 'http_request_duration_seconds', | ||
help: 'Request duration histogram in seconds', | ||
labelNames: ['method', 'path', 'status'] | ||
}) | ||
} | ||
} | ||
}; | ||
function defaultOptions(options) { | ||
options = options || {}; | ||
options.url = options.url || "/metrics"; | ||
return options; | ||
} | ||
function isPathExcluded(options, path) { | ||
const isPathExcluded = (options, path) => { | ||
if (!options.excludePaths) { | ||
return false | ||
} else { | ||
return options.excludePaths.some(pathToExclude => { | ||
let regExp = new RegExp(pathToExclude) | ||
return regExp.test(path) | ||
}) | ||
} | ||
return options.excludePaths.some(pathToExclude => { | ||
const regExp = new RegExp(pathToExclude) | ||
return regExp.test(path) | ||
}) | ||
} | ||
function instrument(server, options) { | ||
const opt = defaultOptions(options); | ||
const instrument = (server, options = {}) => { | ||
const opt = Object.assign({ url: '/metrics' }, options) | ||
function middleware(req, res, next) { | ||
const middleware = (req, res, next) => { | ||
// If path is the excluded paths list, we don't add a metric for it :) | ||
@@ -53,7 +47,8 @@ if (isPathExcluded(opt, req.path)) { | ||
} | ||
if (req.path !== opt.url) { | ||
const end = metric.http.requests.duration.startTimer(); | ||
metric.http.requests.clients.inc(1, Date.now()); | ||
const end = metric.http.requests.duration.startTimer() | ||
metric.http.requests.clients.inc(1, Date.now()) | ||
res.on("finish", function () { | ||
res.on('finish', () => { | ||
const labels = { | ||
@@ -63,23 +58,28 @@ method: req.method, | ||
status: res.statusCode | ||
}; | ||
} | ||
metric.http.requests.clients.dec(1, Date.now()); | ||
metric.http.requests.throughput.inc(labels, 1, Date.now()); | ||
end(labels); | ||
}); | ||
metric.http.requests.clients.dec(1, Date.now()) | ||
metric.http.requests.throughput.inc(labels, 1, Date.now()) | ||
end(labels) | ||
}) | ||
} | ||
return next(); | ||
next() | ||
} | ||
server.use(middleware); | ||
server.use(middleware) | ||
server.get(opt.url, (req, res) => { | ||
res.header("content-type", "text/plain; charset=utf-8"); | ||
return res.send(client.register.metrics()); | ||
}); | ||
server.get(opt.url, async (req, res, next) => { | ||
try { | ||
res.header('content-type', 'text/plain; charset=utf-8') | ||
res.send(await client.register.metrics()) | ||
} catch (e) { | ||
next(e) | ||
} | ||
}) | ||
} | ||
module.exports = { | ||
client, | ||
instrument | ||
}; | ||
} |
@@ -1,32 +0,33 @@ | ||
const supertest = require("supertest"); | ||
const express = require("express"); | ||
const tricorder = require("./index"); | ||
const supertest = require('supertest') | ||
const express = require('express') | ||
const expressPrometheus = require('./index') | ||
describe("index", () => { | ||
let sut; | ||
describe('index', () => { | ||
let sut | ||
describe("options", () => { | ||
describe('options', () => { | ||
beforeEach(() => { | ||
sut = express(); | ||
}); | ||
sut = express() | ||
}) | ||
test("When not given url, should default to metrics", () => { | ||
test('When not given url, should default to metrics', () => { | ||
// Arrange | ||
tricorder.instrument(sut); | ||
expressPrometheus.instrument(sut) | ||
// Act | ||
return supertest(sut) | ||
.get("/metrics") | ||
.get('/metrics') | ||
.then(result => { | ||
// Assert | ||
expect(result.statusCode).toBe(200); | ||
}); | ||
}); | ||
expect(result.statusCode).toBe(200) | ||
}) | ||
}) | ||
test("When given url, should default to url value", () => { | ||
test('When given url, should default to url value', () => { | ||
// Arrange | ||
const options = { | ||
url: "/metric-route" | ||
}; | ||
tricorder.instrument(sut, options); | ||
url: '/metric-route' | ||
} | ||
expressPrometheus.instrument(sut, options) | ||
// Act | ||
@@ -37,191 +38,190 @@ return supertest(sut) | ||
// Assert | ||
expect(result.statusCode).toBe(200); | ||
}); | ||
}); | ||
}); | ||
expect(result.statusCode).toBe(200) | ||
}) | ||
}) | ||
}) | ||
describe("metrics", () => { | ||
let agent; | ||
describe('metrics', () => { | ||
let agent | ||
beforeEach(() => { | ||
sut = express(); | ||
tricorder.instrument(sut); | ||
sut = express() | ||
expressPrometheus.instrument(sut) | ||
sut.get("/resource", (req, res) => { | ||
res.send(); | ||
}); | ||
sut.get('/resource', (req, res) => { | ||
res.send() | ||
}) | ||
sut.get("/resource/:id", (req, res) => { | ||
res.send(); | ||
}); | ||
sut.get('/resource/:id', (req, res) => { | ||
res.send() | ||
}) | ||
agent = supertest.agent(sut); | ||
}); | ||
agent = supertest.agent(sut) | ||
}) | ||
describe("http_requests_processing", () => { | ||
test("Given metrics are running, should include http_requests_processing", () => { | ||
describe('http_requests_processing', () => { | ||
test('Given metrics are running, should include http_requests_processing', () => { | ||
// Arrange | ||
// Act | ||
return agent.get("/resource").then(() => { | ||
return agent.get("/metrics").then(result => { | ||
expect(result.statusCode).toBe(200); | ||
expect(result.headers["content-type"]).toBe( | ||
"text/plain; charset=utf-8" | ||
); | ||
expect(result.text).toContain("http_requests_processing 0"); | ||
}); | ||
}); | ||
}); | ||
}); | ||
return agent.get('/resource').then(() => { | ||
return agent.get('/metrics').then(result => { | ||
expect(result.statusCode).toBe(200) | ||
expect(result.headers['content-type']).toBe( | ||
'text/plain; charset=utf-8' | ||
) | ||
expect(result.text).toContain('http_requests_processing 0') | ||
}) | ||
}) | ||
}) | ||
}) | ||
describe("http_requests_total", () => { | ||
test("Given resource has been request, should being included in http_requests_total", () => { | ||
describe('http_requests_total', () => { | ||
test('Given resource has been request, should being included in http_requests_total', () => { | ||
// Arrange | ||
// Act | ||
return agent.get("/resource").then(() => { | ||
return agent.get("/metrics").then(result => { | ||
expect(result.statusCode).toBe(200); | ||
expect(result.headers["content-type"]).toBe( | ||
"text/plain; charset=utf-8" | ||
); | ||
return agent.get('/resource').then(() => { | ||
return agent.get('/metrics').then(result => { | ||
expect(result.statusCode).toBe(200) | ||
expect(result.headers['content-type']).toBe( | ||
'text/plain; charset=utf-8' | ||
) | ||
expect(result.text).toContain( | ||
'http_requests_total{method="GET",path="/resource",status="200"} 2' | ||
); | ||
}); | ||
}); | ||
}); | ||
) | ||
}) | ||
}) | ||
}) | ||
test("Given resource/:id has been request, should being included in http_requests_total", () => { | ||
test('Given resource/:id has been request, should being included in http_requests_total', () => { | ||
// Arrange | ||
const id = 8; | ||
const id = 8 | ||
// Act | ||
return agent.get(`/resource/${id}`).then(() => { | ||
return agent.get("/metrics").then(result => { | ||
expect(result.statusCode).toBe(200); | ||
expect(result.headers["content-type"]).toBe( | ||
"text/plain; charset=utf-8" | ||
); | ||
return agent.get('/metrics').then(result => { | ||
expect(result.statusCode).toBe(200) | ||
expect(result.headers['content-type']).toBe( | ||
'text/plain; charset=utf-8' | ||
) | ||
expect(result.text).toContain( | ||
'http_requests_total{method="GET",path="/resource/:id",status="200"} 1' | ||
); | ||
}); | ||
}); | ||
}); | ||
}); | ||
) | ||
}) | ||
}) | ||
}) | ||
}) | ||
describe("http_request_duration_seconds", () => { | ||
test("Given resource has been request, should being included in http_request_duration_seconds", () => { | ||
describe('http_request_duration_seconds', () => { | ||
test('Given resource has been request, should being included in http_request_duration_seconds', () => { | ||
// Arrange | ||
// Act | ||
return agent.get("/resource").then(() => { | ||
return agent.get("/metrics").then(result => { | ||
expect(result.statusCode).toBe(200); | ||
expect(result.headers["content-type"]).toBe( | ||
"text/plain; charset=utf-8" | ||
); | ||
return agent.get('/resource').then(() => { | ||
return agent.get('/metrics').then(result => { | ||
expect(result.statusCode).toBe(200) | ||
expect(result.headers['content-type']).toBe( | ||
'text/plain; charset=utf-8' | ||
) | ||
expect(result.text).toContain( | ||
'http_request_duration_seconds_bucket{le="10",method="GET",path="/resource",status="200"} 3' | ||
); | ||
}); | ||
}); | ||
}); | ||
) | ||
}) | ||
}) | ||
}) | ||
test("Given resource/:id has been request, should being included in http_request_duration_seconds", () => { | ||
test('Given resource/:id has been request, should being included in http_request_duration_seconds', () => { | ||
// Arrange | ||
const id = 3; | ||
const id = 3 | ||
// Act | ||
return agent.get(`/resource/${id}`).then(() => { | ||
return agent.get("/metrics").then(result => { | ||
expect(result.statusCode).toBe(200); | ||
expect(result.headers["content-type"]).toBe( | ||
"text/plain; charset=utf-8" | ||
); | ||
return agent.get('/metrics').then(result => { | ||
expect(result.statusCode).toBe(200) | ||
expect(result.headers['content-type']).toBe( | ||
'text/plain; charset=utf-8' | ||
) | ||
expect(result.text).toContain( | ||
'http_request_duration_seconds_bucket{le="10",method="GET",path="/resource/:id",status="200"} 2' | ||
); | ||
}); | ||
}); | ||
}); | ||
}); | ||
) | ||
}) | ||
}) | ||
}) | ||
}) | ||
describe("third party metrics", () => { | ||
test("Given metrics running, should include default node metrics", () => { | ||
describe('third party metrics', () => { | ||
test('Given metrics running, should include default node metrics', () => { | ||
// Arrange | ||
// Act | ||
return agent.get("/metrics").then(result => { | ||
return agent.get('/metrics').then(result => { | ||
// Assert | ||
expect(result.text).toContain("nodejs_active_handles_total"); | ||
expect(result.text).toContain("nodejs_active_requests_total"); | ||
expect(result.text).toContain("nodejs_external_memory_bytes"); | ||
}); | ||
}); | ||
}); | ||
}); | ||
expect(result.text).toContain('nodejs_active_handles_total') | ||
expect(result.text).toContain('nodejs_active_requests_total') | ||
expect(result.text).toContain('nodejs_external_memory_bytes') | ||
}) | ||
}) | ||
}) | ||
}) | ||
describe("labels", () => { | ||
let agent; | ||
describe('labels', () => { | ||
let agent | ||
beforeEach(() => { | ||
sut = express(); | ||
tricorder.instrument(sut); | ||
sut = express() | ||
expressPrometheus.instrument(sut) | ||
const router = express.Router(); | ||
const router = express.Router() | ||
router.get("/", (req, res) => { | ||
res.send(); | ||
}); | ||
router.get('/', (req, res) => { | ||
res.send() | ||
}) | ||
router.get("/:id", (req, res) => { | ||
res.send(); | ||
}); | ||
router.get('/:id', (req, res) => { | ||
res.send() | ||
}) | ||
sut.use("/resource-1", router); | ||
sut.use("/resource-2", router); | ||
sut.use('/resource-1', router) | ||
sut.use('/resource-2', router) | ||
sut.get("/resource-3/", (req, res) => { | ||
res.send(); | ||
}); | ||
sut.get('/resource-3/', (req, res) => { | ||
res.send() | ||
}) | ||
sut.get("/resource-3/:id", (req, res) => { | ||
res.send(); | ||
}); | ||
sut.get('/resource-3/:id', (req, res) => { | ||
res.send() | ||
}) | ||
agent = supertest.agent(sut); | ||
}); | ||
agent = supertest.agent(sut) | ||
}) | ||
test("Given a router route is called, should calculate path correctly", () => { | ||
test('Given a router route is called, should calculate path correctly', () => { | ||
// Arrange | ||
// Act | ||
return agent.get("/resource-1/id-thing").then(() => { | ||
return agent.get("/metrics").then(result => { | ||
expect(result.statusCode).toBe(200); | ||
expect(result.headers["content-type"]).toBe( | ||
"text/plain; charset=utf-8" | ||
); | ||
return agent.get('/resource-1/id-thing').then(() => { | ||
return agent.get('/metrics').then(result => { | ||
expect(result.statusCode).toBe(200) | ||
expect(result.headers['content-type']).toBe( | ||
'text/plain; charset=utf-8' | ||
) | ||
expect(result.text).toContain( | ||
'http_requests_total{method="GET",path="/resource-1/:id",status="200"} 1' | ||
); | ||
}); | ||
}); | ||
}); | ||
) | ||
}) | ||
}) | ||
}) | ||
test("Given a app route is called, should calculate path correctly", () => { | ||
test('Given a app route is called, should calculate path correctly', () => { | ||
// Arrange | ||
// Act | ||
return agent.get("/resource-3/id-thing-3").then(() => { | ||
return agent.get("/metrics").then(result => { | ||
expect(result.statusCode).toBe(200); | ||
expect(result.headers["content-type"]).toBe( | ||
"text/plain; charset=utf-8" | ||
); | ||
return agent.get('/resource-3/id-thing-3').then(() => { | ||
return agent.get('/metrics').then(result => { | ||
expect(result.statusCode).toBe(200) | ||
expect(result.headers['content-type']).toBe( | ||
'text/plain; charset=utf-8' | ||
) | ||
expect(result.text).toContain( | ||
'http_requests_total{method="GET",path="/resource-3/:id",status="200"} 1' | ||
); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) |
{ | ||
"name": "@apio/express-prometheus", | ||
"version": "1.0.0", | ||
"version": "2.0.0", | ||
"description": "Express middleware to provide basic metrics to Prometheus.", | ||
@@ -35,14 +35,19 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"@types/express": "^4.17.6", | ||
"prom-client": "^12.0.0" | ||
"@types/express": "^4.17.11", | ||
"prom-client": "^13.0.0" | ||
}, | ||
"devDependencies": { | ||
"eslint": "^6.8.0", | ||
"eslint-config-prettier": "^6.10.1", | ||
"@typescript-eslint/eslint-plugin": "^4.14.0", | ||
"@typescript-eslint/parser": "^4.14.0", | ||
"eslint": "^7.18.0", | ||
"eslint-config-standard": "^16.0.2", | ||
"eslint-plugin-import": "^2.22.1", | ||
"eslint-plugin-jest": "^24.1.3", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-promise": "^4.2.1", | ||
"express": "^4.17.1", | ||
"husky": "^4.2.5", | ||
"jest": "^25.3.0", | ||
"prettier": "^2.0.4", | ||
"supertest": "^4.0.2" | ||
"husky": "^4.3.8", | ||
"jest": "^26.6.3", | ||
"supertest": "^6.1.1" | ||
} | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
10493
288
12
2
+ Addedprom-client@13.2.0(transitive)
- Removedprom-client@12.0.0(transitive)
Updated@types/express@^4.17.11
Updatedprom-client@^13.0.0