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 +5 -0
- package/dist/log-observer.d.ts +29 -0
- package/dist/log-observer.js +331 -0
- package/dist/observe-register-debug.js +1769 -0
- package/dist/observe-register.js +264 -6
- package/dist/trace-var.js +7 -1
- package/dist/vite-plugin.js +179 -4
- package/dist-esm/vite-plugin.js +179 -4
- package/package.json +1 -1
- package/src/express.ts +5 -0
- package/src/log-observer.ts +308 -0
- package/src/observe-register.ts +263 -6
- package/src/trace-var.ts +7 -1
- package/src/vite-plugin.ts +181 -4
- package/dist/vite-plugin.test.d.ts +0 -1
- package/dist/vite-plugin.test.js +0 -160
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
|
+
}
|