From 8541a95a6aab829eaad7b046f6193d1839d23e07 Mon Sep 17 00:00:00 2001 From: Nigro Simone Date: Sat, 5 Sep 2026 06:57:44 +0200 Subject: [PATCH 1/2] fix: pino --- README.md | 1 + package-lock.json | 169 +++++++++++++++++++++++++- package.json | 1 + src/response.js | 4 + tests/tests/res/res-pino-http.js | 32 +++++ tests/tests/res/res-writable-ended.js | 19 +++ 6 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 tests/tests/res/res-pino-http.js create mode 100644 tests/tests/res/res-writable-ended.js diff --git a/README.md b/README.md index 6a609518..d4171673 100644 --- a/README.md +++ b/README.md @@ -315,6 +315,7 @@ Almost all middlewares that are compatible with Express are compatible with µEx - ✅ [helmet](https://www.npmjs.com/package/helmet) - ✅ [passport](https://www.npmjs.com/package/passport) - ✅ [morgan](https://www.npmjs.com/package/morgan) +- ✅ [pino-http](https://www.npmjs.com/package/pino-http) - ✅ [swagger-ui-express](https://www.npmjs.com/package/swagger-ui-express) - ✅ [graphql-http](https://www.npmjs.com/package/graphql-http) - ✅ [better-sse](https://www.npmjs.com/package/better-sse) diff --git a/package-lock.json b/package-lock.json index b194478e..17463e35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ultimate-express", - "version": "2.1.1", + "version": "2.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ultimate-express", - "version": "2.1.1", + "version": "2.2.1", "license": "Apache-2.0", "dependencies": { "@types/express": "^4.17.25", @@ -68,6 +68,7 @@ "pako": "^2.1.0", "passport": "^0.7.0", "passport-local": "^1.0.0", + "pino-http": "^11.0.0", "pkg-pr-new": "^0.0.75", "pug": "^3.0.4", "response-time": "^2.3.4", @@ -543,6 +544,13 @@ "@noble/hashes": "^1.1.5" } }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "dev": true, + "license": "MIT" + }, "node_modules/@scarf/scarf": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", @@ -983,6 +991,16 @@ "dev": true, "license": "MIT" }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -5640,6 +5658,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -5999,6 +6027,59 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-http": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-11.0.0.tgz", + "integrity": "sha512-wqg5XIAGRRIWtTk8qPGxkbrfiwEWz1lgedVLvhLALudKXvg1/L2lTFgTGPJ4Z2e3qcRmxoFxDuSdMdMGNM6I1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-caller-file": "^2.0.5", + "pino": "^10.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "dev": true, + "license": "MIT" + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -6089,6 +6170,23 @@ "node": ">=8" } }, + "node_modules/process-warning": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.0.tgz", + "integrity": "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/promise": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", @@ -6318,6 +6416,13 @@ ], "license": "MIT" }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "dev": true, + "license": "MIT" + }, "node_modules/random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -6439,6 +6544,16 @@ "node": ">= 6" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -6754,6 +6869,16 @@ ], "license": "MIT" }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -7090,6 +7215,16 @@ "node": ">=8" } }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -7254,6 +7389,16 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -7631,6 +7776,26 @@ "node": ">=8" } }, + "node_modules/thread-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "real-require": "^1.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/thread-stream/node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/package.json b/package.json index 6faba2a3..62354e69 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "pako": "^2.1.0", "passport": "^0.7.0", "passport-local": "^1.0.0", + "pino-http": "^11.0.0", "pkg-pr-new": "^0.0.75", "pug": "^3.0.4", "response-time": "^2.3.4", diff --git a/src/response.js b/src/response.js index 730b2f3e..8780417e 100644 --- a/src/response.js +++ b/src/response.js @@ -864,4 +864,8 @@ module.exports = class Response extends Writable { get writableFinished() { return this.finished; } + + get writableEnded() { + return this.finished; + } } diff --git a/tests/tests/res/res-pino-http.js b/tests/tests/res/res-pino-http.js new file mode 100644 index 00000000..4959dd0c --- /dev/null +++ b/tests/tests/res/res-pino-http.js @@ -0,0 +1,32 @@ +// must be seen as completed by pino-http, not aborted + +const express = require("express"); +const pinoHttp = require("pino-http"); + +const app = express(); + +const lines = []; +app.use(pinoHttp({ + // drop pid, hostname and time, so the two runs print the same + base: null, + timestamp: false +}, { + write: line => { + const log = JSON.parse(line); + lines.push(`${log.msg} ${log.res.statusCode}`); + } +})); + +app.get('/test', (req, res) => { + res.end('bye'); +}); + +app.listen(13335, async () => { + const response = await fetch('http://localhost:13335/test'); + console.log(await response.text()); + + setTimeout(() => { + console.log(lines.join('\n')); + process.exit(0); + }, 100); +}); diff --git a/tests/tests/res/res-writable-ended.js b/tests/tests/res/res-writable-ended.js new file mode 100644 index 00000000..ebaa9d1c --- /dev/null +++ b/tests/tests/res/res-writable-ended.js @@ -0,0 +1,19 @@ +// must set res.writableEnded after end(), like a node Writable + +const express = require("express"); + +const app = express(); + +app.get('/test', (req, res) => { + console.log('before', res.writableEnded); + res.end('bye'); + console.log('after', res.writableEnded); + console.log('aborted', req.readableAborted); +}); + +app.listen(13334, async () => { + const response = await fetch('http://localhost:13334/test'); + console.log(await response.text()); + + process.exit(0); +}); From d55136fad377f61f940cccedefe2a3317b7b00df Mon Sep 17 00:00:00 2001 From: Nigro Simone Date: Sat, 5 Sep 2026 07:10:35 +0200 Subject: [PATCH 2/2] test: stop comparing res.socket timing that node changed between minors --- tests/tests/res/res-connection.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/tests/res/res-connection.js b/tests/tests/res/res-connection.js index 56b2e75c..c829f914 100644 --- a/tests/tests/res/res-connection.js +++ b/tests/tests/res/res-connection.js @@ -7,12 +7,11 @@ const app = express(); app.get('/test', (req, res) => { console.log(res.writableFinished); console.log(res.connection.writable); + // res.socket and res.writableFinished right after end() are not printed: + // node moved when the socket is detached, so express drifts between minors res.end('bye', () => { - // console.log(res.writable); // express 🐛 true forever... - console.log(res.socket); // should be null after end(). https://nodejs.org/api/http.html#responsesocket + console.log('end callback'); }); - console.log(res.writableFinished); - // console.log(res.connection.writable); on express is true; on ultimate is false }); app.get('/test2', (req, res) => {