@darksheep/express 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 (2025-03-25)
4
+
5
+
6
+ ### 🧹 Chores
7
+
8
+ * Add express as a peer dep ([f259160](https://github.com/DarkSheepSoftware/node-packages/commit/f25916067cc03e7ad0cf651e158c94f7d49e7c82))
9
+ * Move express-logger to express package ([aaa7604](https://github.com/DarkSheepSoftware/node-packages/commit/aaa760470ff9239a43cc9124550e3325099cff06))
10
+
11
+
12
+ ### Dependencies
13
+
14
+ * The following workspace dependencies were updated
15
+ * devDependencies
16
+ * @darksheep/logger bumped from 1.0.10 to 1.1.0
17
+ * peerDependencies
18
+ * @darksheep/logger bumped from ~1.0.10 to ~1.1.0
package/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # @darksheep/express
2
+ Automatically log your express requests.
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@darksheep/express",
3
+ "version": "1.0.0",
4
+ "description": "express utilites and other bits",
5
+ "license": "UNLICENCED",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./types/index.d.ts",
10
+ "import": "./src/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "src",
15
+ "types",
16
+ "!*.test.*"
17
+ ],
18
+ "scripts": {
19
+ "prepack": "yarn dlx -q -p typescript tsc",
20
+ "test": "node --test src/**/*.test.js",
21
+ "test:watch": "node --watch --test src/**/*.test.js"
22
+ },
23
+ "dependencies": {
24
+ "@darksheep/prune": "1.0.4",
25
+ "on-finished": "2.4.1"
26
+ },
27
+ "devDependencies": {
28
+ "@darksheep/logger": "1.1.0",
29
+ "@types/express": "~5.0.0",
30
+ "@types/on-finished": "^2",
31
+ "express": "~4.21.2"
32
+ },
33
+ "peerDependencies": {
34
+ "@darksheep/logger": "~1.0.10",
35
+ "express": "~4.21.2 || ~5"
36
+ },
37
+ "engines": {
38
+ "node": "^20.13.1 || ^22.2.0"
39
+ }
40
+ }
@@ -0,0 +1,18 @@
1
+ /* eslint @typescript-eslint/consistent-type-definitions: 0, n/no-missing-import: 0 */
2
+
3
+ import { type Logger } from '@darksheep/logger';
4
+
5
+ declare global {
6
+ namespace Express {
7
+ interface Request {
8
+ id: string;
9
+ logger: Logger;
10
+ }
11
+ }
12
+ }
13
+
14
+ export {
15
+ type Handler,
16
+ type Request,
17
+ type Response,
18
+ } from 'express';
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @import { Request } from './express.js'
3
+ * @import { Logger } from '@darksheep/logger'
4
+ */
5
+ import { randomUUID } from 'node:crypto';
6
+
7
+ /**
8
+ * @param {Request} request - The express request.
9
+ * @param {Logger} logger - The root logger.
10
+ * @param {string} headerName - The name of the header used in correlation.
11
+ * @returns {string}
12
+ */
13
+ export function getTrail(request, logger, headerName) {
14
+ const loggerTrail = logger.get('trail');
15
+ if (typeof loggerTrail === 'string') {
16
+ return loggerTrail;
17
+ }
18
+
19
+ const header = request.header(headerName);
20
+ if (typeof header === 'string') {
21
+ return header;
22
+ }
23
+
24
+ if (typeof request.id === 'string') {
25
+ return request.id;
26
+ }
27
+
28
+ return randomUUID();
29
+ }
package/src/index.js ADDED
@@ -0,0 +1 @@
1
+ export { createRequestLogger } from './request-logger.js';
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @import { Socket } from 'node:net'
3
+ * @import { Handler } from './express.js'
4
+ * @import { Filter } from './should-filter.js'
5
+ */
6
+
7
+ import { Logger } from '@darksheep/logger';
8
+ import { pruneShallow } from '@darksheep/prune';
9
+ import onFinished from 'on-finished';
10
+
11
+ import { shouldFilterOutRequest } from './should-filter.js';
12
+
13
+ /** @type {WeakMap<Socket, number>} */
14
+ const sockets = new WeakMap();
15
+
16
+ import { randomUUID } from 'node:crypto';
17
+
18
+ /**
19
+ * @param {Logger} logger - A callback that can be bound to the request.
20
+ * @param {Filter[]} [filters] - A list of requests to not log.
21
+ * @param {string} [headerName] - The name of a header to include in the response with the trail.
22
+ * @returns {Handler}
23
+ */
24
+ export function createRequestLogger(
25
+ logger,
26
+ filters,
27
+ headerName = 'X-Request-Id',
28
+ ) {
29
+ return (request, response, next) => {
30
+ request.id ??= (
31
+ request.header(headerName) ??
32
+ request.id ??
33
+ randomUUID()
34
+ );
35
+ request.logger = logger.trail(request.id);
36
+
37
+ Logger.wrap({
38
+ request: request,
39
+ trail: request.id,
40
+ }, () => {
41
+ const [ requestPath ] = request.originalUrl.split('?', 1);
42
+ if (
43
+ requestPath == null ||
44
+ shouldFilterOutRequest(requestPath, filters)
45
+ ) {
46
+ return next();
47
+ }
48
+
49
+ const startBytesRead = sockets.get(request.socket) ?? 0;
50
+ const startBytesWritten = request.socket.bytesWritten;
51
+
52
+ const requestStartMs = Date.now();
53
+ onFinished(response, (error) => {
54
+ sockets.set(request.socket, request.socket.bytesRead);
55
+
56
+ const message = `${request.method} ${requestPath} ${response.statusCode}`;
57
+ const context = pruneShallow({
58
+ duration: Date.now() - requestStartMs,
59
+ error: error,
60
+
61
+ request: request,
62
+ response: response,
63
+ socket: {
64
+ ip: request.ip,
65
+ hostname: request.hostname,
66
+ bytesRead: request.socket.bytesRead - startBytesRead,
67
+ bytesWritten: request.socket.bytesWritten - startBytesWritten,
68
+ },
69
+ });
70
+
71
+ if (response.statusCode < 400) {
72
+ return request.logger.info(message, context);
73
+ }
74
+
75
+ if (response.statusCode < 500) {
76
+ return request.logger.warning(message, context);
77
+ }
78
+
79
+ return request.logger.error(message, context);
80
+ });
81
+
82
+ next();
83
+ });
84
+ };
85
+ }
@@ -0,0 +1,27 @@
1
+ const fallbackFilters = [
2
+ '/favicon.ico',
3
+ /^\/health(?:check)?z?$/u,
4
+ /^\/read(?:y|iness)z?$/u,
5
+ /^\/live(?:ness)?z?$/u,
6
+ ];
7
+
8
+ /** @typedef {string|RegExp} Filter */
9
+
10
+ /**
11
+ * @param {string} requestPath - The path to test.
12
+ * @param {Filter[]} [filters] - A list of paths to filter out.
13
+ * @returns {boolean}
14
+ */
15
+ export function shouldFilterOutRequest(requestPath, filters = fallbackFilters) {
16
+ for (const filter of filters) {
17
+ if (filter instanceof RegExp && filter.test(requestPath)) {
18
+ return true;
19
+ }
20
+
21
+ if (typeof filter === 'string' && requestPath === filter) {
22
+ return true;
23
+ }
24
+ }
25
+
26
+ return false;
27
+ }
@@ -0,0 +1 @@
1
+ export { createRequestLogger } from "./request-logger.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @param {Logger} logger - A callback that can be bound to the request.
3
+ * @param {Filter[]} [filters] - A list of requests to not log.
4
+ * @param {string} [headerName] - The name of a header to include in the response with the trail.
5
+ * @returns {Handler}
6
+ */
7
+ export function createRequestLogger(logger: Logger, filters?: Filter[], headerName?: string): Handler;
8
+ import { Logger } from '@darksheep/logger';
9
+ import type { Filter } from './should-filter.js';
10
+ import type { Handler } from './express.js';
@@ -0,0 +1,8 @@
1
+ /** @typedef {string|RegExp} Filter */
2
+ /**
3
+ * @param {string} requestPath - The path to test.
4
+ * @param {Filter[]} [filters] - A list of paths to filter out.
5
+ * @returns {boolean}
6
+ */
7
+ export function shouldFilterOutRequest(requestPath: string, filters?: Filter[]): boolean;
8
+ export type Filter = string | RegExp;