lazylog-trace 1.0.2 → 1.0.3

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.
@@ -0,0 +1,8 @@
1
+ import { requestLogger, getStore, defaultStorage } from './middleware';
2
+ import type { RequestLogger, LogEntry, RequestWithLogs } from './types';
3
+ export type { RequestLogger, RequestLoggerOptions, LogEntry, RequestRow, RequestWithLogs } from './types';
4
+ export { requestLogger, getStore, defaultStorage };
5
+ export declare function getRequestLogger(): RequestLogger;
6
+ export declare function getLogsByRequestId(requestId: string): LogEntry[];
7
+ export declare function getRequest(requestId: string): RequestWithLogs | null;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEvE,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAExE,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1G,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;AAEnD,wBAAgB,gBAAgB,IAAI,aAAa,CAIhD;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,EAAE,CAGhE;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAGpE"}
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultStorage = exports.getStore = exports.requestLogger = void 0;
4
+ exports.getRequestLogger = getRequestLogger;
5
+ exports.getLogsByRequestId = getLogsByRequestId;
6
+ exports.getRequest = getRequest;
7
+ const middleware_1 = require("./middleware");
8
+ Object.defineProperty(exports, "requestLogger", { enumerable: true, get: function () { return middleware_1.requestLogger; } });
9
+ Object.defineProperty(exports, "getStore", { enumerable: true, get: function () { return middleware_1.getStore; } });
10
+ Object.defineProperty(exports, "defaultStorage", { enumerable: true, get: function () { return middleware_1.defaultStorage; } });
11
+ const logger_1 = require("./logger");
12
+ function getRequestLogger() {
13
+ const store = (0, middleware_1.getStore)();
14
+ if (!store?.logger)
15
+ return logger_1.noopLogger;
16
+ return store.logger;
17
+ }
18
+ function getLogsByRequestId(requestId) {
19
+ if (!middleware_1.defaultStorage)
20
+ return [];
21
+ return middleware_1.defaultStorage.getLogsByRequestId(requestId);
22
+ }
23
+ function getRequest(requestId) {
24
+ if (!middleware_1.defaultStorage)
25
+ return null;
26
+ return middleware_1.defaultStorage.getRequest(requestId);
27
+ }
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAQA,4CAIC;AAED,gDAGC;AAED,gCAGC;AAtBD,6CAAuE;AAM9D,8FANA,0BAAa,OAMA;AAAE,yFANA,qBAAQ,OAMA;AAAE,+FANA,2BAAc,OAMA;AALhD,qCAAsC;AAOtC,SAAgB,gBAAgB;IAC9B,MAAM,KAAK,GAAG,IAAA,qBAAQ,GAAE,CAAC;IACzB,IAAI,CAAC,KAAK,EAAE,MAAM;QAAE,OAAO,mBAAU,CAAC;IACtC,OAAO,KAAK,CAAC,MAAM,CAAC;AACtB,CAAC;AAED,SAAgB,kBAAkB,CAAC,SAAiB;IAClD,IAAI,CAAC,2BAAc;QAAE,OAAO,EAAE,CAAC;IAC/B,OAAO,2BAAc,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;AACtD,CAAC;AAED,SAAgB,UAAU,CAAC,SAAiB;IAC1C,IAAI,CAAC,2BAAc;QAAE,OAAO,IAAI,CAAC;IACjC,OAAO,2BAAc,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Storage, RequestLogger } from './types';
2
+ export declare function createRequestLogger(requestId: string, storage: Storage, contextRef: Record<string, unknown>): RequestLogger;
3
+ export declare const noopLogger: RequestLogger;
4
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAY,MAAM,SAAS,CAAC;AAEhE,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,aAAa,CAqCf;AAED,eAAO,MAAM,UAAU,EAAE,aAcxB,CAAC"}
package/dist/logger.js ADDED
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noopLogger = void 0;
4
+ exports.createRequestLogger = createRequestLogger;
5
+ function createRequestLogger(requestId, storage, contextRef) {
6
+ function write(level, message, meta = {}) {
7
+ const context = { ...contextRef };
8
+ const payload = { ...context, ...meta };
9
+ storage.saveLog(requestId, level, message, payload);
10
+ const out = payload && Object.keys(payload).length ? ` ${JSON.stringify(payload)}` : '';
11
+ const line = `[${requestId}] ${level}: ${message}${out}`;
12
+ if (level === 'log')
13
+ console.log(line);
14
+ else if (level === 'info')
15
+ console.info(line);
16
+ else if (level === 'warn')
17
+ console.warn(line);
18
+ else
19
+ console.error(line);
20
+ }
21
+ return {
22
+ addContext(obj) {
23
+ if (obj && typeof obj === 'object') {
24
+ Object.assign(contextRef, obj);
25
+ }
26
+ },
27
+ log(message, meta) {
28
+ write('log', message, meta ?? {});
29
+ },
30
+ info(message, meta) {
31
+ write('info', message, meta ?? {});
32
+ },
33
+ warn(message, meta) {
34
+ write('warn', message, meta ?? {});
35
+ },
36
+ error(message, meta) {
37
+ write('error', message, meta ?? {});
38
+ },
39
+ };
40
+ }
41
+ exports.noopLogger = {
42
+ addContext() { },
43
+ log(msg, meta) {
44
+ console.log(msg, meta != null ? meta : '');
45
+ },
46
+ info(msg, meta) {
47
+ console.info(msg, meta != null ? meta : '');
48
+ },
49
+ warn(msg, meta) {
50
+ console.warn(msg, meta != null ? meta : '');
51
+ },
52
+ error(msg, meta) {
53
+ console.error(msg, meta != null ? meta : '');
54
+ },
55
+ };
56
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":";;;AAEA,kDAyCC;AAzCD,SAAgB,mBAAmB,CACjC,SAAiB,EACjB,OAAgB,EAChB,UAAmC;IAEnC,SAAS,KAAK,CAAC,KAAe,EAAE,OAAe,EAAE,OAAgC,EAAE;QACjF,MAAM,OAAO,GAAG,EAAE,GAAG,UAAU,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC;QACxC,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QACpD,MAAM,GAAG,GACP,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9E,MAAM,IAAI,GAAG,IAAI,SAAS,KAAK,KAAK,KAAK,OAAO,GAAG,GAAG,EAAE,CAAC;QACzD,IAAI,KAAK,KAAK,KAAK;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;aAClC,IAAI,KAAK,KAAK,MAAM;YAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aACzC,IAAI,KAAK,KAAK,MAAM;YAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;;YACzC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO;QACL,UAAU,CAAC,GAA4B;YACrC,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBACnC,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;QAED,GAAG,CAAC,OAAe,EAAE,IAA8B;YACjD,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;QACpC,CAAC;QAED,IAAI,CAAC,OAAe,EAAE,IAA8B;YAClD,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;QACrC,CAAC;QAED,IAAI,CAAC,OAAe,EAAE,IAA8B;YAClD,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;QACrC,CAAC;QAED,KAAK,CAAC,OAAe,EAAE,IAA8B;YACnD,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;QACtC,CAAC;KACF,CAAC;AACJ,CAAC;AAEY,QAAA,UAAU,GAAkB;IACvC,UAAU,KAAU,CAAC;IACrB,GAAG,CAAC,GAAW,EAAE,IAA8B;QAC7C,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,CAAC,GAAW,EAAE,IAA8B;QAC9C,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,CAAC,GAAW,EAAE,IAA8B;QAC9C,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,KAAK,CAAC,GAAW,EAAE,IAA8B;QAC/C,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC/C,CAAC;CACF,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { Request, Response, NextFunction } from 'express';
2
+ import { createRequestLogger } from './logger';
3
+ import type { Storage, RequestLoggerOptions } from './types';
4
+ interface Store {
5
+ requestId: string;
6
+ logger: ReturnType<typeof createRequestLogger>;
7
+ context: Record<string, unknown>;
8
+ }
9
+ declare let defaultStorage: Storage | null;
10
+ export declare function requestLogger(options?: RequestLoggerOptions): (req: Request, res: Response, next: NextFunction) => void;
11
+ export declare function getStore(): Store | undefined;
12
+ export { defaultStorage };
13
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAE/C,OAAO,KAAK,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE7D,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;IAC/C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAID,QAAA,IAAI,cAAc,EAAE,OAAO,GAAG,IAAW,CAAC;AAkB1C,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAuE3H;AAED,wBAAgB,QAAQ,IAAI,KAAK,GAAG,SAAS,CAE5C;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.defaultStorage = void 0;
7
+ exports.requestLogger = requestLogger;
8
+ exports.getStore = getStore;
9
+ const async_hooks_1 = require("async_hooks");
10
+ const crypto_1 = __importDefault(require("crypto"));
11
+ const logger_1 = require("./logger");
12
+ const sqlite_1 = require("./sqlite");
13
+ const asyncLocalStorage = new async_hooks_1.AsyncLocalStorage();
14
+ let defaultStorage = null;
15
+ exports.defaultStorage = defaultStorage;
16
+ function generateRequestId() {
17
+ if (crypto_1.default.randomUUID) {
18
+ return crypto_1.default.randomUUID();
19
+ }
20
+ return crypto_1.default.randomBytes(16).toString('hex');
21
+ }
22
+ function matchesIgnore(path, ignorePaths) {
23
+ if (!ignorePaths || !ignorePaths.length)
24
+ return false;
25
+ for (const p of ignorePaths) {
26
+ if (typeof p === 'string' && path === p)
27
+ return true;
28
+ if (p instanceof RegExp && p.test(path))
29
+ return true;
30
+ }
31
+ return false;
32
+ }
33
+ function requestLogger(options = {}) {
34
+ const { ignorePaths = [], logBody = true, logResponse = true, databasePath, databaseUrl, } = options;
35
+ const storage = (0, sqlite_1.createSqliteStorage)({ databasePath, databaseUrl });
36
+ exports.defaultStorage = defaultStorage = storage;
37
+ return function lazylogMiddleware(req, res, next) {
38
+ const pathName = req.path || (req.url?.split('?')[0] ?? '');
39
+ if (matchesIgnore(pathName, ignorePaths)) {
40
+ return next();
41
+ }
42
+ const requestId = generateRequestId();
43
+ const startedAt = new Date().toISOString();
44
+ const context = {};
45
+ const logger = (0, logger_1.createRequestLogger)(requestId, storage, context);
46
+ storage.saveRequest(requestId, {
47
+ method: req.method,
48
+ path: pathName,
49
+ body: logBody ? req.body ?? null : null,
50
+ response: null,
51
+ response_status: null,
52
+ started_at: startedAt,
53
+ finished_at: null,
54
+ });
55
+ let responseBody = null;
56
+ let responseStatus = null;
57
+ if (logResponse) {
58
+ const originalSend = res.send.bind(res);
59
+ const originalJson = res.json.bind(res);
60
+ res.send = function (body) {
61
+ responseBody = typeof body === 'string' ? body : JSON.stringify(body);
62
+ return originalSend(body);
63
+ };
64
+ res.json = function (body) {
65
+ responseBody = JSON.stringify(body);
66
+ return originalJson(body);
67
+ };
68
+ res.on('finish', () => {
69
+ responseStatus = res.statusCode;
70
+ storage.updateRequest(requestId, {
71
+ response: responseBody,
72
+ response_status: responseStatus,
73
+ finished_at: new Date().toISOString(),
74
+ });
75
+ });
76
+ }
77
+ else {
78
+ res.on('finish', () => {
79
+ storage.updateRequest(requestId, {
80
+ response: null,
81
+ response_status: res.statusCode,
82
+ finished_at: new Date().toISOString(),
83
+ });
84
+ });
85
+ }
86
+ res.setHeader('X-Request-Id', requestId);
87
+ const store = { requestId, logger, context };
88
+ asyncLocalStorage.run(store, () => next());
89
+ };
90
+ }
91
+ function getStore() {
92
+ return asyncLocalStorage.getStore();
93
+ }
94
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Storage } from './types';
2
+ export interface SqliteOptions {
3
+ databasePath?: string;
4
+ databaseUrl?: string;
5
+ }
6
+ export declare function resolveDbPath(options?: SqliteOptions): string;
7
+ export declare function createSqliteStorage(options?: SqliteOptions): Storage;
8
+ //# sourceMappingURL=sqlite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../src/sqlite.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAiE,MAAM,SAAS,CAAC;AAItG,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,aAAa,CAAC,OAAO,GAAE,aAAkB,GAAG,MAAM,CAYjE;AAED,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAgIxE"}
package/dist/sqlite.js ADDED
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveDbPath = resolveDbPath;
7
+ exports.createSqliteStorage = createSqliteStorage;
8
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const DEFAULT_DB_PATH = './logs.sqlite';
11
+ function resolveDbPath(options = {}) {
12
+ if (options.databasePath) {
13
+ return path_1.default.resolve(process.cwd(), options.databasePath);
14
+ }
15
+ if (options.databaseUrl) {
16
+ const url = options.databaseUrl;
17
+ if (url.startsWith('file:')) {
18
+ return path_1.default.resolve(process.cwd(), url.slice(5).replace(/^\/+/, ''));
19
+ }
20
+ return path_1.default.resolve(process.cwd(), url);
21
+ }
22
+ return path_1.default.resolve(process.cwd(), DEFAULT_DB_PATH);
23
+ }
24
+ function createSqliteStorage(options = {}) {
25
+ const dbPath = resolveDbPath(options);
26
+ const db = new better_sqlite3_1.default(dbPath);
27
+ db.exec(`
28
+ CREATE TABLE IF NOT EXISTS requests (
29
+ id TEXT PRIMARY KEY,
30
+ method TEXT NOT NULL,
31
+ path TEXT NOT NULL,
32
+ body TEXT,
33
+ response TEXT,
34
+ response_status INTEGER,
35
+ started_at TEXT NOT NULL,
36
+ finished_at TEXT
37
+ );
38
+
39
+ CREATE TABLE IF NOT EXISTS logs (
40
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
41
+ request_id TEXT NOT NULL,
42
+ level TEXT NOT NULL,
43
+ message TEXT NOT NULL,
44
+ context TEXT,
45
+ created_at TEXT NOT NULL,
46
+ FOREIGN KEY (request_id) REFERENCES requests(id)
47
+ );
48
+
49
+ CREATE INDEX IF NOT EXISTS idx_logs_request_id ON logs(request_id);
50
+ `);
51
+ return {
52
+ saveRequest(requestId, data) {
53
+ const stmt = db.prepare(`
54
+ INSERT INTO requests (id, method, path, body, response, response_status, started_at, finished_at)
55
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
56
+ `);
57
+ const bodyStr = data.body != null ? JSON.stringify(data.body) : null;
58
+ stmt.run(requestId, data.method, data.path, bodyStr, data.response ?? null, data.response_status ?? null, data.started_at, data.finished_at ?? null);
59
+ },
60
+ saveLog(requestId, level, message, context) {
61
+ const stmt = db.prepare(`
62
+ INSERT INTO logs (request_id, level, message, context, created_at)
63
+ VALUES (?, ?, ?, ?, ?)
64
+ `);
65
+ const contextStr = context && Object.keys(context).length > 0 ? JSON.stringify(context) : null;
66
+ stmt.run(requestId, level, message, contextStr, new Date().toISOString());
67
+ },
68
+ getLogsByRequestId(requestId) {
69
+ const stmt = db.prepare(`
70
+ SELECT id, request_id, level, message, context, created_at
71
+ FROM logs WHERE request_id = ? ORDER BY created_at ASC
72
+ `);
73
+ const rows = stmt.all(requestId);
74
+ return rows.map((row) => ({
75
+ id: row.id,
76
+ request_id: row.request_id,
77
+ level: row.level,
78
+ message: row.message,
79
+ context: row.context ? JSON.parse(row.context) : {},
80
+ created_at: row.created_at,
81
+ }));
82
+ },
83
+ updateRequest(requestId, data) {
84
+ const stmt = db.prepare(`
85
+ UPDATE requests SET response = ?, response_status = ?, finished_at = ?
86
+ WHERE id = ?
87
+ `);
88
+ stmt.run(data.response ?? null, data.response_status ?? null, data.finished_at ?? null, requestId);
89
+ },
90
+ getRequest(requestId) {
91
+ const reqStmt = db.prepare('SELECT * FROM requests WHERE id = ?');
92
+ const requestRow = reqStmt.get(requestId);
93
+ if (!requestRow)
94
+ return null;
95
+ const request = {
96
+ id: requestRow.id,
97
+ method: requestRow.method,
98
+ path: requestRow.path,
99
+ body: requestRow.body ? JSON.parse(requestRow.body) : null,
100
+ response: requestRow.response,
101
+ response_status: requestRow.response_status,
102
+ started_at: requestRow.started_at,
103
+ finished_at: requestRow.finished_at,
104
+ };
105
+ const logs = this.getLogsByRequestId(requestId);
106
+ return { request, logs };
107
+ },
108
+ };
109
+ }
110
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../src/sqlite.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Shared types for request-scoped logging and storage.
3
+ */
4
+ export type LogLevel = 'log' | 'info' | 'warn' | 'error';
5
+ export interface LogEntry {
6
+ id: number;
7
+ request_id: string;
8
+ level: string;
9
+ message: string;
10
+ context: Record<string, unknown>;
11
+ created_at: string;
12
+ }
13
+ export interface RequestRow {
14
+ id: string;
15
+ method: string;
16
+ path: string;
17
+ body: string | object | null;
18
+ response: string | null;
19
+ response_status: number | null;
20
+ started_at: string;
21
+ finished_at: string | null;
22
+ }
23
+ export interface RequestWithLogs {
24
+ request: RequestRow;
25
+ logs: LogEntry[];
26
+ }
27
+ export interface RequestLoggerOptions {
28
+ ignorePaths?: (string | RegExp)[];
29
+ logBody?: boolean;
30
+ logResponse?: boolean;
31
+ databasePath?: string;
32
+ databaseUrl?: string;
33
+ }
34
+ export interface RequestLogger {
35
+ addContext(obj: Record<string, unknown>): void;
36
+ log(message: string, meta?: Record<string, unknown>): void;
37
+ info(message: string, meta?: Record<string, unknown>): void;
38
+ warn(message: string, meta?: Record<string, unknown>): void;
39
+ error(message: string, meta?: Record<string, unknown>): void;
40
+ }
41
+ export interface SaveRequestData {
42
+ method: string;
43
+ path: string;
44
+ body: object | null;
45
+ response: string | null;
46
+ response_status: number | null;
47
+ started_at: string;
48
+ finished_at: string | null;
49
+ }
50
+ export interface UpdateRequestData {
51
+ response: string | null;
52
+ response_status: number | null;
53
+ finished_at: string | null;
54
+ }
55
+ export interface Storage {
56
+ saveRequest(requestId: string, data: SaveRequestData): void;
57
+ saveLog(requestId: string, level: string, message: string, context: Record<string, unknown>): void;
58
+ getLogsByRequestId(requestId: string): LogEntry[];
59
+ updateRequest(requestId: string, data: UpdateRequestData): void;
60
+ getRequest(requestId: string): RequestWithLogs | null;
61
+ }
62
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ /**
3
+ * Shared types for request-scoped logging and storage.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;GAEG"}
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "lazylog-trace",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Request-scoped logging for Express with SQLite persistence. Tie logs to each request and retrieve them by request id.",
5
- "main": "src/index.js",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
6
7
  "bin": {
7
8
  "lazylog-studio": "./bin/lazylog-studio.js"
8
9
  },
@@ -10,15 +11,16 @@
10
11
  "node": ">=14.0.0"
11
12
  },
12
13
  "scripts": {
14
+ "build": "tsc",
13
15
  "test": "node test/integration.js",
14
16
  "studio": "node bin/lazylog-studio.js",
15
17
  "studio:dev": "cd lazylog-studio && npm run dev",
16
18
  "build:studio": "node -e \"process.chdir('lazylog-studio');require('child_process').execSync('npm run build',{stdio:'inherit'});require('fs').cpSync('dist','../studio/dist',{recursive:true})\"",
17
19
  "test:all": "node lazylog-studio/scripts/generate-test-data.js",
18
- "prepublishOnly": "npm test"
20
+ "prepublishOnly": "npm run build && npm test"
19
21
  },
20
22
  "files": [
21
- "src",
23
+ "dist",
22
24
  "bin",
23
25
  "studio",
24
26
  "README.md",
@@ -47,12 +49,16 @@
47
49
  "homepage": "https://github.com/ChomasDev/lazylog#readme",
48
50
  "dependencies": {
49
51
  "better-sqlite3": "^11.0.0",
50
- "express": "^4.21.0"
52
+ "express": "^4.0.0 || ^5.0.0"
51
53
  },
52
54
  "peerDependencies": {
53
55
  "express": "^4.0.0 || ^5.0.0"
54
56
  },
55
57
  "devDependencies": {
56
- "express": "^4.21.0"
58
+ "@types/better-sqlite3": "^7.6.8",
59
+ "@types/express": "^4.17.21",
60
+ "@types/node": "^20.10.0",
61
+ "express": "^4.21.0",
62
+ "typescript": "^5.9.3"
57
63
  }
58
64
  }
package/src/index.js DELETED
@@ -1,43 +0,0 @@
1
- const middleware = require('./middleware.js');
2
- const { noopLogger } = require('./logger.js');
3
-
4
- /**
5
- * Returns the request-scoped logger for the current request.
6
- * If called outside a request (no middleware context), returns a no-op logger that only writes to console.
7
- * @returns {{ log: function, info: function, warn: function, error: function, addContext: function }}
8
- */
9
- function getRequestLogger() {
10
- const store = middleware.getStore();
11
- if (!store || !store.logger) return noopLogger;
12
- return store.logger;
13
- }
14
-
15
- /**
16
- * Returns all log entries for the given request id.
17
- * Uses the storage from the last-configured requestLogger middleware.
18
- * @param {string} requestId
19
- * @returns {Array<{ id: number, request_id: string, level: string, message: string, context: object, created_at: string }>}
20
- */
21
- function getLogsByRequestId(requestId) {
22
- const storage = middleware.defaultStorage;
23
- if (!storage) return [];
24
- return storage.getLogsByRequestId(requestId);
25
- }
26
-
27
- /**
28
- * Returns the request row and its logs, or null if not found.
29
- * @param {string} requestId
30
- * @returns {{ request: object, logs: array } | null}
31
- */
32
- function getRequest(requestId) {
33
- const storage = middleware.defaultStorage;
34
- if (!storage) return null;
35
- return storage.getRequest(requestId);
36
- }
37
-
38
- module.exports = {
39
- requestLogger: middleware.requestLogger,
40
- getRequestLogger,
41
- getLogsByRequestId,
42
- getRequest,
43
- };
package/src/logger.js DELETED
@@ -1,60 +0,0 @@
1
- /**
2
- * Request-scoped logger. Writes to storage with request_id and optional context.
3
- * addContext() merges into a shared context object; every log includes that context.
4
- */
5
-
6
- function createRequestLogger(requestId, storage, contextRef) {
7
- function write(level, message, meta = {}) {
8
- const context = { ...contextRef };
9
- const payload = { ...context, ...meta };
10
- storage.saveLog(requestId, level, message, payload);
11
- const out = payload && Object.keys(payload).length ? ` ${JSON.stringify(payload)}` : '';
12
- console[level === 'log' ? 'log' : level](`[${requestId}] ${level}: ${message}${out}`);
13
- }
14
-
15
- return {
16
- addContext(obj) {
17
- if (obj && typeof obj === 'object') {
18
- Object.assign(contextRef, obj);
19
- }
20
- },
21
-
22
- log(message, meta) {
23
- write('log', message, meta);
24
- },
25
-
26
- info(message, meta) {
27
- write('info', message, meta);
28
- },
29
-
30
- warn(message, meta) {
31
- write('warn', message, meta);
32
- },
33
-
34
- error(message, meta) {
35
- write('error', message, meta);
36
- },
37
- };
38
- }
39
-
40
- /**
41
- * No-op logger for use outside a request (e.g. getRequestLogger() when not in middleware).
42
- * Logs to console only, no DB.
43
- */
44
- const noopLogger = {
45
- addContext() {},
46
- log(msg, meta) {
47
- console.log(msg, meta != null ? meta : '');
48
- },
49
- info(msg, meta) {
50
- console.info(msg, meta != null ? meta : '');
51
- },
52
- warn(msg, meta) {
53
- console.warn(msg, meta != null ? meta : '');
54
- },
55
- error(msg, meta) {
56
- console.error(msg, meta != null ? meta : '');
57
- },
58
- };
59
-
60
- module.exports = { createRequestLogger, noopLogger };
package/src/middleware.js DELETED
@@ -1,112 +0,0 @@
1
- const { AsyncLocalStorage } = require('async_hooks');
2
- const crypto = require('crypto');
3
- const { createRequestLogger } = require('./logger.js');
4
- const { createSqliteStorage } = require('./sqlite.js');
5
-
6
- const asyncLocalStorage = new AsyncLocalStorage();
7
-
8
- /** Default storage instance; set when requestLogger is first used. */
9
- let defaultStorage = null;
10
-
11
- function generateRequestId() {
12
- if (crypto.randomUUID) {
13
- return crypto.randomUUID();
14
- }
15
- return crypto.randomBytes(16).toString('hex');
16
- }
17
-
18
- function matchesIgnore(path, ignorePaths) {
19
- if (!ignorePaths || !ignorePaths.length) return false;
20
- for (const p of ignorePaths) {
21
- if (typeof p === 'string' && path === p) return true;
22
- if (p instanceof RegExp && p.test(path)) return true;
23
- }
24
- return false;
25
- }
26
-
27
- /**
28
- * @param {object} options
29
- * @param {string[]|RegExp[]} [options.ignorePaths]
30
- * @param {boolean} [options.logBody=true]
31
- * @param {boolean} [options.logResponse=true]
32
- * @param {string} [options.databasePath]
33
- * @param {string} [options.databaseUrl]
34
- */
35
- function requestLogger(options = {}) {
36
- const {
37
- ignorePaths = [],
38
- logBody = true,
39
- logResponse = true,
40
- databasePath,
41
- databaseUrl,
42
- } = options;
43
-
44
- const storage = createSqliteStorage({ databasePath, databaseUrl });
45
- defaultStorage = storage;
46
-
47
- return function lazylogMiddleware(req, res, next) {
48
- const path = req.path || req.url?.split('?')[0] || '';
49
- if (matchesIgnore(path, ignorePaths)) {
50
- return next();
51
- }
52
-
53
- const requestId = generateRequestId();
54
- const startedAt = new Date().toISOString();
55
- const context = {};
56
- const logger = createRequestLogger(requestId, storage, context);
57
-
58
- storage.saveRequest(requestId, {
59
- method: req.method,
60
- path,
61
- body: logBody ? req.body : null,
62
- response: null,
63
- response_status: null,
64
- started_at: startedAt,
65
- finished_at: null,
66
- });
67
-
68
- let responseBody = null;
69
- let responseStatus = null;
70
-
71
- if (logResponse) {
72
- const originalSend = res.send.bind(res);
73
- const originalJson = res.json.bind(res);
74
-
75
- res.send = function (body) {
76
- responseBody = typeof body === 'string' ? body : JSON.stringify(body);
77
- return originalSend(body);
78
- };
79
- res.json = function (body) {
80
- responseBody = JSON.stringify(body);
81
- return originalJson(body);
82
- };
83
-
84
- res.on('finish', () => {
85
- responseStatus = res.statusCode;
86
- storage.updateRequest(requestId, {
87
- response: responseBody,
88
- response_status: responseStatus,
89
- finished_at: new Date().toISOString(),
90
- });
91
- });
92
- } else {
93
- res.on('finish', () => {
94
- storage.updateRequest(requestId, {
95
- response: null,
96
- response_status: res.statusCode,
97
- finished_at: new Date().toISOString(),
98
- });
99
- });
100
- }
101
-
102
- res.setHeader('X-Request-Id', requestId);
103
- const store = { requestId, logger, context };
104
- asyncLocalStorage.run(store, () => next());
105
- };
106
- }
107
-
108
- function getStore() {
109
- return asyncLocalStorage.getStore();
110
- }
111
-
112
- module.exports = { requestLogger, getStore, get defaultStorage() { return defaultStorage; } };
package/src/sqlite.js DELETED
@@ -1,139 +0,0 @@
1
- const Database = require('better-sqlite3');
2
- const path = require('path');
3
-
4
- const DEFAULT_DB_PATH = './logs.sqlite';
5
-
6
- /**
7
- * Resolve database path from options (databasePath or databaseUrl).
8
- * databaseUrl like 'file:./logs.sqlite' is parsed to a path.
9
- * @param {{ databasePath?: string, databaseUrl?: string }} options
10
- * @returns {string}
11
- */
12
- function resolveDbPath(options = {}) {
13
- if (options.databasePath) {
14
- return path.resolve(process.cwd(), options.databasePath);
15
- }
16
- if (options.databaseUrl) {
17
- const url = options.databaseUrl;
18
- if (url.startsWith('file:')) {
19
- return path.resolve(process.cwd(), url.slice(5).replace(/^\/+/, ''));
20
- }
21
- return path.resolve(process.cwd(), url);
22
- }
23
- return path.resolve(process.cwd(), DEFAULT_DB_PATH);
24
- }
25
-
26
- /**
27
- * Create SQLite storage and ensure tables exist.
28
- * @param {{ databasePath?: string, databaseUrl?: string }} options
29
- * @returns {import('./storage.js')}
30
- */
31
- function createSqliteStorage(options = {}) {
32
- const dbPath = resolveDbPath(options);
33
- const db = new Database(dbPath);
34
-
35
- db.exec(`
36
- CREATE TABLE IF NOT EXISTS requests (
37
- id TEXT PRIMARY KEY,
38
- method TEXT NOT NULL,
39
- path TEXT NOT NULL,
40
- body TEXT,
41
- response TEXT,
42
- response_status INTEGER,
43
- started_at TEXT NOT NULL,
44
- finished_at TEXT
45
- );
46
-
47
- CREATE TABLE IF NOT EXISTS logs (
48
- id INTEGER PRIMARY KEY AUTOINCREMENT,
49
- request_id TEXT NOT NULL,
50
- level TEXT NOT NULL,
51
- message TEXT NOT NULL,
52
- context TEXT,
53
- created_at TEXT NOT NULL,
54
- FOREIGN KEY (request_id) REFERENCES requests(id)
55
- );
56
-
57
- CREATE INDEX IF NOT EXISTS idx_logs_request_id ON logs(request_id);
58
- `);
59
-
60
- return {
61
- saveRequest(requestId, data) {
62
- const stmt = db.prepare(`
63
- INSERT INTO requests (id, method, path, body, response, response_status, started_at, finished_at)
64
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
65
- `);
66
- const bodyStr = data.body != null ? JSON.stringify(data.body) : null;
67
- stmt.run(
68
- requestId,
69
- data.method,
70
- data.path,
71
- bodyStr,
72
- data.response ?? null,
73
- data.response_status ?? null,
74
- data.started_at,
75
- data.finished_at ?? null
76
- );
77
- },
78
-
79
- saveLog(requestId, level, message, context) {
80
- const stmt = db.prepare(`
81
- INSERT INTO logs (request_id, level, message, context, created_at)
82
- VALUES (?, ?, ?, ?, ?)
83
- `);
84
- const contextStr = context && Object.keys(context).length > 0 ? JSON.stringify(context) : null;
85
- stmt.run(requestId, level, message, contextStr, new Date().toISOString());
86
- },
87
-
88
- getLogsByRequestId(requestId) {
89
- const stmt = db.prepare(`
90
- SELECT id, request_id, level, message, context, created_at
91
- FROM logs WHERE request_id = ? ORDER BY created_at ASC
92
- `);
93
- const rows = stmt.all(requestId);
94
- return rows.map((row) => ({
95
- id: row.id,
96
- request_id: row.request_id,
97
- level: row.level,
98
- message: row.message,
99
- context: row.context ? JSON.parse(row.context) : {},
100
- created_at: row.created_at,
101
- }));
102
- },
103
-
104
- updateRequest(requestId, data) {
105
- const stmt = db.prepare(`
106
- UPDATE requests SET response = ?, response_status = ?, finished_at = ?
107
- WHERE id = ?
108
- `);
109
- stmt.run(
110
- data.response ?? null,
111
- data.response_status ?? null,
112
- data.finished_at ?? null,
113
- requestId
114
- );
115
- },
116
-
117
- getRequest(requestId) {
118
- const reqStmt = db.prepare('SELECT * FROM requests WHERE id = ?');
119
- const requestRow = reqStmt.get(requestId);
120
- if (!requestRow) return null;
121
-
122
- const request = {
123
- id: requestRow.id,
124
- method: requestRow.method,
125
- path: requestRow.path,
126
- body: requestRow.body ? JSON.parse(requestRow.body) : null,
127
- response: requestRow.response,
128
- response_status: requestRow.response_status,
129
- started_at: requestRow.started_at,
130
- finished_at: requestRow.finished_at,
131
- };
132
-
133
- const logs = this.getLogsByRequestId(requestId);
134
- return { request, logs };
135
- },
136
- };
137
- }
138
-
139
- module.exports = { createSqliteStorage, resolveDbPath };
package/src/storage.js DELETED
@@ -1,41 +0,0 @@
1
- /**
2
- * Abstract storage interface for request and log persistence.
3
- * Implementations (e.g. sqlite.js) provide: saveRequest, saveLog, getLogsByRequestId, getRequest.
4
- */
5
-
6
- /**
7
- * @typedef {Object} RequestRow
8
- * @property {string} id
9
- * @property {string} method
10
- * @property {string} path
11
- * @property {string|object|null} body
12
- * @property {string|null} response
13
- * @property {number|null} response_status
14
- * @property {string} started_at
15
- * @property {string|null} finished_at
16
- */
17
-
18
- /**
19
- * @typedef {Object} LogEntry
20
- * @property {number} id
21
- * @property {string} request_id
22
- * @property {string} level
23
- * @property {string} message
24
- * @property {object} context
25
- * @property {string} created_at
26
- */
27
-
28
- /**
29
- * @typedef {Object} RequestWithLogs
30
- * @property {RequestRow} request
31
- * @property {LogEntry[]} logs
32
- */
33
-
34
- /**
35
- * Storage interface. Implementations must provide:
36
- * - saveRequest(requestId, data)
37
- * - saveLog(requestId, level, message, context)
38
- * - getLogsByRequestId(requestId) -> LogEntry[]
39
- * - getRequest(requestId) -> RequestWithLogs | null
40
- */
41
- module.exports = {};