@zintrust/trace 0.4.86 → 0.4.92
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/build-manifest.json +10 -10
- package/dist/register.js +101 -56
- package/dist/watchers/LogWatcher.js +32 -3
- package/dist/watchers/QueryWatcher.js +3 -1
- package/package.json +2 -2
- package/src/register.ts +171 -90
- package/src/watchers/LogWatcher.ts +45 -3
- package/src/watchers/QueryWatcher.ts +5 -1
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.92",
|
|
4
|
+
"buildDate": "2026-04-10T08:41:38.521Z",
|
|
5
5
|
"buildEnvironment": {
|
|
6
6
|
"node": "v20.20.2",
|
|
7
7
|
"platform": "linux",
|
|
8
8
|
"arch": "x64"
|
|
9
9
|
},
|
|
10
10
|
"git": {
|
|
11
|
-
"commit": "
|
|
11
|
+
"commit": "d195a996",
|
|
12
12
|
"branch": "master"
|
|
13
13
|
},
|
|
14
14
|
"package": {
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
},
|
|
76
76
|
"index.js": {
|
|
77
77
|
"size": 3255,
|
|
78
|
-
"sha256": "
|
|
78
|
+
"sha256": "683151fa06b666ec62b150a4fda949c3d35221228b8538644ef740252b63587d"
|
|
79
79
|
},
|
|
80
80
|
"migrations/20260331000001_create_zin_trace_entries_table.d.ts": {
|
|
81
81
|
"size": 304,
|
|
@@ -130,8 +130,8 @@
|
|
|
130
130
|
"sha256": "71d366165dd36f1675aa253a76262b226fb6c62e5ab632746b8aea61c0c625fc"
|
|
131
131
|
},
|
|
132
132
|
"register.js": {
|
|
133
|
-
"size":
|
|
134
|
-
"sha256": "
|
|
133
|
+
"size": 14225,
|
|
134
|
+
"sha256": "07e1643982cc93149c609d44beb11dea168ecf1cac3f78d95d6d22fedcafbc23"
|
|
135
135
|
},
|
|
136
136
|
"storage/TraceContentRedaction.d.ts": {
|
|
137
137
|
"size": 207,
|
|
@@ -330,8 +330,8 @@
|
|
|
330
330
|
"sha256": "f3ddc5f8b58c6c86ac6b464dd48e5a55e79ab2bf2e735feacffc7480e4ccc0c4"
|
|
331
331
|
},
|
|
332
332
|
"watchers/LogWatcher.js": {
|
|
333
|
-
"size":
|
|
334
|
-
"sha256": "
|
|
333
|
+
"size": 3126,
|
|
334
|
+
"sha256": "e0944661b48b682520d60ee9e98b3fa9f8ba4694743f134f98b04b8b2dd479e6"
|
|
335
335
|
},
|
|
336
336
|
"watchers/MailWatcher.d.ts": {
|
|
337
337
|
"size": 244,
|
|
@@ -370,8 +370,8 @@
|
|
|
370
370
|
"sha256": "5d5046c65e5b683369c7709f1acd09b60aec3e7f44748fd1baeb35498836465b"
|
|
371
371
|
},
|
|
372
372
|
"watchers/QueryWatcher.js": {
|
|
373
|
-
"size":
|
|
374
|
-
"sha256": "
|
|
373
|
+
"size": 3002,
|
|
374
|
+
"sha256": "c7131284e75ab2f0193597cdf3ef0aa7eab1a3872fe9193579a140a41fadb57e"
|
|
375
375
|
},
|
|
376
376
|
"watchers/RedisWatcher.d.ts": {
|
|
377
377
|
"size": 294,
|
package/dist/register.js
CHANGED
|
@@ -39,6 +39,11 @@ const importCore = async () => {
|
|
|
39
39
|
return {};
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
|
+
const TRACE_REQUIRED_TABLES = [
|
|
43
|
+
'zin_trace_entries',
|
|
44
|
+
'zin_trace_entries_tags',
|
|
45
|
+
'zin_trace_monitoring',
|
|
46
|
+
];
|
|
42
47
|
const resolveRegisterMiddleware = () => {
|
|
43
48
|
const globalMiddlewareRegistrarState = globalThis;
|
|
44
49
|
return (middleware) => {
|
|
@@ -69,7 +74,7 @@ const resolveObservedConnectionName = (env, configuredObservedConnection, storag
|
|
|
69
74
|
configuredObservedConnection.trim() !== '') {
|
|
70
75
|
return resolveTraceConnectionName(env, configuredObservedConnection);
|
|
71
76
|
}
|
|
72
|
-
const defaultConnectionName = resolveTraceConnectionName(env
|
|
77
|
+
const defaultConnectionName = resolveTraceConnectionName(env);
|
|
73
78
|
if (storageConnectionName !== defaultConnectionName) {
|
|
74
79
|
return defaultConnectionName;
|
|
75
80
|
}
|
|
@@ -150,6 +155,43 @@ const buildTraceRedactionOverrides = (input) => {
|
|
|
150
155
|
? redaction
|
|
151
156
|
: undefined;
|
|
152
157
|
};
|
|
158
|
+
const createTraceConfigError = (coreApi, message, details) => {
|
|
159
|
+
if (coreApi.ErrorFactory?.createConfigError !== undefined) {
|
|
160
|
+
return coreApi.ErrorFactory.createConfigError(message, details);
|
|
161
|
+
}
|
|
162
|
+
const error = new globalThis.Error(message);
|
|
163
|
+
error.name = 'ConfigError';
|
|
164
|
+
error.code = 'CONFIG_ERROR';
|
|
165
|
+
error.statusCode = 500;
|
|
166
|
+
error.details = details;
|
|
167
|
+
return error;
|
|
168
|
+
};
|
|
169
|
+
function assertTraceConnectionResolved(coreApi, db, params) {
|
|
170
|
+
if (db !== undefined) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
throw createTraceConfigError(coreApi, `Trace connection "${params.connectionName}" could not be resolved.`, {
|
|
174
|
+
connectionName: params.connectionName,
|
|
175
|
+
envKey: params.envKey,
|
|
176
|
+
hint: params.envKey === 'TRACE_DB_CONNECTION'
|
|
177
|
+
? 'Configure TRACE_DB_CONNECTION to an existing database connection before enabling TRACE_ENABLED.'
|
|
178
|
+
: 'Configure TRACE_QUERY_CONNECTION, or ensure DB_CONNECTION resolves to an existing database connection.',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const assertTraceStorageReady = async (coreApi, db, connectionName) => {
|
|
182
|
+
try {
|
|
183
|
+
await Promise.all(TRACE_REQUIRED_TABLES.map(async (table) => {
|
|
184
|
+
await db.queryOne(`SELECT 1 AS ok FROM ${table} LIMIT 1`, []);
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
throw createTraceConfigError(coreApi, `Trace storage connection "${connectionName}" is not ready. Create the database if needed and run \`zin migrate:trace\` before enabling TRACE_ENABLED.`, {
|
|
189
|
+
connectionName,
|
|
190
|
+
error,
|
|
191
|
+
requiredTables: [...TRACE_REQUIRED_TABLES],
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
};
|
|
153
195
|
const core = (await importCore());
|
|
154
196
|
const Env = core.Env;
|
|
155
197
|
const startupOverrides = resolveTraceStartupOverrides(core);
|
|
@@ -205,62 +247,65 @@ if (!traceAlreadyInitialized && Env) {
|
|
|
205
247
|
const resolvedObservedConnectionName = resolveObservedConnectionName(Env, config.observeConnection, resolvedConnectionName);
|
|
206
248
|
const storageDb = core.useDatabase?.(undefined, resolvedConnectionName);
|
|
207
249
|
const observedDb = core.useDatabase?.(undefined, resolvedObservedConnectionName);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
import('./watchers/ScheduleWatcher.js'),
|
|
224
|
-
import('./watchers/MailWatcher.js'),
|
|
225
|
-
import('./watchers/AuthWatcher.js'),
|
|
226
|
-
import('./watchers/EventWatcher.js'),
|
|
227
|
-
import('./watchers/ModelWatcher.js'),
|
|
228
|
-
import('./watchers/NotificationWatcher.js'),
|
|
229
|
-
import('./watchers/RedisWatcher.js'),
|
|
230
|
-
import('./watchers/GateWatcher.js'),
|
|
231
|
-
import('./watchers/MiddlewareWatcher.js'),
|
|
232
|
-
import('./watchers/CommandWatcher.js'),
|
|
233
|
-
import('./watchers/BatchWatcher.js'),
|
|
234
|
-
import('./watchers/DumpWatcher.js'),
|
|
235
|
-
import('./watchers/ViewWatcher.js'),
|
|
236
|
-
import('./watchers/HttpClientWatcher.js'),
|
|
237
|
-
]);
|
|
238
|
-
const watcherArgs = { storage, config, db: observedDb };
|
|
239
|
-
HttpWatcher.register({ ...watcherArgs, registerMiddleware: resolveRegisterMiddleware() });
|
|
240
|
-
QueryWatcher.register(watcherArgs);
|
|
241
|
-
LogWatcher.register(watcherArgs);
|
|
242
|
-
ExceptionWatcher.register(watcherArgs);
|
|
243
|
-
JobWatcher.register(watcherArgs);
|
|
244
|
-
CacheWatcher.register(watcherArgs);
|
|
245
|
-
ScheduleWatcher.register(watcherArgs);
|
|
246
|
-
MailWatcher.register(watcherArgs);
|
|
247
|
-
AuthWatcher.register(watcherArgs);
|
|
248
|
-
EventWatcher.register(watcherArgs);
|
|
249
|
-
ModelWatcher.register(watcherArgs);
|
|
250
|
-
NotificationWatcher.register(watcherArgs);
|
|
251
|
-
RedisWatcher.register(watcherArgs);
|
|
252
|
-
GateWatcher.register(watcherArgs);
|
|
253
|
-
MiddlewareWatcher.register(watcherArgs);
|
|
254
|
-
CommandWatcher.register(watcherArgs);
|
|
255
|
-
BatchWatcher.register(watcherArgs);
|
|
256
|
-
DumpWatcher.register(watcherArgs);
|
|
257
|
-
ViewWatcher.register(watcherArgs);
|
|
258
|
-
HttpClientWatcher.register(watcherArgs);
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
// eslint-disable-next-line no-console
|
|
262
|
-
console.warn('[trace] Could not resolve database connection - skipping init.');
|
|
250
|
+
assertTraceConnectionResolved(core, storageDb, {
|
|
251
|
+
connectionName: resolvedConnectionName,
|
|
252
|
+
envKey: 'TRACE_DB_CONNECTION',
|
|
253
|
+
});
|
|
254
|
+
assertTraceConnectionResolved(core, observedDb, {
|
|
255
|
+
connectionName: resolvedObservedConnectionName,
|
|
256
|
+
envKey: 'TRACE_QUERY_CONNECTION',
|
|
257
|
+
});
|
|
258
|
+
await assertTraceStorageReady(core, storageDb, resolvedConnectionName);
|
|
259
|
+
const storage = TraceWriteDiagnostics.wrapStorage(TraceContentRedaction.wrapStorage(TraceEntryFiltering.wrapStorage(TraceStorage.resolveStorage(storageDb), config), config.redaction), {
|
|
260
|
+
connectionName: resolvedConnectionName,
|
|
261
|
+
logger: core.Logger,
|
|
262
|
+
});
|
|
263
|
+
if (core.RequestContext) {
|
|
264
|
+
TraceContext.setRequestContextImpl(core.RequestContext);
|
|
263
265
|
}
|
|
266
|
+
const [{ HttpWatcher }, { QueryWatcher }, { LogWatcher }, { ExceptionWatcher }, { JobWatcher }, { CacheWatcher }, { ScheduleWatcher }, { MailWatcher }, { AuthWatcher }, { EventWatcher }, { ModelWatcher }, { NotificationWatcher }, { RedisWatcher }, { GateWatcher }, { MiddlewareWatcher }, { CommandWatcher }, { BatchWatcher }, { DumpWatcher }, { ViewWatcher }, { HttpClientWatcher },] = await Promise.all([
|
|
267
|
+
import('./watchers/HttpWatcher.js'),
|
|
268
|
+
import('./watchers/QueryWatcher.js'),
|
|
269
|
+
import('./watchers/LogWatcher.js'),
|
|
270
|
+
import('./watchers/ExceptionWatcher.js'),
|
|
271
|
+
import('./watchers/JobWatcher.js'),
|
|
272
|
+
import('./watchers/CacheWatcher.js'),
|
|
273
|
+
import('./watchers/ScheduleWatcher.js'),
|
|
274
|
+
import('./watchers/MailWatcher.js'),
|
|
275
|
+
import('./watchers/AuthWatcher.js'),
|
|
276
|
+
import('./watchers/EventWatcher.js'),
|
|
277
|
+
import('./watchers/ModelWatcher.js'),
|
|
278
|
+
import('./watchers/NotificationWatcher.js'),
|
|
279
|
+
import('./watchers/RedisWatcher.js'),
|
|
280
|
+
import('./watchers/GateWatcher.js'),
|
|
281
|
+
import('./watchers/MiddlewareWatcher.js'),
|
|
282
|
+
import('./watchers/CommandWatcher.js'),
|
|
283
|
+
import('./watchers/BatchWatcher.js'),
|
|
284
|
+
import('./watchers/DumpWatcher.js'),
|
|
285
|
+
import('./watchers/ViewWatcher.js'),
|
|
286
|
+
import('./watchers/HttpClientWatcher.js'),
|
|
287
|
+
]);
|
|
288
|
+
const watcherArgs = { storage, config, db: observedDb };
|
|
289
|
+
HttpWatcher.register({ ...watcherArgs, registerMiddleware: resolveRegisterMiddleware() });
|
|
290
|
+
QueryWatcher.register(watcherArgs);
|
|
291
|
+
LogWatcher.register(watcherArgs);
|
|
292
|
+
ExceptionWatcher.register(watcherArgs);
|
|
293
|
+
JobWatcher.register(watcherArgs);
|
|
294
|
+
CacheWatcher.register(watcherArgs);
|
|
295
|
+
ScheduleWatcher.register(watcherArgs);
|
|
296
|
+
MailWatcher.register(watcherArgs);
|
|
297
|
+
AuthWatcher.register(watcherArgs);
|
|
298
|
+
EventWatcher.register(watcherArgs);
|
|
299
|
+
ModelWatcher.register(watcherArgs);
|
|
300
|
+
NotificationWatcher.register(watcherArgs);
|
|
301
|
+
RedisWatcher.register(watcherArgs);
|
|
302
|
+
GateWatcher.register(watcherArgs);
|
|
303
|
+
MiddlewareWatcher.register(watcherArgs);
|
|
304
|
+
CommandWatcher.register(watcherArgs);
|
|
305
|
+
BatchWatcher.register(watcherArgs);
|
|
306
|
+
DumpWatcher.register(watcherArgs);
|
|
307
|
+
ViewWatcher.register(watcherArgs);
|
|
308
|
+
HttpClientWatcher.register(watcherArgs);
|
|
264
309
|
}
|
|
265
310
|
}
|
|
266
311
|
else if (!traceAlreadyInitialized) {
|
|
@@ -17,8 +17,37 @@ const TRACE_INFRASTRUCTURE_LOG_MESSAGES = new Set([
|
|
|
17
17
|
'[MySQLProxyAdapter] Proxy request failed',
|
|
18
18
|
'[trace] Trace storage write degraded',
|
|
19
19
|
]);
|
|
20
|
-
const
|
|
21
|
-
|
|
20
|
+
const TRACE_STORAGE_TABLE_NAMES = [
|
|
21
|
+
'zin_trace_entries',
|
|
22
|
+
'zin_trace_entries_tags',
|
|
23
|
+
'zin_trace_monitoring',
|
|
24
|
+
];
|
|
25
|
+
const isTraceStorageQuery = (sql) => {
|
|
26
|
+
const normalized = sql.toLowerCase();
|
|
27
|
+
return TRACE_STORAGE_TABLE_NAMES.some((tableName) => normalized.includes(tableName));
|
|
28
|
+
};
|
|
29
|
+
const extractSqlFromLog = (message, context) => {
|
|
30
|
+
const contextSql = context?.['sql'];
|
|
31
|
+
if (typeof contextSql === 'string')
|
|
32
|
+
return contextSql;
|
|
33
|
+
const trimmed = message.trim();
|
|
34
|
+
const rawPrefix = 'Raw SQL Query executed:';
|
|
35
|
+
if (trimmed.startsWith(rawPrefix)) {
|
|
36
|
+
const sql = trimmed.slice(rawPrefix.length).trim();
|
|
37
|
+
return sql === '' ? undefined : sql;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
};
|
|
41
|
+
const isTraceStorageQueryLog = (message, context) => {
|
|
42
|
+
const normalizedMessage = message.trim().toLowerCase();
|
|
43
|
+
if (!normalizedMessage.includes('query executed'))
|
|
44
|
+
return false;
|
|
45
|
+
const sql = extractSqlFromLog(message, context);
|
|
46
|
+
return typeof sql === 'string' && isTraceStorageQuery(sql);
|
|
47
|
+
};
|
|
48
|
+
const shouldSkipTraceInfrastructureLog = (message, context) => {
|
|
49
|
+
return (TRACE_INFRASTRUCTURE_LOG_MESSAGES.has(message.trim()) ||
|
|
50
|
+
isTraceStorageQueryLog(message, context));
|
|
22
51
|
};
|
|
23
52
|
export const LogWatcher = Object.freeze({
|
|
24
53
|
register({ storage, config }) {
|
|
@@ -34,7 +63,7 @@ export const LogWatcher = Object.freeze({
|
|
|
34
63
|
return;
|
|
35
64
|
if (RequestFilter.shouldIgnoreCurrentRequest(config.ignoreRoutes))
|
|
36
65
|
return;
|
|
37
|
-
if (shouldSkipTraceInfrastructureLog(message))
|
|
66
|
+
if (shouldSkipTraceInfrastructureLog(message, context))
|
|
38
67
|
return;
|
|
39
68
|
const content = {
|
|
40
69
|
level,
|
|
@@ -22,7 +22,9 @@ const bindingsInterpolated = (sql, params) => {
|
|
|
22
22
|
};
|
|
23
23
|
const isTraceStorageQuery = (sql) => {
|
|
24
24
|
const normalized = sql.toLowerCase();
|
|
25
|
-
return normalized.includes('zin_trace_entries') ||
|
|
25
|
+
return (normalized.includes('zin_trace_entries') ||
|
|
26
|
+
normalized.includes('zin_trace_entries_tags') ||
|
|
27
|
+
normalized.includes('zin_trace_monitoring'));
|
|
26
28
|
};
|
|
27
29
|
const emit = (query, params, duration, connection = 'default') => {
|
|
28
30
|
if (_storage === null || _config === null)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/trace",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.92",
|
|
4
4
|
"description": "Trace assistant for ZinTrust: logs requests, queries, exceptions, jobs, and more.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"node": ">=20.0.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@zintrust/core": "^0.4.
|
|
43
|
+
"@zintrust/core": "^0.4.91"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
package/src/register.ts
CHANGED
|
@@ -64,6 +64,9 @@ type CoreApi = {
|
|
|
64
64
|
Logger?: {
|
|
65
65
|
warn(message: string, context?: Record<string, unknown>): void;
|
|
66
66
|
};
|
|
67
|
+
ErrorFactory?: {
|
|
68
|
+
createConfigError(message: string, details?: unknown): Error;
|
|
69
|
+
};
|
|
67
70
|
StartupConfigFile?: {
|
|
68
71
|
Trace?: string;
|
|
69
72
|
};
|
|
@@ -72,6 +75,14 @@ type CoreApi = {
|
|
|
72
75
|
};
|
|
73
76
|
};
|
|
74
77
|
|
|
78
|
+
type CoreDatabase = import('@zintrust/core').IDatabase;
|
|
79
|
+
|
|
80
|
+
const TRACE_REQUIRED_TABLES = [
|
|
81
|
+
'zin_trace_entries',
|
|
82
|
+
'zin_trace_entries_tags',
|
|
83
|
+
'zin_trace_monitoring',
|
|
84
|
+
] as const;
|
|
85
|
+
|
|
75
86
|
type GlobalMiddlewareRegistrarState = {
|
|
76
87
|
__zintrust_register_global_middleware__?: ITraceWatcherConfig['registerMiddleware'];
|
|
77
88
|
__zintrust_pending_global_middlewares__?: Array<
|
|
@@ -125,7 +136,7 @@ const resolveObservedConnectionName = (
|
|
|
125
136
|
return resolveTraceConnectionName(env, configuredObservedConnection);
|
|
126
137
|
}
|
|
127
138
|
|
|
128
|
-
const defaultConnectionName = resolveTraceConnectionName(env
|
|
139
|
+
const defaultConnectionName = resolveTraceConnectionName(env);
|
|
129
140
|
if (storageConnectionName !== defaultConnectionName) {
|
|
130
141
|
return defaultConnectionName;
|
|
131
142
|
}
|
|
@@ -217,6 +228,71 @@ const buildTraceRedactionOverrides = (input: {
|
|
|
217
228
|
: undefined;
|
|
218
229
|
};
|
|
219
230
|
|
|
231
|
+
const createTraceConfigError = (coreApi: CoreApi, message: string, details?: unknown): Error => {
|
|
232
|
+
if (coreApi.ErrorFactory?.createConfigError !== undefined) {
|
|
233
|
+
return coreApi.ErrorFactory.createConfigError(message, details);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const error = new globalThis.Error(message) as Error & {
|
|
237
|
+
code?: string;
|
|
238
|
+
details?: unknown;
|
|
239
|
+
name?: string;
|
|
240
|
+
statusCode?: number;
|
|
241
|
+
};
|
|
242
|
+
error.name = 'ConfigError';
|
|
243
|
+
error.code = 'CONFIG_ERROR';
|
|
244
|
+
error.statusCode = 500;
|
|
245
|
+
error.details = details;
|
|
246
|
+
return error;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
function assertTraceConnectionResolved(
|
|
250
|
+
coreApi: CoreApi,
|
|
251
|
+
db: CoreDatabase | undefined,
|
|
252
|
+
params: { connectionName: string; envKey: 'TRACE_DB_CONNECTION' | 'TRACE_QUERY_CONNECTION' }
|
|
253
|
+
): asserts db is CoreDatabase {
|
|
254
|
+
if (db !== undefined) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
throw createTraceConfigError(
|
|
259
|
+
coreApi,
|
|
260
|
+
`Trace connection "${params.connectionName}" could not be resolved.`,
|
|
261
|
+
{
|
|
262
|
+
connectionName: params.connectionName,
|
|
263
|
+
envKey: params.envKey,
|
|
264
|
+
hint:
|
|
265
|
+
params.envKey === 'TRACE_DB_CONNECTION'
|
|
266
|
+
? 'Configure TRACE_DB_CONNECTION to an existing database connection before enabling TRACE_ENABLED.'
|
|
267
|
+
: 'Configure TRACE_QUERY_CONNECTION, or ensure DB_CONNECTION resolves to an existing database connection.',
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const assertTraceStorageReady = async (
|
|
273
|
+
coreApi: CoreApi,
|
|
274
|
+
db: CoreDatabase,
|
|
275
|
+
connectionName: string
|
|
276
|
+
): Promise<void> => {
|
|
277
|
+
try {
|
|
278
|
+
await Promise.all(
|
|
279
|
+
TRACE_REQUIRED_TABLES.map(async (table) => {
|
|
280
|
+
await db.queryOne(`SELECT 1 AS ok FROM ${table} LIMIT 1`, []);
|
|
281
|
+
})
|
|
282
|
+
);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
throw createTraceConfigError(
|
|
285
|
+
coreApi,
|
|
286
|
+
`Trace storage connection "${connectionName}" is not ready. Create the database if needed and run \`zin migrate:trace\` before enabling TRACE_ENABLED.`,
|
|
287
|
+
{
|
|
288
|
+
connectionName,
|
|
289
|
+
error,
|
|
290
|
+
requiredTables: [...TRACE_REQUIRED_TABLES],
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
220
296
|
const core = (await importCore()) as CoreApi;
|
|
221
297
|
const Env = core.Env;
|
|
222
298
|
const startupOverrides = resolveTraceStartupOverrides(core);
|
|
@@ -292,98 +368,103 @@ if (!traceAlreadyInitialized && Env) {
|
|
|
292
368
|
const storageDb = core.useDatabase?.(undefined, resolvedConnectionName);
|
|
293
369
|
const observedDb = core.useDatabase?.(undefined, resolvedObservedConnectionName);
|
|
294
370
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
);
|
|
371
|
+
assertTraceConnectionResolved(core, storageDb, {
|
|
372
|
+
connectionName: resolvedConnectionName,
|
|
373
|
+
envKey: 'TRACE_DB_CONNECTION',
|
|
374
|
+
});
|
|
375
|
+
assertTraceConnectionResolved(core, observedDb, {
|
|
376
|
+
connectionName: resolvedObservedConnectionName,
|
|
377
|
+
envKey: 'TRACE_QUERY_CONNECTION',
|
|
378
|
+
});
|
|
379
|
+
await assertTraceStorageReady(core, storageDb, resolvedConnectionName);
|
|
380
|
+
|
|
381
|
+
const storage = TraceWriteDiagnostics.wrapStorage(
|
|
382
|
+
TraceContentRedaction.wrapStorage(
|
|
383
|
+
TraceEntryFiltering.wrapStorage(TraceStorage.resolveStorage(storageDb), config),
|
|
384
|
+
config.redaction
|
|
385
|
+
),
|
|
386
|
+
{
|
|
387
|
+
connectionName: resolvedConnectionName,
|
|
388
|
+
logger: core.Logger,
|
|
314
389
|
}
|
|
390
|
+
);
|
|
315
391
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
{ ScheduleWatcher },
|
|
324
|
-
{ MailWatcher },
|
|
325
|
-
{ AuthWatcher },
|
|
326
|
-
{ EventWatcher },
|
|
327
|
-
{ ModelWatcher },
|
|
328
|
-
{ NotificationWatcher },
|
|
329
|
-
{ RedisWatcher },
|
|
330
|
-
{ GateWatcher },
|
|
331
|
-
{ MiddlewareWatcher },
|
|
332
|
-
{ CommandWatcher },
|
|
333
|
-
{ BatchWatcher },
|
|
334
|
-
{ DumpWatcher },
|
|
335
|
-
{ ViewWatcher },
|
|
336
|
-
{ HttpClientWatcher },
|
|
337
|
-
] = await Promise.all([
|
|
338
|
-
import('./watchers/HttpWatcher'),
|
|
339
|
-
import('./watchers/QueryWatcher'),
|
|
340
|
-
import('./watchers/LogWatcher'),
|
|
341
|
-
import('./watchers/ExceptionWatcher'),
|
|
342
|
-
import('./watchers/JobWatcher'),
|
|
343
|
-
import('./watchers/CacheWatcher'),
|
|
344
|
-
import('./watchers/ScheduleWatcher'),
|
|
345
|
-
import('./watchers/MailWatcher'),
|
|
346
|
-
import('./watchers/AuthWatcher'),
|
|
347
|
-
import('./watchers/EventWatcher'),
|
|
348
|
-
import('./watchers/ModelWatcher'),
|
|
349
|
-
import('./watchers/NotificationWatcher'),
|
|
350
|
-
import('./watchers/RedisWatcher'),
|
|
351
|
-
import('./watchers/GateWatcher'),
|
|
352
|
-
import('./watchers/MiddlewareWatcher'),
|
|
353
|
-
import('./watchers/CommandWatcher'),
|
|
354
|
-
import('./watchers/BatchWatcher'),
|
|
355
|
-
import('./watchers/DumpWatcher'),
|
|
356
|
-
import('./watchers/ViewWatcher'),
|
|
357
|
-
import('./watchers/HttpClientWatcher'),
|
|
358
|
-
]);
|
|
359
|
-
|
|
360
|
-
const watcherArgs = { storage, config, db: observedDb };
|
|
361
|
-
|
|
362
|
-
HttpWatcher.register({ ...watcherArgs, registerMiddleware: resolveRegisterMiddleware() });
|
|
363
|
-
|
|
364
|
-
QueryWatcher.register(watcherArgs);
|
|
365
|
-
LogWatcher.register(watcherArgs);
|
|
366
|
-
ExceptionWatcher.register(watcherArgs);
|
|
367
|
-
JobWatcher.register(watcherArgs);
|
|
368
|
-
CacheWatcher.register(watcherArgs);
|
|
369
|
-
ScheduleWatcher.register(watcherArgs);
|
|
370
|
-
MailWatcher.register(watcherArgs);
|
|
371
|
-
AuthWatcher.register(watcherArgs);
|
|
372
|
-
EventWatcher.register(watcherArgs);
|
|
373
|
-
ModelWatcher.register(watcherArgs);
|
|
374
|
-
NotificationWatcher.register(watcherArgs);
|
|
375
|
-
RedisWatcher.register(watcherArgs);
|
|
376
|
-
GateWatcher.register(watcherArgs);
|
|
377
|
-
MiddlewareWatcher.register(watcherArgs);
|
|
378
|
-
CommandWatcher.register(watcherArgs);
|
|
379
|
-
BatchWatcher.register(watcherArgs);
|
|
380
|
-
DumpWatcher.register(watcherArgs);
|
|
381
|
-
ViewWatcher.register(watcherArgs);
|
|
382
|
-
HttpClientWatcher.register(watcherArgs);
|
|
383
|
-
} else {
|
|
384
|
-
// eslint-disable-next-line no-console
|
|
385
|
-
console.warn('[trace] Could not resolve database connection - skipping init.');
|
|
392
|
+
if (core.RequestContext) {
|
|
393
|
+
TraceContext.setRequestContextImpl(
|
|
394
|
+
core.RequestContext as {
|
|
395
|
+
current?: () => unknown;
|
|
396
|
+
peek?: () => unknown;
|
|
397
|
+
}
|
|
398
|
+
);
|
|
386
399
|
}
|
|
400
|
+
|
|
401
|
+
const [
|
|
402
|
+
{ HttpWatcher },
|
|
403
|
+
{ QueryWatcher },
|
|
404
|
+
{ LogWatcher },
|
|
405
|
+
{ ExceptionWatcher },
|
|
406
|
+
{ JobWatcher },
|
|
407
|
+
{ CacheWatcher },
|
|
408
|
+
{ ScheduleWatcher },
|
|
409
|
+
{ MailWatcher },
|
|
410
|
+
{ AuthWatcher },
|
|
411
|
+
{ EventWatcher },
|
|
412
|
+
{ ModelWatcher },
|
|
413
|
+
{ NotificationWatcher },
|
|
414
|
+
{ RedisWatcher },
|
|
415
|
+
{ GateWatcher },
|
|
416
|
+
{ MiddlewareWatcher },
|
|
417
|
+
{ CommandWatcher },
|
|
418
|
+
{ BatchWatcher },
|
|
419
|
+
{ DumpWatcher },
|
|
420
|
+
{ ViewWatcher },
|
|
421
|
+
{ HttpClientWatcher },
|
|
422
|
+
] = await Promise.all([
|
|
423
|
+
import('./watchers/HttpWatcher'),
|
|
424
|
+
import('./watchers/QueryWatcher'),
|
|
425
|
+
import('./watchers/LogWatcher'),
|
|
426
|
+
import('./watchers/ExceptionWatcher'),
|
|
427
|
+
import('./watchers/JobWatcher'),
|
|
428
|
+
import('./watchers/CacheWatcher'),
|
|
429
|
+
import('./watchers/ScheduleWatcher'),
|
|
430
|
+
import('./watchers/MailWatcher'),
|
|
431
|
+
import('./watchers/AuthWatcher'),
|
|
432
|
+
import('./watchers/EventWatcher'),
|
|
433
|
+
import('./watchers/ModelWatcher'),
|
|
434
|
+
import('./watchers/NotificationWatcher'),
|
|
435
|
+
import('./watchers/RedisWatcher'),
|
|
436
|
+
import('./watchers/GateWatcher'),
|
|
437
|
+
import('./watchers/MiddlewareWatcher'),
|
|
438
|
+
import('./watchers/CommandWatcher'),
|
|
439
|
+
import('./watchers/BatchWatcher'),
|
|
440
|
+
import('./watchers/DumpWatcher'),
|
|
441
|
+
import('./watchers/ViewWatcher'),
|
|
442
|
+
import('./watchers/HttpClientWatcher'),
|
|
443
|
+
]);
|
|
444
|
+
|
|
445
|
+
const watcherArgs = { storage, config, db: observedDb };
|
|
446
|
+
|
|
447
|
+
HttpWatcher.register({ ...watcherArgs, registerMiddleware: resolveRegisterMiddleware() });
|
|
448
|
+
|
|
449
|
+
QueryWatcher.register(watcherArgs);
|
|
450
|
+
LogWatcher.register(watcherArgs);
|
|
451
|
+
ExceptionWatcher.register(watcherArgs);
|
|
452
|
+
JobWatcher.register(watcherArgs);
|
|
453
|
+
CacheWatcher.register(watcherArgs);
|
|
454
|
+
ScheduleWatcher.register(watcherArgs);
|
|
455
|
+
MailWatcher.register(watcherArgs);
|
|
456
|
+
AuthWatcher.register(watcherArgs);
|
|
457
|
+
EventWatcher.register(watcherArgs);
|
|
458
|
+
ModelWatcher.register(watcherArgs);
|
|
459
|
+
NotificationWatcher.register(watcherArgs);
|
|
460
|
+
RedisWatcher.register(watcherArgs);
|
|
461
|
+
GateWatcher.register(watcherArgs);
|
|
462
|
+
MiddlewareWatcher.register(watcherArgs);
|
|
463
|
+
CommandWatcher.register(watcherArgs);
|
|
464
|
+
BatchWatcher.register(watcherArgs);
|
|
465
|
+
DumpWatcher.register(watcherArgs);
|
|
466
|
+
ViewWatcher.register(watcherArgs);
|
|
467
|
+
HttpClientWatcher.register(watcherArgs);
|
|
387
468
|
}
|
|
388
469
|
} else if (!traceAlreadyInitialized) {
|
|
389
470
|
// Running outside a ZinTrust project - skip init silently.
|
|
@@ -21,8 +21,50 @@ const TRACE_INFRASTRUCTURE_LOG_MESSAGES = new Set<string>([
|
|
|
21
21
|
'[trace] Trace storage write degraded',
|
|
22
22
|
]);
|
|
23
23
|
|
|
24
|
-
const
|
|
25
|
-
|
|
24
|
+
const TRACE_STORAGE_TABLE_NAMES = [
|
|
25
|
+
'zin_trace_entries',
|
|
26
|
+
'zin_trace_entries_tags',
|
|
27
|
+
'zin_trace_monitoring',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const isTraceStorageQuery = (sql: string): boolean => {
|
|
31
|
+
const normalized = sql.toLowerCase();
|
|
32
|
+
return TRACE_STORAGE_TABLE_NAMES.some((tableName) => normalized.includes(tableName));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const extractSqlFromLog = (
|
|
36
|
+
message: string,
|
|
37
|
+
context?: Record<string, unknown>
|
|
38
|
+
): string | undefined => {
|
|
39
|
+
const contextSql = context?.['sql'];
|
|
40
|
+
if (typeof contextSql === 'string') return contextSql;
|
|
41
|
+
|
|
42
|
+
const trimmed = message.trim();
|
|
43
|
+
const rawPrefix = 'Raw SQL Query executed:';
|
|
44
|
+
if (trimmed.startsWith(rawPrefix)) {
|
|
45
|
+
const sql = trimmed.slice(rawPrefix.length).trim();
|
|
46
|
+
return sql === '' ? undefined : sql;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return undefined;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const isTraceStorageQueryLog = (message: string, context?: Record<string, unknown>): boolean => {
|
|
53
|
+
const normalizedMessage = message.trim().toLowerCase();
|
|
54
|
+
if (!normalizedMessage.includes('query executed')) return false;
|
|
55
|
+
|
|
56
|
+
const sql = extractSqlFromLog(message, context);
|
|
57
|
+
return typeof sql === 'string' && isTraceStorageQuery(sql);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const shouldSkipTraceInfrastructureLog = (
|
|
61
|
+
message: string,
|
|
62
|
+
context?: Record<string, unknown>
|
|
63
|
+
): boolean => {
|
|
64
|
+
return (
|
|
65
|
+
TRACE_INFRASTRUCTURE_LOG_MESSAGES.has(message.trim()) ||
|
|
66
|
+
isTraceStorageQueryLog(message, context)
|
|
67
|
+
);
|
|
26
68
|
};
|
|
27
69
|
|
|
28
70
|
export const LogWatcher: ITraceWatcher = Object.freeze({
|
|
@@ -41,7 +83,7 @@ export const LogWatcher: ITraceWatcher = Object.freeze({
|
|
|
41
83
|
(level: string, message: string, context?: Record<string, unknown>) => {
|
|
42
84
|
if ((LEVEL_PRIORITY[level] ?? 0) < minPriority) return;
|
|
43
85
|
if (RequestFilter.shouldIgnoreCurrentRequest(config.ignoreRoutes)) return;
|
|
44
|
-
if (shouldSkipTraceInfrastructureLog(message)) return;
|
|
86
|
+
if (shouldSkipTraceInfrastructureLog(message, context)) return;
|
|
45
87
|
|
|
46
88
|
const content: LogContent = {
|
|
47
89
|
level,
|
|
@@ -24,7 +24,11 @@ const bindingsInterpolated = (sql: string, params: unknown[]): string => {
|
|
|
24
24
|
|
|
25
25
|
const isTraceStorageQuery = (sql: string): boolean => {
|
|
26
26
|
const normalized = sql.toLowerCase();
|
|
27
|
-
return
|
|
27
|
+
return (
|
|
28
|
+
normalized.includes('zin_trace_entries') ||
|
|
29
|
+
normalized.includes('zin_trace_entries_tags') ||
|
|
30
|
+
normalized.includes('zin_trace_monitoring')
|
|
31
|
+
);
|
|
28
32
|
};
|
|
29
33
|
|
|
30
34
|
const emit = (query: string, params: unknown[], duration: number, connection = 'default'): void => {
|