@zintrust/trace 0.4.82 → 0.4.83
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/README.md +6 -0
- package/dist/build-manifest.json +15 -15
- package/dist/config.js +1 -0
- package/dist/register.js +20 -4
- package/dist/types.d.ts +1 -0
- package/dist/watchers/QueryWatcher.d.ts +5 -1
- package/dist/watchers/QueryWatcher.js +49 -37
- package/package.json +1 -1
- package/src/config.ts +1 -0
- package/src/register.ts +35 -5
- package/src/types.ts +1 -0
- package/src/watchers/QueryWatcher.ts +53 -39
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ You can still import the package migrations manually if you prefer to keep them
|
|
|
29
29
|
```env
|
|
30
30
|
TRACE_ENABLED=true
|
|
31
31
|
TRACE_DB_CONNECTION=d1 # optional — omit to inherit DB_CONNECTION
|
|
32
|
+
TRACE_QUERY_CONNECTION=main # optional — app DB to observe for SQL traces
|
|
32
33
|
TRACE_PRUNE_HOURS=24 # how long entries are kept (default: 24)
|
|
33
34
|
TRACE_SLOW_QUERY_MS=100 # slow-query threshold in ms (default: 100)
|
|
34
35
|
TRACE_LOG_LEVEL=info # minimum log level captured (default: info)
|
|
@@ -77,6 +78,7 @@ import type { TraceConfigOverrides } from '@zintrust/trace';
|
|
|
77
78
|
export default {
|
|
78
79
|
enabled: Env.getBool('TRACE_ENABLED', false),
|
|
79
80
|
connection: Env.get('TRACE_DB_CONNECTION', '') || undefined,
|
|
81
|
+
observeConnection: Env.get('TRACE_QUERY_CONNECTION', '') || undefined,
|
|
80
82
|
pruneAfterHours: Env.getInt('TRACE_PRUNE_HOURS', 24),
|
|
81
83
|
slowQueryThreshold: Env.getInt('TRACE_SLOW_QUERY_MS', 100),
|
|
82
84
|
logMinLevel: Env.get('TRACE_LOG_LEVEL', 'info') as TraceConfigOverrides['logMinLevel'],
|
|
@@ -102,6 +104,10 @@ export default {
|
|
|
102
104
|
|
|
103
105
|
All include/exclude matching is contains-based, so a term like `report` matches `/reports/daily`, `monthly-report`, or any other trace content containing that fragment.
|
|
104
106
|
|
|
107
|
+
When trace storage lives on a different connection than your application data, keep `TRACE_DB_CONNECTION` pointed at the trace tables and set `TRACE_QUERY_CONNECTION` to the app connection you want SQL traces to observe. If you omit `TRACE_QUERY_CONNECTION`, trace automatically falls back to the main `DB_CONNECTION` whenever the storage connection is different.
|
|
108
|
+
|
|
109
|
+
When ZinTrust proxy runtimes also have trace enabled, proxy-executed SQL and SMTP operations are traced at the proxy boundary too. That means MySQL, PostgreSQL, SQL Server, and D1 proxy requests can still record the final SQL plus bound values, and the SMTP proxy can persist rendered mail text and HTML, even when the consumer application is talking to the proxy rather than executing the transport locally.
|
|
110
|
+
|
|
105
111
|
### 3. Mount the dashboard
|
|
106
112
|
|
|
107
113
|
Register the dashboard explicitly in your route file when you want the UI. Restrict access with middleware — the trace does **not** apply auth automatically.
|
package/dist/build-manifest.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/trace",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"buildDate": "2026-04-
|
|
3
|
+
"version": "0.4.83",
|
|
4
|
+
"buildDate": "2026-04-08T17:13:19.081Z",
|
|
5
5
|
"buildEnvironment": {
|
|
6
6
|
"node": "v22.22.1",
|
|
7
7
|
"platform": "darwin",
|
|
8
8
|
"arch": "arm64"
|
|
9
9
|
},
|
|
10
10
|
"git": {
|
|
11
|
-
"commit": "
|
|
11
|
+
"commit": "3c792064",
|
|
12
12
|
"branch": "release"
|
|
13
13
|
},
|
|
14
14
|
"package": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"files": {
|
|
24
24
|
"build-manifest.json": {
|
|
25
25
|
"size": 14439,
|
|
26
|
-
"sha256": "
|
|
26
|
+
"sha256": "dab00c1ade5c0b5fe2b10b5eeeae31dfd9a017f198794e18e7c2b69cfb5560d8"
|
|
27
27
|
},
|
|
28
28
|
"cli-register.d.ts": {
|
|
29
29
|
"size": 255,
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"sha256": "b034cbef0c71fb868071363624ef7a9f8d7acc20f8be8c895dd5db5a75e81f37"
|
|
39
39
|
},
|
|
40
40
|
"config.js": {
|
|
41
|
-
"size":
|
|
42
|
-
"sha256": "
|
|
41
|
+
"size": 5961,
|
|
42
|
+
"sha256": "1402cf8ad7c850da99e429c8c937385b7b5cf401fa3714d972d4e6bd94f1663e"
|
|
43
43
|
},
|
|
44
44
|
"context.d.ts": {
|
|
45
45
|
"size": 596,
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
},
|
|
80
80
|
"index.js": {
|
|
81
81
|
"size": 3255,
|
|
82
|
-
"sha256": "
|
|
82
|
+
"sha256": "a293d38a9e56a2ab802f99143eb1eaebb18f854e610f29bc95878746ffe26326"
|
|
83
83
|
},
|
|
84
84
|
"migrations/20260331000001_create_zin_trace_entries_table.d.ts": {
|
|
85
85
|
"size": 304,
|
|
@@ -134,8 +134,8 @@
|
|
|
134
134
|
"sha256": "71d366165dd36f1675aa253a76262b226fb6c62e5ab632746b8aea61c0c625fc"
|
|
135
135
|
},
|
|
136
136
|
"register.js": {
|
|
137
|
-
"size":
|
|
138
|
-
"sha256": "
|
|
137
|
+
"size": 12522,
|
|
138
|
+
"sha256": "66bccec7fac4d669702fa18e439788325d36ce17f228639005ffcde2cd968d70"
|
|
139
139
|
},
|
|
140
140
|
"storage/DebuggerStorage.d.ts": {
|
|
141
141
|
"size": 517,
|
|
@@ -186,8 +186,8 @@
|
|
|
186
186
|
"sha256": "d916e8e3abb1b1087f6b184851b0e6265e53380d7857b008e745d566aad15d44"
|
|
187
187
|
},
|
|
188
188
|
"types.d.ts": {
|
|
189
|
-
"size":
|
|
190
|
-
"sha256": "
|
|
189
|
+
"size": 8448,
|
|
190
|
+
"sha256": "de994120c04696e08afb428cff27a99fb2918f5c4277b8190b8fb3d36d139f0d"
|
|
191
191
|
},
|
|
192
192
|
"types.js": {
|
|
193
193
|
"size": 696,
|
|
@@ -378,12 +378,12 @@
|
|
|
378
378
|
"sha256": "b0ecc1df6a49dc8c5ffeb8dff0f1e3594ba016c4de3424dc8b5d7832e2f4cd11"
|
|
379
379
|
},
|
|
380
380
|
"watchers/QueryWatcher.d.ts": {
|
|
381
|
-
"size":
|
|
382
|
-
"sha256": "
|
|
381
|
+
"size": 240,
|
|
382
|
+
"sha256": "5d5046c65e5b683369c7709f1acd09b60aec3e7f44748fd1baeb35498836465b"
|
|
383
383
|
},
|
|
384
384
|
"watchers/QueryWatcher.js": {
|
|
385
|
-
"size":
|
|
386
|
-
"sha256": "
|
|
385
|
+
"size": 2935,
|
|
386
|
+
"sha256": "577c6fec0282d2290db5c4b6c606b9b6ecdd64209af2b09f3205a15bf656bbef"
|
|
387
387
|
},
|
|
388
388
|
"watchers/RedisWatcher.d.ts": {
|
|
389
389
|
"size": 294,
|
package/dist/config.js
CHANGED
package/dist/register.js
CHANGED
|
@@ -64,6 +64,17 @@ const resolveTraceConnectionName = (env, configuredConnection) => {
|
|
|
64
64
|
}
|
|
65
65
|
return resolveDefaultConnection();
|
|
66
66
|
};
|
|
67
|
+
const resolveObservedConnectionName = (env, configuredObservedConnection, storageConnectionName) => {
|
|
68
|
+
if (typeof configuredObservedConnection === 'string' &&
|
|
69
|
+
configuredObservedConnection.trim() !== '') {
|
|
70
|
+
return resolveTraceConnectionName(env, configuredObservedConnection);
|
|
71
|
+
}
|
|
72
|
+
const defaultConnectionName = resolveTraceConnectionName(env, undefined);
|
|
73
|
+
if (storageConnectionName !== defaultConnectionName) {
|
|
74
|
+
return defaultConnectionName;
|
|
75
|
+
}
|
|
76
|
+
return storageConnectionName;
|
|
77
|
+
};
|
|
67
78
|
const isObjectValue = (value) => {
|
|
68
79
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
69
80
|
};
|
|
@@ -146,6 +157,7 @@ if (!traceAlreadyInitialized && Env) {
|
|
|
146
157
|
const enabled = startupOverrides?.enabled === true || Env.getBool('TRACE_ENABLED', false);
|
|
147
158
|
if (enabled) {
|
|
148
159
|
const connectionRaw = Env.get('TRACE_DB_CONNECTION', '').trim();
|
|
160
|
+
const observeConnectionRaw = Env.get('TRACE_QUERY_CONNECTION', '').trim();
|
|
149
161
|
const pruneAfterHoursRaw = Env.get('TRACE_PRUNE_HOURS', '').trim();
|
|
150
162
|
const slowQueryThresholdRaw = Env.get('TRACE_SLOW_QUERY_MS', '').trim();
|
|
151
163
|
const logMinLevelRaw = Env.get('TRACE_LOG_LEVEL', '').trim();
|
|
@@ -156,6 +168,7 @@ if (!traceAlreadyInitialized && Env) {
|
|
|
156
168
|
const redactionBody = parseEnvList(Env.get('TRACE_REDACT_BODY', ''));
|
|
157
169
|
const redactionQuery = parseEnvList(Env.get('TRACE_REDACT_QUERY', ''));
|
|
158
170
|
const connection = connectionRaw === '' ? startupOverrides?.connection : connectionRaw;
|
|
171
|
+
const observeConnection = observeConnectionRaw === '' ? startupOverrides?.observeConnection : observeConnectionRaw;
|
|
159
172
|
const pruneAfterHours = pruneAfterHoursRaw === ''
|
|
160
173
|
? startupOverrides?.pruneAfterHours
|
|
161
174
|
: Number.parseInt(pruneAfterHoursRaw, 10);
|
|
@@ -176,6 +189,7 @@ if (!traceAlreadyInitialized && Env) {
|
|
|
176
189
|
...startupOverrides,
|
|
177
190
|
enabled,
|
|
178
191
|
connection,
|
|
192
|
+
observeConnection,
|
|
179
193
|
...(typeof pruneAfterHours === 'number' && Number.isFinite(pruneAfterHours)
|
|
180
194
|
? { pruneAfterHours }
|
|
181
195
|
: {}),
|
|
@@ -188,9 +202,11 @@ if (!traceAlreadyInitialized && Env) {
|
|
|
188
202
|
...(redaction === undefined ? {} : { redaction }),
|
|
189
203
|
});
|
|
190
204
|
const resolvedConnectionName = resolveTraceConnectionName(Env, config.connection);
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
205
|
+
const resolvedObservedConnectionName = resolveObservedConnectionName(Env, config.observeConnection, resolvedConnectionName);
|
|
206
|
+
const storageDb = core.useDatabase?.(undefined, resolvedConnectionName);
|
|
207
|
+
const observedDb = core.useDatabase?.(undefined, resolvedObservedConnectionName);
|
|
208
|
+
if (storageDb && observedDb) {
|
|
209
|
+
const storage = TraceWriteDiagnostics.wrapStorage(TraceContentRedaction.wrapStorage(TraceEntryFiltering.wrapStorage(TraceStorage.resolveStorage(storageDb), config), config.redaction), {
|
|
194
210
|
connectionName: resolvedConnectionName,
|
|
195
211
|
logger: core.Logger,
|
|
196
212
|
});
|
|
@@ -219,7 +235,7 @@ if (!traceAlreadyInitialized && Env) {
|
|
|
219
235
|
import('./watchers/ViewWatcher.js'),
|
|
220
236
|
import('./watchers/HttpClientWatcher.js'),
|
|
221
237
|
]);
|
|
222
|
-
const watcherArgs = { storage, config, db };
|
|
238
|
+
const watcherArgs = { storage, config, db: observedDb };
|
|
223
239
|
HttpWatcher.register({ ...watcherArgs, registerMiddleware: resolveRegisterMiddleware() });
|
|
224
240
|
QueryWatcher.register(watcherArgs);
|
|
225
241
|
LogWatcher.register(watcherArgs);
|
package/dist/types.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
import type { ITraceWatcher } from '../types';
|
|
2
|
-
|
|
2
|
+
declare const emit: (query: string, params: unknown[], duration: number, connection?: string) => void;
|
|
3
|
+
export declare const QueryWatcher: ITraceWatcher & {
|
|
4
|
+
emit: typeof emit;
|
|
5
|
+
};
|
|
6
|
+
export {};
|
|
@@ -6,6 +6,8 @@ import { TraceStorage } from '../storage/index.js';
|
|
|
6
6
|
import { EntryType } from '../types.js';
|
|
7
7
|
import { AuthTag } from '../utils/authTag.js';
|
|
8
8
|
import { RequestFilter } from '../utils/requestFilter.js';
|
|
9
|
+
let _storage = null;
|
|
10
|
+
let _config = null;
|
|
9
11
|
const bindingsInterpolated = (sql, params) => {
|
|
10
12
|
// Inline params for display only — safe, not for re-execution.
|
|
11
13
|
let i = 0;
|
|
@@ -22,54 +24,64 @@ const isTraceStorageQuery = (sql) => {
|
|
|
22
24
|
const normalized = sql.toLowerCase();
|
|
23
25
|
return normalized.includes('zin_trace_entries') || normalized.includes('zin_trace_monitoring');
|
|
24
26
|
};
|
|
27
|
+
const emit = (query, params, duration, connection = 'default') => {
|
|
28
|
+
if (_storage === null || _config === null)
|
|
29
|
+
return;
|
|
30
|
+
if (RequestFilter.shouldIgnoreCurrentRequest(_config.ignoreRoutes))
|
|
31
|
+
return;
|
|
32
|
+
if (isTraceStorageQuery(query))
|
|
33
|
+
return;
|
|
34
|
+
const batchId = TraceContext.getBatchId();
|
|
35
|
+
const includeBindings = _config.captureQueryBindings !== false;
|
|
36
|
+
const sql = includeBindings ? bindingsInterpolated(query, params) : query;
|
|
37
|
+
const roundedDuration = Math.round(duration * 100) / 100;
|
|
38
|
+
const hash = TraceStorage.familyHash(query);
|
|
39
|
+
const slow = roundedDuration >= _config.slowQueryThreshold;
|
|
40
|
+
const content = {
|
|
41
|
+
connection,
|
|
42
|
+
sql,
|
|
43
|
+
statement: query,
|
|
44
|
+
...(includeBindings ? { bindings: [...params] } : {}),
|
|
45
|
+
bindingsIncluded: includeBindings,
|
|
46
|
+
time: roundedDuration,
|
|
47
|
+
duration: roundedDuration,
|
|
48
|
+
slow,
|
|
49
|
+
hash,
|
|
50
|
+
hostname: TraceContext.getHostname(),
|
|
51
|
+
};
|
|
52
|
+
const tags = AuthTag.append([]);
|
|
53
|
+
if (slow)
|
|
54
|
+
tags.push('slow');
|
|
55
|
+
_storage
|
|
56
|
+
.writeEntry({
|
|
57
|
+
uuid: crypto.randomUUID(),
|
|
58
|
+
batchId,
|
|
59
|
+
familyHash: hash,
|
|
60
|
+
type: EntryType.QUERY,
|
|
61
|
+
content,
|
|
62
|
+
tags,
|
|
63
|
+
isLatest: true,
|
|
64
|
+
createdAt: TraceContext.now(),
|
|
65
|
+
})
|
|
66
|
+
.catch(() => undefined);
|
|
67
|
+
};
|
|
25
68
|
export const QueryWatcher = Object.freeze({
|
|
69
|
+
emit,
|
|
26
70
|
register({ storage, config, db: injectedDb }) {
|
|
27
71
|
if (config.watchers.query === false)
|
|
28
72
|
return () => undefined;
|
|
29
73
|
if (!injectedDb)
|
|
30
74
|
return () => undefined; // no db available
|
|
75
|
+
_storage = storage;
|
|
76
|
+
_config = config;
|
|
31
77
|
const db = injectedDb;
|
|
32
78
|
const handler = (query, params, duration) => {
|
|
33
|
-
|
|
34
|
-
return;
|
|
35
|
-
if (isTraceStorageQuery(query))
|
|
36
|
-
return;
|
|
37
|
-
const batchId = TraceContext.getBatchId();
|
|
38
|
-
const includeBindings = config.captureQueryBindings !== false;
|
|
39
|
-
const sql = includeBindings ? bindingsInterpolated(query, params) : query;
|
|
40
|
-
const roundedDuration = Math.round(duration * 100) / 100;
|
|
41
|
-
const hash = TraceStorage.familyHash(query);
|
|
42
|
-
const slow = roundedDuration >= config.slowQueryThreshold;
|
|
43
|
-
const content = {
|
|
44
|
-
connection: 'default',
|
|
45
|
-
sql,
|
|
46
|
-
statement: query,
|
|
47
|
-
...(includeBindings ? { bindings: [...params] } : {}),
|
|
48
|
-
bindingsIncluded: includeBindings,
|
|
49
|
-
time: roundedDuration,
|
|
50
|
-
duration: roundedDuration,
|
|
51
|
-
slow,
|
|
52
|
-
hash,
|
|
53
|
-
hostname: TraceContext.getHostname(),
|
|
54
|
-
};
|
|
55
|
-
const tags = AuthTag.append([]);
|
|
56
|
-
if (slow)
|
|
57
|
-
tags.push('slow');
|
|
58
|
-
storage
|
|
59
|
-
.writeEntry({
|
|
60
|
-
uuid: crypto.randomUUID(),
|
|
61
|
-
batchId,
|
|
62
|
-
familyHash: hash,
|
|
63
|
-
type: EntryType.QUERY,
|
|
64
|
-
content,
|
|
65
|
-
tags,
|
|
66
|
-
isLatest: true,
|
|
67
|
-
createdAt: TraceContext.now(),
|
|
68
|
-
})
|
|
69
|
-
.catch(() => undefined);
|
|
79
|
+
emit(query, params, duration);
|
|
70
80
|
};
|
|
71
81
|
db.onAfterQuery?.(handler);
|
|
72
82
|
return () => {
|
|
83
|
+
_storage = null;
|
|
84
|
+
_config = null;
|
|
73
85
|
db.offAfterQuery?.(handler);
|
|
74
86
|
};
|
|
75
87
|
},
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -106,6 +106,7 @@ const mergeWatchers = (
|
|
|
106
106
|
const DEFAULTS: ITraceConfig = Object.freeze({
|
|
107
107
|
enabled: false,
|
|
108
108
|
connection: undefined,
|
|
109
|
+
observeConnection: undefined,
|
|
109
110
|
pruneAfterHours: 24,
|
|
110
111
|
ignoreRoutes: ['/trace', '/health', '/ping'],
|
|
111
112
|
slowQueryThreshold: 100,
|
package/src/register.ts
CHANGED
|
@@ -113,6 +113,26 @@ const resolveTraceConnectionName = (
|
|
|
113
113
|
return resolveDefaultConnection();
|
|
114
114
|
};
|
|
115
115
|
|
|
116
|
+
const resolveObservedConnectionName = (
|
|
117
|
+
env: Pick<NonNullable<CoreApi['Env']>, 'get'> | undefined,
|
|
118
|
+
configuredObservedConnection: string | undefined,
|
|
119
|
+
storageConnectionName: string
|
|
120
|
+
): string => {
|
|
121
|
+
if (
|
|
122
|
+
typeof configuredObservedConnection === 'string' &&
|
|
123
|
+
configuredObservedConnection.trim() !== ''
|
|
124
|
+
) {
|
|
125
|
+
return resolveTraceConnectionName(env, configuredObservedConnection);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const defaultConnectionName = resolveTraceConnectionName(env, undefined);
|
|
129
|
+
if (storageConnectionName !== defaultConnectionName) {
|
|
130
|
+
return defaultConnectionName;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return storageConnectionName;
|
|
134
|
+
};
|
|
135
|
+
|
|
116
136
|
const isObjectValue = (value: unknown): value is Record<string, unknown> => {
|
|
117
137
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
118
138
|
};
|
|
@@ -206,6 +226,7 @@ if (!traceAlreadyInitialized && Env) {
|
|
|
206
226
|
|
|
207
227
|
if (enabled) {
|
|
208
228
|
const connectionRaw = Env.get('TRACE_DB_CONNECTION', '').trim();
|
|
229
|
+
const observeConnectionRaw = Env.get('TRACE_QUERY_CONNECTION', '').trim();
|
|
209
230
|
const pruneAfterHoursRaw = Env.get('TRACE_PRUNE_HOURS', '').trim();
|
|
210
231
|
const slowQueryThresholdRaw = Env.get('TRACE_SLOW_QUERY_MS', '').trim();
|
|
211
232
|
const logMinLevelRaw = Env.get('TRACE_LOG_LEVEL', '').trim();
|
|
@@ -217,6 +238,8 @@ if (!traceAlreadyInitialized && Env) {
|
|
|
217
238
|
const redactionQuery = parseEnvList(Env.get('TRACE_REDACT_QUERY', ''));
|
|
218
239
|
|
|
219
240
|
const connection = connectionRaw === '' ? startupOverrides?.connection : connectionRaw;
|
|
241
|
+
const observeConnection =
|
|
242
|
+
observeConnectionRaw === '' ? startupOverrides?.observeConnection : observeConnectionRaw;
|
|
220
243
|
const pruneAfterHours =
|
|
221
244
|
pruneAfterHoursRaw === ''
|
|
222
245
|
? startupOverrides?.pruneAfterHours
|
|
@@ -247,6 +270,7 @@ if (!traceAlreadyInitialized && Env) {
|
|
|
247
270
|
...startupOverrides,
|
|
248
271
|
enabled,
|
|
249
272
|
connection,
|
|
273
|
+
observeConnection,
|
|
250
274
|
...(typeof pruneAfterHours === 'number' && Number.isFinite(pruneAfterHours)
|
|
251
275
|
? { pruneAfterHours }
|
|
252
276
|
: {}),
|
|
@@ -260,12 +284,18 @@ if (!traceAlreadyInitialized && Env) {
|
|
|
260
284
|
});
|
|
261
285
|
|
|
262
286
|
const resolvedConnectionName = resolveTraceConnectionName(Env, config.connection);
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
287
|
+
const resolvedObservedConnectionName = resolveObservedConnectionName(
|
|
288
|
+
Env,
|
|
289
|
+
config.observeConnection,
|
|
290
|
+
resolvedConnectionName
|
|
291
|
+
);
|
|
292
|
+
const storageDb = core.useDatabase?.(undefined, resolvedConnectionName);
|
|
293
|
+
const observedDb = core.useDatabase?.(undefined, resolvedObservedConnectionName);
|
|
294
|
+
|
|
295
|
+
if (storageDb && observedDb) {
|
|
266
296
|
const storage = TraceWriteDiagnostics.wrapStorage(
|
|
267
297
|
TraceContentRedaction.wrapStorage(
|
|
268
|
-
TraceEntryFiltering.wrapStorage(TraceStorage.resolveStorage(
|
|
298
|
+
TraceEntryFiltering.wrapStorage(TraceStorage.resolveStorage(storageDb), config),
|
|
269
299
|
config.redaction
|
|
270
300
|
),
|
|
271
301
|
{
|
|
@@ -327,7 +357,7 @@ if (!traceAlreadyInitialized && Env) {
|
|
|
327
357
|
import('./watchers/HttpClientWatcher'),
|
|
328
358
|
]);
|
|
329
359
|
|
|
330
|
-
const watcherArgs = { storage, config, db };
|
|
360
|
+
const watcherArgs = { storage, config, db: observedDb };
|
|
331
361
|
|
|
332
362
|
HttpWatcher.register({ ...watcherArgs, registerMiddleware: resolveRegisterMiddleware() });
|
|
333
363
|
|
package/src/types.ts
CHANGED
|
@@ -8,6 +8,9 @@ import { EntryType } from '../types';
|
|
|
8
8
|
import { AuthTag } from '../utils/authTag';
|
|
9
9
|
import { RequestFilter } from '../utils/requestFilter';
|
|
10
10
|
|
|
11
|
+
let _storage: ITraceWatcherConfig['storage'] | null = null;
|
|
12
|
+
let _config: ITraceWatcherConfig['config'] | null = null;
|
|
13
|
+
|
|
11
14
|
const bindingsInterpolated = (sql: string, params: unknown[]): string => {
|
|
12
15
|
// Inline params for display only — safe, not for re-execution.
|
|
13
16
|
let i = 0;
|
|
@@ -24,52 +27,61 @@ const isTraceStorageQuery = (sql: string): boolean => {
|
|
|
24
27
|
return normalized.includes('zin_trace_entries') || normalized.includes('zin_trace_monitoring');
|
|
25
28
|
};
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
const emit = (query: string, params: unknown[], duration: number, connection = 'default'): void => {
|
|
31
|
+
if (_storage === null || _config === null) return;
|
|
32
|
+
if (RequestFilter.shouldIgnoreCurrentRequest(_config.ignoreRoutes)) return;
|
|
33
|
+
if (isTraceStorageQuery(query)) return;
|
|
34
|
+
|
|
35
|
+
const batchId = TraceContext.getBatchId();
|
|
36
|
+
const includeBindings = _config.captureQueryBindings !== false;
|
|
37
|
+
const sql = includeBindings ? bindingsInterpolated(query, params) : query;
|
|
38
|
+
const roundedDuration = Math.round(duration * 100) / 100;
|
|
39
|
+
const hash = TraceStorage.familyHash(query);
|
|
40
|
+
const slow = roundedDuration >= _config.slowQueryThreshold;
|
|
41
|
+
|
|
42
|
+
const content: QueryContent = {
|
|
43
|
+
connection,
|
|
44
|
+
sql,
|
|
45
|
+
statement: query,
|
|
46
|
+
...(includeBindings ? { bindings: [...params] } : {}),
|
|
47
|
+
bindingsIncluded: includeBindings,
|
|
48
|
+
time: roundedDuration,
|
|
49
|
+
duration: roundedDuration,
|
|
50
|
+
slow,
|
|
51
|
+
hash,
|
|
52
|
+
hostname: TraceContext.getHostname(),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const tags = AuthTag.append([]);
|
|
56
|
+
if (slow) tags.push('slow');
|
|
57
|
+
|
|
58
|
+
_storage
|
|
59
|
+
.writeEntry({
|
|
60
|
+
uuid: crypto.randomUUID(),
|
|
61
|
+
batchId,
|
|
62
|
+
familyHash: hash,
|
|
63
|
+
type: EntryType.QUERY,
|
|
64
|
+
content,
|
|
65
|
+
tags,
|
|
66
|
+
isLatest: true,
|
|
67
|
+
createdAt: TraceContext.now(),
|
|
68
|
+
})
|
|
69
|
+
.catch(() => undefined);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const QueryWatcher: ITraceWatcher & { emit: typeof emit } = Object.freeze({
|
|
73
|
+
emit,
|
|
74
|
+
|
|
28
75
|
register({ storage, config, db: injectedDb }: ITraceWatcherConfig): () => void {
|
|
29
76
|
if (config.watchers.query === false) return () => undefined;
|
|
30
77
|
if (!injectedDb) return () => undefined; // no db available
|
|
31
78
|
|
|
79
|
+
_storage = storage;
|
|
80
|
+
_config = config;
|
|
32
81
|
const db = injectedDb;
|
|
33
82
|
|
|
34
83
|
const handler = (query: string, params: unknown[], duration: number): void => {
|
|
35
|
-
|
|
36
|
-
if (isTraceStorageQuery(query)) return;
|
|
37
|
-
|
|
38
|
-
const batchId = TraceContext.getBatchId();
|
|
39
|
-
const includeBindings = config.captureQueryBindings !== false;
|
|
40
|
-
const sql = includeBindings ? bindingsInterpolated(query, params) : query;
|
|
41
|
-
const roundedDuration = Math.round(duration * 100) / 100;
|
|
42
|
-
const hash = TraceStorage.familyHash(query);
|
|
43
|
-
const slow = roundedDuration >= config.slowQueryThreshold;
|
|
44
|
-
|
|
45
|
-
const content: QueryContent = {
|
|
46
|
-
connection: 'default',
|
|
47
|
-
sql,
|
|
48
|
-
statement: query,
|
|
49
|
-
...(includeBindings ? { bindings: [...params] } : {}),
|
|
50
|
-
bindingsIncluded: includeBindings,
|
|
51
|
-
time: roundedDuration,
|
|
52
|
-
duration: roundedDuration,
|
|
53
|
-
slow,
|
|
54
|
-
hash,
|
|
55
|
-
hostname: TraceContext.getHostname(),
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const tags = AuthTag.append([]);
|
|
59
|
-
if (slow) tags.push('slow');
|
|
60
|
-
|
|
61
|
-
storage
|
|
62
|
-
.writeEntry({
|
|
63
|
-
uuid: crypto.randomUUID(),
|
|
64
|
-
batchId,
|
|
65
|
-
familyHash: hash,
|
|
66
|
-
type: EntryType.QUERY,
|
|
67
|
-
content,
|
|
68
|
-
tags,
|
|
69
|
-
isLatest: true,
|
|
70
|
-
createdAt: TraceContext.now(),
|
|
71
|
-
})
|
|
72
|
-
.catch(() => undefined);
|
|
84
|
+
emit(query, params, duration);
|
|
73
85
|
};
|
|
74
86
|
|
|
75
87
|
(
|
|
@@ -79,6 +91,8 @@ export const QueryWatcher: ITraceWatcher = Object.freeze({
|
|
|
79
91
|
).onAfterQuery?.(handler);
|
|
80
92
|
|
|
81
93
|
return () => {
|
|
94
|
+
_storage = null;
|
|
95
|
+
_config = null;
|
|
82
96
|
(
|
|
83
97
|
db as {
|
|
84
98
|
offAfterQuery?: (h: (sql: string, params: unknown[], duration: number) => void) => void;
|