trickle-observe 0.2.108 → 0.2.110

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/dist/express.js CHANGED
@@ -181,6 +181,11 @@ function wrapExpressHandler(handler, routeName, opts) {
181
181
  return next(err);
182
182
  }
183
183
  };
184
+ const debug = process.env.TRICKLE_DEBUG === '1';
185
+ if (debug) {
186
+ const hs = handler.toString().substring(0, 200);
187
+ process.stderr.write(`[trickle/express-wrap] Calling handler: ${hs.replace(/\n/g, '\\n')}\n`);
188
+ }
184
189
  try {
185
190
  const result = handler.call(this, req, res, wrappedNext);
186
191
  // Handle async handlers that return a promise
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Structured log observer — patches popular Node.js logging libraries
3
+ * to capture structured log entries with context.
4
+ *
5
+ * Supports:
6
+ * - winston (most popular Node.js logger, 13M weekly downloads)
7
+ * - pino (fastest Node.js logger, 5M weekly downloads)
8
+ * - bunyan (legacy but still used, 1M weekly downloads)
9
+ *
10
+ * Writes to .trickle/logs.jsonl as:
11
+ * { "kind": "log", "level": "error", "logger": "winston",
12
+ * "message": "User not found", "timestamp": 1710516000,
13
+ * "meta": { "userId": 123 } }
14
+ */
15
+ /**
16
+ * Patch winston to capture structured log entries.
17
+ * Winston uses transports — we add a custom transport that writes to logs.jsonl.
18
+ */
19
+ export declare function patchWinston(winstonModule: any, debug: boolean): void;
20
+ /**
21
+ * Patch pino to capture structured log entries.
22
+ * Pino uses a destination stream — we wrap the pino factory to intercept log calls.
23
+ */
24
+ export declare function patchPino(pinoModule: any, debug: boolean): void;
25
+ /**
26
+ * Patch bunyan to capture structured log entries.
27
+ * Bunyan loggers have addStream() — we add a custom stream.
28
+ */
29
+ export declare function patchBunyan(bunyanModule: any, debug: boolean): void;
@@ -0,0 +1,331 @@
1
+ "use strict";
2
+ /**
3
+ * Structured log observer — patches popular Node.js logging libraries
4
+ * to capture structured log entries with context.
5
+ *
6
+ * Supports:
7
+ * - winston (most popular Node.js logger, 13M weekly downloads)
8
+ * - pino (fastest Node.js logger, 5M weekly downloads)
9
+ * - bunyan (legacy but still used, 1M weekly downloads)
10
+ *
11
+ * Writes to .trickle/logs.jsonl as:
12
+ * { "kind": "log", "level": "error", "logger": "winston",
13
+ * "message": "User not found", "timestamp": 1710516000,
14
+ * "meta": { "userId": 123 } }
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.patchWinston = patchWinston;
51
+ exports.patchPino = patchPino;
52
+ exports.patchBunyan = patchBunyan;
53
+ const fs = __importStar(require("fs"));
54
+ const path = __importStar(require("path"));
55
+ let logsFile = null;
56
+ let debugMode = false;
57
+ const MAX_LOGS = 1000;
58
+ let logCount = 0;
59
+ const buffer = [];
60
+ function getLogsFile() {
61
+ if (logsFile)
62
+ return logsFile;
63
+ const dir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
64
+ try {
65
+ fs.mkdirSync(dir, { recursive: true });
66
+ }
67
+ catch { }
68
+ logsFile = path.join(dir, 'logs.jsonl');
69
+ try {
70
+ fs.writeFileSync(logsFile, '');
71
+ }
72
+ catch { }
73
+ return logsFile;
74
+ }
75
+ function writeLog(record) {
76
+ if (logCount >= MAX_LOGS)
77
+ return;
78
+ logCount++;
79
+ buffer.push(JSON.stringify(record));
80
+ if (buffer.length >= 20) {
81
+ flushLogs();
82
+ }
83
+ }
84
+ function flushLogs() {
85
+ if (buffer.length === 0)
86
+ return;
87
+ try {
88
+ fs.appendFileSync(getLogsFile(), buffer.join('\n') + '\n');
89
+ }
90
+ catch { }
91
+ buffer.length = 0;
92
+ }
93
+ // Flush on exit
94
+ process.on('exit', flushLogs);
95
+ /**
96
+ * Patch winston to capture structured log entries.
97
+ * Winston uses transports — we add a custom transport that writes to logs.jsonl.
98
+ */
99
+ function patchWinston(winstonModule, debug) {
100
+ debugMode = debug;
101
+ if (winstonModule.__trickle_patched)
102
+ return;
103
+ winstonModule.__trickle_patched = true;
104
+ getLogsFile(); // Initialize file
105
+ // Create a custom transport
106
+ const Transport = winstonModule.Transport;
107
+ if (!Transport)
108
+ return;
109
+ class TrickleTransport extends Transport {
110
+ constructor(opts) {
111
+ super(opts);
112
+ }
113
+ log(info, callback) {
114
+ const level = info.level || info[Symbol.for('level')] || 'info';
115
+ const message = info.message || info[Symbol.for('message')] || '';
116
+ // Extract metadata (everything except level, message, and internal symbols)
117
+ const meta = {};
118
+ for (const key of Object.keys(info)) {
119
+ if (key !== 'level' && key !== 'message' && key !== 'timestamp' && key !== 'splat') {
120
+ const val = info[key];
121
+ if (val !== undefined && typeof val !== 'symbol' && typeof val !== 'function') {
122
+ try {
123
+ JSON.stringify(val);
124
+ meta[key] = val;
125
+ }
126
+ catch { }
127
+ }
128
+ }
129
+ }
130
+ writeLog({
131
+ kind: 'log',
132
+ level: String(level),
133
+ logger: 'winston',
134
+ message: String(message).substring(0, 500),
135
+ timestamp: Date.now(),
136
+ meta: Object.keys(meta).length > 0 ? meta : undefined,
137
+ });
138
+ if (callback)
139
+ callback();
140
+ }
141
+ }
142
+ // Patch createLogger to auto-add our transport
143
+ const origCreateLogger = winstonModule.createLogger;
144
+ if (origCreateLogger && !origCreateLogger.__trickle_patched) {
145
+ winstonModule.createLogger = function patchedCreateLogger(opts = {}) {
146
+ const logger = origCreateLogger(opts);
147
+ try {
148
+ logger.add(new TrickleTransport({ level: 'silly' }));
149
+ }
150
+ catch { }
151
+ return logger;
152
+ };
153
+ winstonModule.createLogger.__trickle_patched = true;
154
+ }
155
+ // Also patch the default logger if it exists
156
+ if (winstonModule.add && winstonModule.transports) {
157
+ try {
158
+ winstonModule.add(new TrickleTransport({ level: 'silly' }));
159
+ }
160
+ catch { }
161
+ }
162
+ if (debug)
163
+ console.log('[trickle/log] Winston log tracing enabled');
164
+ }
165
+ /**
166
+ * Patch pino to capture structured log entries.
167
+ * Pino uses a destination stream — we wrap the pino factory to intercept log calls.
168
+ */
169
+ function patchPino(pinoModule, debug) {
170
+ debugMode = debug;
171
+ const pinoFn = pinoModule.default || pinoModule;
172
+ if (typeof pinoFn !== 'function' || pinoFn.__trickle_patched)
173
+ return;
174
+ getLogsFile(); // Initialize file
175
+ const PINO_LEVELS = {
176
+ 10: 'trace', 20: 'debug', 30: 'info', 40: 'warn', 50: 'error', 60: 'fatal',
177
+ };
178
+ const wrappedPino = function patchedPino(...args) {
179
+ const logger = pinoFn.apply(this, args);
180
+ // Wrap the logger's write method to intercept log entries
181
+ const origWrite = logger[Symbol.for('pino.write')] || logger.write;
182
+ if (origWrite && typeof origWrite === 'function') {
183
+ const interceptWrite = function (obj, ...rest) {
184
+ try {
185
+ const parsed = typeof obj === 'string' ? JSON.parse(obj) : obj;
186
+ const level = PINO_LEVELS[parsed.level] || String(parsed.level || 'info');
187
+ const message = parsed.msg || parsed.message || '';
188
+ const meta = {};
189
+ for (const key of Object.keys(parsed)) {
190
+ if (!['level', 'time', 'pid', 'hostname', 'msg', 'message', 'v'].includes(key)) {
191
+ meta[key] = parsed[key];
192
+ }
193
+ }
194
+ writeLog({
195
+ kind: 'log',
196
+ level,
197
+ logger: 'pino',
198
+ message: String(message).substring(0, 500),
199
+ timestamp: parsed.time || Date.now(),
200
+ meta: Object.keys(meta).length > 0 ? meta : undefined,
201
+ });
202
+ }
203
+ catch { }
204
+ return origWrite.apply(this, [obj, ...rest]);
205
+ };
206
+ if (logger[Symbol.for('pino.write')]) {
207
+ logger[Symbol.for('pino.write')] = interceptWrite;
208
+ }
209
+ }
210
+ // Also wrap individual level methods as fallback
211
+ for (const levelName of ['trace', 'debug', 'info', 'warn', 'error', 'fatal']) {
212
+ const orig = logger[levelName];
213
+ if (orig && typeof orig === 'function' && !orig.__trickle_patched) {
214
+ logger[levelName] = function (...logArgs) {
215
+ try {
216
+ let message = '';
217
+ let meta;
218
+ if (typeof logArgs[0] === 'object' && logArgs[0] !== null && !(logArgs[0] instanceof Error)) {
219
+ meta = {};
220
+ for (const [k, v] of Object.entries(logArgs[0])) {
221
+ try {
222
+ JSON.stringify(v);
223
+ meta[k] = v;
224
+ }
225
+ catch { }
226
+ }
227
+ message = logArgs.length > 1 ? String(logArgs[1]).substring(0, 500) : '';
228
+ }
229
+ else if (logArgs[0] instanceof Error) {
230
+ message = logArgs[0].message.substring(0, 500);
231
+ meta = { errorType: logArgs[0].name, stack: logArgs[0].stack?.substring(0, 200) };
232
+ }
233
+ else {
234
+ message = String(logArgs[0] || '').substring(0, 500);
235
+ }
236
+ writeLog({
237
+ kind: 'log',
238
+ level: levelName,
239
+ logger: 'pino',
240
+ message,
241
+ timestamp: Date.now(),
242
+ meta,
243
+ });
244
+ }
245
+ catch { }
246
+ return orig.apply(this, logArgs);
247
+ };
248
+ logger[levelName].__trickle_patched = true;
249
+ }
250
+ }
251
+ return logger;
252
+ };
253
+ // Copy properties
254
+ Object.setPrototypeOf(wrappedPino, pinoFn);
255
+ for (const key of Object.getOwnPropertyNames(pinoFn)) {
256
+ if (key !== 'length' && key !== 'name' && key !== 'prototype') {
257
+ try {
258
+ Object.defineProperty(wrappedPino, key, Object.getOwnPropertyDescriptor(pinoFn, key));
259
+ }
260
+ catch { }
261
+ }
262
+ }
263
+ if (pinoModule.default) {
264
+ pinoModule.default = wrappedPino;
265
+ }
266
+ else {
267
+ // pino exports the function as module.exports — observe-register handles replacement
268
+ }
269
+ wrappedPino.__trickle_patched = true;
270
+ if (debug)
271
+ console.log('[trickle/log] Pino log tracing enabled');
272
+ return wrappedPino; // Return for observe-register to use
273
+ }
274
+ /**
275
+ * Patch bunyan to capture structured log entries.
276
+ * Bunyan loggers have addStream() — we add a custom stream.
277
+ */
278
+ function patchBunyan(bunyanModule, debug) {
279
+ debugMode = debug;
280
+ if (bunyanModule.__trickle_patched)
281
+ return;
282
+ bunyanModule.__trickle_patched = true;
283
+ getLogsFile(); // Initialize file
284
+ const BUNYAN_LEVELS = {
285
+ 10: 'trace', 20: 'debug', 30: 'info', 40: 'warn', 50: 'error', 60: 'fatal',
286
+ };
287
+ const origCreateLogger = bunyanModule.createLogger;
288
+ if (!origCreateLogger)
289
+ return;
290
+ bunyanModule.createLogger = function patchedCreateLogger(opts) {
291
+ const logger = origCreateLogger(opts);
292
+ // Add a trickle stream
293
+ try {
294
+ logger.addStream({
295
+ level: 'trace',
296
+ type: 'raw',
297
+ stream: {
298
+ write(rec) {
299
+ try {
300
+ const level = BUNYAN_LEVELS[rec.level] || String(rec.level || 'info');
301
+ const message = rec.msg || '';
302
+ const meta = {};
303
+ for (const key of Object.keys(rec)) {
304
+ if (!['v', 'level', 'name', 'hostname', 'pid', 'time', 'msg', 'src'].includes(key)) {
305
+ try {
306
+ JSON.stringify(rec[key]);
307
+ meta[key] = rec[key];
308
+ }
309
+ catch { }
310
+ }
311
+ }
312
+ writeLog({
313
+ kind: 'log',
314
+ level,
315
+ logger: `bunyan:${rec.name || 'default'}`,
316
+ message: String(message).substring(0, 500),
317
+ timestamp: rec.time ? new Date(rec.time).getTime() : Date.now(),
318
+ meta: Object.keys(meta).length > 0 ? meta : undefined,
319
+ });
320
+ }
321
+ catch { }
322
+ },
323
+ },
324
+ });
325
+ }
326
+ catch { }
327
+ return logger;
328
+ };
329
+ if (debug)
330
+ console.log('[trickle/log] Bunyan log tracing enabled');
331
+ }