trickle-observe 0.2.92 → 0.2.94
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/db-observer.d.ts +15 -0
- package/dist/db-observer.js +199 -0
- package/dist/observe-register.js +9 -0
- package/dist/trace-var.js +41 -0
- package/package.json +1 -1
- package/src/db-observer.ts +185 -0
- package/src/observe-register.ts +9 -0
- package/src/trace-var.ts +41 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database query observer — patches popular database drivers to capture
|
|
3
|
+
* SQL queries, execution time, and result shapes.
|
|
4
|
+
*
|
|
5
|
+
* Currently supports:
|
|
6
|
+
* - pg (node-postgres) — used by Prisma, Knex, Sequelize, TypeORM
|
|
7
|
+
*
|
|
8
|
+
* Captured data is written to .trickle/queries.jsonl as:
|
|
9
|
+
* { query: "SELECT ...", params: [...], durationMs: 2.5, rowCount: 42, columns: [...] }
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Patch pg (node-postgres) to capture queries.
|
|
13
|
+
* Called from observe-register when pg is required.
|
|
14
|
+
*/
|
|
15
|
+
export declare function patchPg(pgModule: any, debug: boolean): void;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Database query observer — patches popular database drivers to capture
|
|
4
|
+
* SQL queries, execution time, and result shapes.
|
|
5
|
+
*
|
|
6
|
+
* Currently supports:
|
|
7
|
+
* - pg (node-postgres) — used by Prisma, Knex, Sequelize, TypeORM
|
|
8
|
+
*
|
|
9
|
+
* Captured data is written to .trickle/queries.jsonl as:
|
|
10
|
+
* { query: "SELECT ...", params: [...], durationMs: 2.5, rowCount: 42, columns: [...] }
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.patchPg = patchPg;
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
let queriesFile = null;
|
|
50
|
+
let debugMode = false;
|
|
51
|
+
const MAX_QUERY_LENGTH = 500;
|
|
52
|
+
const MAX_QUERIES = 100;
|
|
53
|
+
let queryCount = 0;
|
|
54
|
+
function getQueriesFile() {
|
|
55
|
+
if (queriesFile)
|
|
56
|
+
return queriesFile;
|
|
57
|
+
const dir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
|
|
58
|
+
try {
|
|
59
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
catch { }
|
|
62
|
+
queriesFile = path.join(dir, 'queries.jsonl');
|
|
63
|
+
// Clear previous
|
|
64
|
+
try {
|
|
65
|
+
fs.writeFileSync(queriesFile, '');
|
|
66
|
+
}
|
|
67
|
+
catch { }
|
|
68
|
+
return queriesFile;
|
|
69
|
+
}
|
|
70
|
+
function writeQuery(record) {
|
|
71
|
+
if (queryCount >= MAX_QUERIES)
|
|
72
|
+
return;
|
|
73
|
+
queryCount++;
|
|
74
|
+
try {
|
|
75
|
+
fs.appendFileSync(getQueriesFile(), JSON.stringify(record) + '\n');
|
|
76
|
+
}
|
|
77
|
+
catch { }
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Patch pg (node-postgres) to capture queries.
|
|
81
|
+
* Called from observe-register when pg is required.
|
|
82
|
+
*/
|
|
83
|
+
function patchPg(pgModule, debug) {
|
|
84
|
+
debugMode = debug;
|
|
85
|
+
// Patch Client.prototype.query
|
|
86
|
+
const Client = pgModule.Client;
|
|
87
|
+
if (!Client || !Client.prototype)
|
|
88
|
+
return;
|
|
89
|
+
const originalQuery = Client.prototype.query;
|
|
90
|
+
if (originalQuery.__trickle_patched)
|
|
91
|
+
return;
|
|
92
|
+
Client.prototype.query = function patchedQuery(...args) {
|
|
93
|
+
const startTime = performance.now();
|
|
94
|
+
// Extract query text and params
|
|
95
|
+
let queryText = '';
|
|
96
|
+
let params;
|
|
97
|
+
if (typeof args[0] === 'string') {
|
|
98
|
+
queryText = args[0];
|
|
99
|
+
params = Array.isArray(args[1]) ? args[1] : undefined;
|
|
100
|
+
}
|
|
101
|
+
else if (args[0] && typeof args[0] === 'object' && args[0].text) {
|
|
102
|
+
queryText = args[0].text;
|
|
103
|
+
params = args[0].values;
|
|
104
|
+
}
|
|
105
|
+
const truncatedQuery = queryText.length > MAX_QUERY_LENGTH
|
|
106
|
+
? queryText.substring(0, MAX_QUERY_LENGTH) + '...'
|
|
107
|
+
: queryText;
|
|
108
|
+
// Call original
|
|
109
|
+
const result = originalQuery.apply(this, args);
|
|
110
|
+
// Handle promise-based queries
|
|
111
|
+
if (result && typeof result.then === 'function') {
|
|
112
|
+
return result.then((res) => {
|
|
113
|
+
const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
|
|
114
|
+
const columns = res.fields?.map((f) => f.name) || [];
|
|
115
|
+
writeQuery({
|
|
116
|
+
kind: 'query',
|
|
117
|
+
query: truncatedQuery,
|
|
118
|
+
params: params?.slice(0, 5),
|
|
119
|
+
durationMs,
|
|
120
|
+
rowCount: res.rowCount || 0,
|
|
121
|
+
columns: columns.length > 0 ? columns : undefined,
|
|
122
|
+
timestamp: Date.now(),
|
|
123
|
+
});
|
|
124
|
+
if (debugMode) {
|
|
125
|
+
console.log(`[trickle/db] ${truncatedQuery.substring(0, 60)}... (${durationMs}ms, ${res.rowCount} rows)`);
|
|
126
|
+
}
|
|
127
|
+
return res;
|
|
128
|
+
}, (err) => {
|
|
129
|
+
const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
|
|
130
|
+
writeQuery({
|
|
131
|
+
kind: 'query',
|
|
132
|
+
query: truncatedQuery,
|
|
133
|
+
params: params?.slice(0, 5),
|
|
134
|
+
durationMs,
|
|
135
|
+
rowCount: 0,
|
|
136
|
+
error: err.message?.substring(0, 200),
|
|
137
|
+
timestamp: Date.now(),
|
|
138
|
+
});
|
|
139
|
+
throw err;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
};
|
|
144
|
+
Client.prototype.query.__trickle_patched = true;
|
|
145
|
+
// Also patch Pool if available
|
|
146
|
+
if (pgModule.Pool) {
|
|
147
|
+
const Pool = pgModule.Pool;
|
|
148
|
+
const origPoolQuery = Pool.prototype.query;
|
|
149
|
+
if (origPoolQuery && !origPoolQuery.__trickle_patched) {
|
|
150
|
+
Pool.prototype.query = function patchedPoolQuery(...args) {
|
|
151
|
+
const startTime = performance.now();
|
|
152
|
+
let queryText = '';
|
|
153
|
+
let params;
|
|
154
|
+
if (typeof args[0] === 'string') {
|
|
155
|
+
queryText = args[0];
|
|
156
|
+
params = Array.isArray(args[1]) ? args[1] : undefined;
|
|
157
|
+
}
|
|
158
|
+
else if (args[0] && typeof args[0] === 'object' && args[0].text) {
|
|
159
|
+
queryText = args[0].text;
|
|
160
|
+
params = args[0].values;
|
|
161
|
+
}
|
|
162
|
+
const truncatedQuery = queryText.length > MAX_QUERY_LENGTH
|
|
163
|
+
? queryText.substring(0, MAX_QUERY_LENGTH) + '...'
|
|
164
|
+
: queryText;
|
|
165
|
+
const result = origPoolQuery.apply(this, args);
|
|
166
|
+
if (result && typeof result.then === 'function') {
|
|
167
|
+
return result.then((res) => {
|
|
168
|
+
const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
|
|
169
|
+
writeQuery({
|
|
170
|
+
kind: 'query',
|
|
171
|
+
query: truncatedQuery,
|
|
172
|
+
params: params?.slice(0, 5),
|
|
173
|
+
durationMs,
|
|
174
|
+
rowCount: res.rowCount || 0,
|
|
175
|
+
columns: res.fields?.map((f) => f.name),
|
|
176
|
+
timestamp: Date.now(),
|
|
177
|
+
});
|
|
178
|
+
return res;
|
|
179
|
+
}, (err) => {
|
|
180
|
+
writeQuery({
|
|
181
|
+
kind: 'query',
|
|
182
|
+
query: truncatedQuery,
|
|
183
|
+
durationMs: Math.round((performance.now() - startTime) * 100) / 100,
|
|
184
|
+
rowCount: 0,
|
|
185
|
+
error: err.message?.substring(0, 200),
|
|
186
|
+
timestamp: Date.now(),
|
|
187
|
+
});
|
|
188
|
+
throw err;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
};
|
|
193
|
+
Pool.prototype.query.__trickle_patched = true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (debugMode) {
|
|
197
|
+
console.log('[trickle/db] PostgreSQL query tracing enabled');
|
|
198
|
+
}
|
|
199
|
+
}
|
package/dist/observe-register.js
CHANGED
|
@@ -1123,6 +1123,15 @@ if (enabled) {
|
|
|
1123
1123
|
}
|
|
1124
1124
|
catch { /* fall through to normal processing */ }
|
|
1125
1125
|
}
|
|
1126
|
+
// ── PostgreSQL auto-detection: patch pg to capture SQL queries ──
|
|
1127
|
+
if (request === 'pg' && !expressPatched.has('pg')) {
|
|
1128
|
+
expressPatched.add('pg');
|
|
1129
|
+
try {
|
|
1130
|
+
const { patchPg } = require(path_1.default.join(__dirname, 'db-observer.js'));
|
|
1131
|
+
patchPg(exports, debug);
|
|
1132
|
+
}
|
|
1133
|
+
catch { /* pg observer not critical */ }
|
|
1134
|
+
}
|
|
1126
1135
|
// Resolve to absolute path for dedup — do this FIRST since bundlers like
|
|
1127
1136
|
// tsx/esbuild may use path aliases (e.g., @config/env) that don't start
|
|
1128
1137
|
// with './' or '/'. We need the resolved path to decide if it's user code.
|
package/dist/trace-var.js
CHANGED
|
@@ -85,6 +85,47 @@ function initVarTracer(opts = {}) {
|
|
|
85
85
|
if (debugMode) {
|
|
86
86
|
console.log(`[trickle/vars] Variable tracing enabled → ${varsFilePath}`);
|
|
87
87
|
}
|
|
88
|
+
// Capture console output to .trickle/console.jsonl for agent debugging
|
|
89
|
+
if (process.env.TRICKLE_CAPTURE_CONSOLE !== '0') {
|
|
90
|
+
patchConsole(dir);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Patch console.log/error/warn to also write to console.jsonl */
|
|
94
|
+
function patchConsole(dir) {
|
|
95
|
+
const consoleFile = path.join(dir, 'console.jsonl');
|
|
96
|
+
// Clear previous console log
|
|
97
|
+
try {
|
|
98
|
+
fs.writeFileSync(consoleFile, '');
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const origLog = console.log;
|
|
104
|
+
const origError = console.error;
|
|
105
|
+
const origWarn = console.warn;
|
|
106
|
+
function capture(level, args) {
|
|
107
|
+
try {
|
|
108
|
+
const message = args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ');
|
|
109
|
+
// Skip trickle's own output
|
|
110
|
+
if (message.startsWith('[trickle'))
|
|
111
|
+
return;
|
|
112
|
+
const record = { level, message: message.substring(0, 500), timestamp: Date.now() };
|
|
113
|
+
fs.appendFileSync(consoleFile, JSON.stringify(record) + '\n');
|
|
114
|
+
}
|
|
115
|
+
catch { }
|
|
116
|
+
}
|
|
117
|
+
console.log = function (...args) {
|
|
118
|
+
capture('log', args);
|
|
119
|
+
return origLog.apply(console, args);
|
|
120
|
+
};
|
|
121
|
+
console.error = function (...args) {
|
|
122
|
+
capture('error', args);
|
|
123
|
+
return origError.apply(console, args);
|
|
124
|
+
};
|
|
125
|
+
console.warn = function (...args) {
|
|
126
|
+
capture('warn', args);
|
|
127
|
+
return origWarn.apply(console, args);
|
|
128
|
+
};
|
|
88
129
|
}
|
|
89
130
|
/**
|
|
90
131
|
* Trace a variable's runtime value.
|
package/package.json
CHANGED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database query observer — patches popular database drivers to capture
|
|
3
|
+
* SQL queries, execution time, and result shapes.
|
|
4
|
+
*
|
|
5
|
+
* Currently supports:
|
|
6
|
+
* - pg (node-postgres) — used by Prisma, Knex, Sequelize, TypeORM
|
|
7
|
+
*
|
|
8
|
+
* Captured data is written to .trickle/queries.jsonl as:
|
|
9
|
+
* { query: "SELECT ...", params: [...], durationMs: 2.5, rowCount: 42, columns: [...] }
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
|
|
15
|
+
interface QueryRecord {
|
|
16
|
+
kind: 'query';
|
|
17
|
+
query: string;
|
|
18
|
+
params?: unknown[];
|
|
19
|
+
durationMs: number;
|
|
20
|
+
rowCount: number;
|
|
21
|
+
columns?: string[];
|
|
22
|
+
error?: string;
|
|
23
|
+
timestamp: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let queriesFile: string | null = null;
|
|
27
|
+
let debugMode = false;
|
|
28
|
+
const MAX_QUERY_LENGTH = 500;
|
|
29
|
+
const MAX_QUERIES = 100;
|
|
30
|
+
let queryCount = 0;
|
|
31
|
+
|
|
32
|
+
function getQueriesFile(): string {
|
|
33
|
+
if (queriesFile) return queriesFile;
|
|
34
|
+
const dir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
|
|
35
|
+
try { fs.mkdirSync(dir, { recursive: true }); } catch {}
|
|
36
|
+
queriesFile = path.join(dir, 'queries.jsonl');
|
|
37
|
+
// Clear previous
|
|
38
|
+
try { fs.writeFileSync(queriesFile, ''); } catch {}
|
|
39
|
+
return queriesFile;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function writeQuery(record: QueryRecord): void {
|
|
43
|
+
if (queryCount >= MAX_QUERIES) return;
|
|
44
|
+
queryCount++;
|
|
45
|
+
try {
|
|
46
|
+
fs.appendFileSync(getQueriesFile(), JSON.stringify(record) + '\n');
|
|
47
|
+
} catch {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Patch pg (node-postgres) to capture queries.
|
|
52
|
+
* Called from observe-register when pg is required.
|
|
53
|
+
*/
|
|
54
|
+
export function patchPg(pgModule: any, debug: boolean): void {
|
|
55
|
+
debugMode = debug;
|
|
56
|
+
|
|
57
|
+
// Patch Client.prototype.query
|
|
58
|
+
const Client = pgModule.Client;
|
|
59
|
+
if (!Client || !Client.prototype) return;
|
|
60
|
+
|
|
61
|
+
const originalQuery = Client.prototype.query;
|
|
62
|
+
if ((originalQuery as any).__trickle_patched) return;
|
|
63
|
+
|
|
64
|
+
Client.prototype.query = function patchedQuery(...args: any[]): any {
|
|
65
|
+
const startTime = performance.now();
|
|
66
|
+
|
|
67
|
+
// Extract query text and params
|
|
68
|
+
let queryText = '';
|
|
69
|
+
let params: unknown[] | undefined;
|
|
70
|
+
if (typeof args[0] === 'string') {
|
|
71
|
+
queryText = args[0];
|
|
72
|
+
params = Array.isArray(args[1]) ? args[1] : undefined;
|
|
73
|
+
} else if (args[0] && typeof args[0] === 'object' && args[0].text) {
|
|
74
|
+
queryText = args[0].text;
|
|
75
|
+
params = args[0].values;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const truncatedQuery = queryText.length > MAX_QUERY_LENGTH
|
|
79
|
+
? queryText.substring(0, MAX_QUERY_LENGTH) + '...'
|
|
80
|
+
: queryText;
|
|
81
|
+
|
|
82
|
+
// Call original
|
|
83
|
+
const result = originalQuery.apply(this, args);
|
|
84
|
+
|
|
85
|
+
// Handle promise-based queries
|
|
86
|
+
if (result && typeof result.then === 'function') {
|
|
87
|
+
return result.then(
|
|
88
|
+
(res: any) => {
|
|
89
|
+
const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
|
|
90
|
+
const columns = res.fields?.map((f: any) => f.name) || [];
|
|
91
|
+
writeQuery({
|
|
92
|
+
kind: 'query',
|
|
93
|
+
query: truncatedQuery,
|
|
94
|
+
params: params?.slice(0, 5),
|
|
95
|
+
durationMs,
|
|
96
|
+
rowCount: res.rowCount || 0,
|
|
97
|
+
columns: columns.length > 0 ? columns : undefined,
|
|
98
|
+
timestamp: Date.now(),
|
|
99
|
+
});
|
|
100
|
+
if (debugMode) {
|
|
101
|
+
console.log(`[trickle/db] ${truncatedQuery.substring(0, 60)}... (${durationMs}ms, ${res.rowCount} rows)`);
|
|
102
|
+
}
|
|
103
|
+
return res;
|
|
104
|
+
},
|
|
105
|
+
(err: any) => {
|
|
106
|
+
const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
|
|
107
|
+
writeQuery({
|
|
108
|
+
kind: 'query',
|
|
109
|
+
query: truncatedQuery,
|
|
110
|
+
params: params?.slice(0, 5),
|
|
111
|
+
durationMs,
|
|
112
|
+
rowCount: 0,
|
|
113
|
+
error: err.message?.substring(0, 200),
|
|
114
|
+
timestamp: Date.now(),
|
|
115
|
+
});
|
|
116
|
+
throw err;
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return result;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
(Client.prototype.query as any).__trickle_patched = true;
|
|
125
|
+
|
|
126
|
+
// Also patch Pool if available
|
|
127
|
+
if (pgModule.Pool) {
|
|
128
|
+
const Pool = pgModule.Pool;
|
|
129
|
+
const origPoolQuery = Pool.prototype.query;
|
|
130
|
+
if (origPoolQuery && !(origPoolQuery as any).__trickle_patched) {
|
|
131
|
+
Pool.prototype.query = function patchedPoolQuery(...args: any[]): any {
|
|
132
|
+
const startTime = performance.now();
|
|
133
|
+
let queryText = '';
|
|
134
|
+
let params: unknown[] | undefined;
|
|
135
|
+
if (typeof args[0] === 'string') {
|
|
136
|
+
queryText = args[0];
|
|
137
|
+
params = Array.isArray(args[1]) ? args[1] : undefined;
|
|
138
|
+
} else if (args[0] && typeof args[0] === 'object' && args[0].text) {
|
|
139
|
+
queryText = args[0].text;
|
|
140
|
+
params = args[0].values;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const truncatedQuery = queryText.length > MAX_QUERY_LENGTH
|
|
144
|
+
? queryText.substring(0, MAX_QUERY_LENGTH) + '...'
|
|
145
|
+
: queryText;
|
|
146
|
+
|
|
147
|
+
const result = origPoolQuery.apply(this, args);
|
|
148
|
+
if (result && typeof result.then === 'function') {
|
|
149
|
+
return result.then(
|
|
150
|
+
(res: any) => {
|
|
151
|
+
const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
|
|
152
|
+
writeQuery({
|
|
153
|
+
kind: 'query',
|
|
154
|
+
query: truncatedQuery,
|
|
155
|
+
params: params?.slice(0, 5),
|
|
156
|
+
durationMs,
|
|
157
|
+
rowCount: res.rowCount || 0,
|
|
158
|
+
columns: res.fields?.map((f: any) => f.name),
|
|
159
|
+
timestamp: Date.now(),
|
|
160
|
+
});
|
|
161
|
+
return res;
|
|
162
|
+
},
|
|
163
|
+
(err: any) => {
|
|
164
|
+
writeQuery({
|
|
165
|
+
kind: 'query',
|
|
166
|
+
query: truncatedQuery,
|
|
167
|
+
durationMs: Math.round((performance.now() - startTime) * 100) / 100,
|
|
168
|
+
rowCount: 0,
|
|
169
|
+
error: err.message?.substring(0, 200),
|
|
170
|
+
timestamp: Date.now(),
|
|
171
|
+
});
|
|
172
|
+
throw err;
|
|
173
|
+
},
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
};
|
|
178
|
+
(Pool.prototype.query as any).__trickle_patched = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (debugMode) {
|
|
183
|
+
console.log('[trickle/db] PostgreSQL query tracing enabled');
|
|
184
|
+
}
|
|
185
|
+
}
|
package/src/observe-register.ts
CHANGED
|
@@ -1113,6 +1113,15 @@ if (enabled) {
|
|
|
1113
1113
|
} catch { /* fall through to normal processing */ }
|
|
1114
1114
|
}
|
|
1115
1115
|
|
|
1116
|
+
// ── PostgreSQL auto-detection: patch pg to capture SQL queries ──
|
|
1117
|
+
if (request === 'pg' && !expressPatched.has('pg')) {
|
|
1118
|
+
expressPatched.add('pg');
|
|
1119
|
+
try {
|
|
1120
|
+
const { patchPg } = require(path.join(__dirname, 'db-observer.js'));
|
|
1121
|
+
patchPg(exports, debug);
|
|
1122
|
+
} catch { /* pg observer not critical */ }
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1116
1125
|
// Resolve to absolute path for dedup — do this FIRST since bundlers like
|
|
1117
1126
|
// tsx/esbuild may use path aliases (e.g., @config/env) that don't start
|
|
1118
1127
|
// with './' or '/'. We need the resolved path to decide if it's user code.
|
package/src/trace-var.ts
CHANGED
|
@@ -63,6 +63,47 @@ export function initVarTracer(opts: { debug?: boolean } = {}): void {
|
|
|
63
63
|
if (debugMode) {
|
|
64
64
|
console.log(`[trickle/vars] Variable tracing enabled → ${varsFilePath}`);
|
|
65
65
|
}
|
|
66
|
+
|
|
67
|
+
// Capture console output to .trickle/console.jsonl for agent debugging
|
|
68
|
+
if (process.env.TRICKLE_CAPTURE_CONSOLE !== '0') {
|
|
69
|
+
patchConsole(dir);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Patch console.log/error/warn to also write to console.jsonl */
|
|
74
|
+
function patchConsole(dir: string): void {
|
|
75
|
+
const consoleFile = path.join(dir, 'console.jsonl');
|
|
76
|
+
// Clear previous console log
|
|
77
|
+
try { fs.writeFileSync(consoleFile, ''); } catch { return; }
|
|
78
|
+
|
|
79
|
+
const origLog = console.log;
|
|
80
|
+
const origError = console.error;
|
|
81
|
+
const origWarn = console.warn;
|
|
82
|
+
|
|
83
|
+
function capture(level: string, args: unknown[]): void {
|
|
84
|
+
try {
|
|
85
|
+
const message = args.map(a =>
|
|
86
|
+
typeof a === 'string' ? a : JSON.stringify(a)
|
|
87
|
+
).join(' ');
|
|
88
|
+
// Skip trickle's own output
|
|
89
|
+
if (message.startsWith('[trickle')) return;
|
|
90
|
+
const record = { level, message: message.substring(0, 500), timestamp: Date.now() };
|
|
91
|
+
fs.appendFileSync(consoleFile, JSON.stringify(record) + '\n');
|
|
92
|
+
} catch {}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log = function (...args: unknown[]) {
|
|
96
|
+
capture('log', args);
|
|
97
|
+
return origLog.apply(console, args);
|
|
98
|
+
};
|
|
99
|
+
console.error = function (...args: unknown[]) {
|
|
100
|
+
capture('error', args);
|
|
101
|
+
return origError.apply(console, args);
|
|
102
|
+
};
|
|
103
|
+
console.warn = function (...args: unknown[]) {
|
|
104
|
+
capture('warn', args);
|
|
105
|
+
return origWarn.apply(console, args);
|
|
106
|
+
};
|
|
66
107
|
}
|
|
67
108
|
|
|
68
109
|
/**
|