@zintrust/trace 1.6.4 → 1.6.6
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 +14 -14
- package/dist/storage/TraceContentBudget.js +2 -1
- package/dist/watchers/HttpWatcher.js +23 -8
- package/package.json +3 -2
- package/src/TraceConnection.ts +182 -0
- package/src/cli-register.ts +63 -0
- package/src/config.ts +383 -0
- package/src/context.ts +101 -0
- package/src/dashboard/handlers.ts +353 -0
- package/src/dashboard/routes.ts +114 -0
- package/src/dashboard/ui.ts +1262 -0
- package/src/dashboard/zintrust-debuger.svg +30 -0
- package/src/index.ts +102 -0
- package/src/ingest/TraceIngestGateway.ts +414 -0
- package/src/plugin.ts +9 -0
- package/src/register.ts +702 -0
- package/src/storage/ProxyTraceStorage.ts +190 -0
- package/src/storage/TraceContentBudget.ts +493 -0
- package/src/storage/TraceContentRedaction.ts +44 -0
- package/src/storage/TraceEntryFiltering.ts +50 -0
- package/src/storage/TraceServiceTag.ts +56 -0
- package/src/storage/TraceStorage.ts +543 -0
- package/src/storage/TraceWriteDiagnostics.ts +289 -0
- package/src/storage/index.ts +4 -0
- package/src/types.ts +430 -0
- package/src/ui.ts +9 -0
- package/src/utils/authTag.ts +20 -0
- package/src/utils/entryFilter.ts +131 -0
- package/src/utils/familyHash.ts +8 -0
- package/src/utils/redact.ts +112 -0
- package/src/utils/requestFilter.ts +79 -0
- package/src/utils/stackFrame.ts +44 -0
- package/src/watchers/AuthWatcher.ts +53 -0
- package/src/watchers/BatchWatcher.ts +55 -0
- package/src/watchers/CacheWatcher.ts +72 -0
- package/src/watchers/CommandWatcher.ts +58 -0
- package/src/watchers/DumpWatcher.ts +45 -0
- package/src/watchers/EventWatcher.ts +46 -0
- package/src/watchers/ExceptionWatcher.ts +130 -0
- package/src/watchers/GateWatcher.ts +53 -0
- package/src/watchers/HttpClientWatcher.ts +219 -0
- package/src/watchers/HttpWatcher.ts +249 -0
- package/src/watchers/JobWatcher.ts +124 -0
- package/src/watchers/LogWatcher.ts +120 -0
- package/src/watchers/MailWatcher.ts +65 -0
- package/src/watchers/MiddlewareWatcher.ts +54 -0
- package/src/watchers/ModelWatcher.ts +60 -0
- package/src/watchers/NotificationWatcher.ts +60 -0
- package/src/watchers/QueryWatcher.ts +105 -0
- package/src/watchers/RedisWatcher.ts +42 -0
- package/src/watchers/ScheduleWatcher.ts +57 -0
- package/src/watchers/ViewWatcher.ts +40 -0
package/src/register.ts
ADDED
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zintrust/trace register side-effect module.
|
|
3
|
+
*
|
|
4
|
+
* For plugin-file activation, prefer:
|
|
5
|
+
* import '@zintrust/trace/plugin';
|
|
6
|
+
*
|
|
7
|
+
* The framework boot layer will lazy-load this register module once the app
|
|
8
|
+
* runtime is ready. Importing this file directly is still supported for
|
|
9
|
+
* advanced manual bootstrap flows that intentionally activate the trace
|
|
10
|
+
* after databases and the kernel are available.
|
|
11
|
+
*
|
|
12
|
+
* Config is read from environment variables (TRACE_* keys) matching
|
|
13
|
+
* the defaults in TraceConfig. For custom overrides supply them via
|
|
14
|
+
* calling `initTrace(overrides)` instead.
|
|
15
|
+
*
|
|
16
|
+
* Routes are NOT auto-mounted here. Wire the dashboard into your router:
|
|
17
|
+
* import { registerTraceDashboard } from '@zintrust/trace/ui';
|
|
18
|
+
* registerTraceDashboard(router, {
|
|
19
|
+
* middleware: ['admin'],
|
|
20
|
+
* });
|
|
21
|
+
*/
|
|
22
|
+
import { TraceConfig } from './config';
|
|
23
|
+
import { TraceContext } from './context';
|
|
24
|
+
import { ProxyTraceStorage, TraceServiceTag, TraceStorage } from './storage';
|
|
25
|
+
import { TraceContentBudget } from './storage/TraceContentBudget';
|
|
26
|
+
import { TraceContentRedaction } from './storage/TraceContentRedaction';
|
|
27
|
+
import { TraceEntryFiltering } from './storage/TraceEntryFiltering';
|
|
28
|
+
import { TraceWriteDiagnostics } from './storage/TraceWriteDiagnostics';
|
|
29
|
+
import {
|
|
30
|
+
assertTraceConnectionResolved,
|
|
31
|
+
assertTraceStorageReady,
|
|
32
|
+
resolveObservedConnectionName,
|
|
33
|
+
resolveTraceConnectionName,
|
|
34
|
+
} from './TraceConnection';
|
|
35
|
+
import type { ITraceWatcherConfig, TraceConfigOverrides } from './types';
|
|
36
|
+
|
|
37
|
+
export type {}; // side-effect ESM module
|
|
38
|
+
|
|
39
|
+
type GlobalTraceRegisterState = {
|
|
40
|
+
__zintrust_system_trace_register_initialized__?: boolean;
|
|
41
|
+
__zintrust_system_trace_plugin_requested__?: boolean;
|
|
42
|
+
__zintrust_system_trace_connection_name__?: string;
|
|
43
|
+
__zintrust_system_trace_observe_connection_name__?: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const globalTraceRegisterState = globalThis as unknown as GlobalTraceRegisterState;
|
|
47
|
+
const traceAlreadyInitialized =
|
|
48
|
+
globalTraceRegisterState.__zintrust_system_trace_register_initialized__ === true;
|
|
49
|
+
|
|
50
|
+
if (!traceAlreadyInitialized) {
|
|
51
|
+
globalTraceRegisterState.__zintrust_system_trace_register_initialized__ = true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const importCore = async (): Promise<unknown> => {
|
|
55
|
+
try {
|
|
56
|
+
return await import('@zintrust/core');
|
|
57
|
+
} catch {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type CoreApi = {
|
|
63
|
+
Env?: {
|
|
64
|
+
getBool(key: string, fallback: boolean): boolean;
|
|
65
|
+
get(key: string, fallback: string): string;
|
|
66
|
+
getInt(key: string, fallback: number): number;
|
|
67
|
+
};
|
|
68
|
+
useDatabase?: (config?: unknown, connection?: string) => import('@zintrust/core').IDatabase;
|
|
69
|
+
RequestContext?: {
|
|
70
|
+
current(): unknown;
|
|
71
|
+
};
|
|
72
|
+
Logger?: {
|
|
73
|
+
warn(message: string, context?: Record<string, unknown>): void;
|
|
74
|
+
};
|
|
75
|
+
ErrorFactory?: {
|
|
76
|
+
createConfigError(message: string, details?: unknown): Error;
|
|
77
|
+
};
|
|
78
|
+
StartupConfigFile?: {
|
|
79
|
+
Trace?: string;
|
|
80
|
+
};
|
|
81
|
+
StartupConfigFileRegistry?: {
|
|
82
|
+
preload?(files: readonly string[]): Promise<void>;
|
|
83
|
+
get<T>(file: string): T | undefined;
|
|
84
|
+
has?(file: string): boolean;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type GlobalMiddlewareRegistrarState = {
|
|
89
|
+
__zintrust_register_global_middleware__?: ITraceWatcherConfig['registerMiddleware'];
|
|
90
|
+
__zintrust_pending_global_middlewares__?: Array<
|
|
91
|
+
Parameters<NonNullable<ITraceWatcherConfig['registerMiddleware']>>[0]
|
|
92
|
+
>;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const resolveRegisterMiddleware = (): NonNullable<ITraceWatcherConfig['registerMiddleware']> => {
|
|
96
|
+
const globalMiddlewareRegistrarState = globalThis as unknown as GlobalMiddlewareRegistrarState;
|
|
97
|
+
|
|
98
|
+
return (middleware): void => {
|
|
99
|
+
const registerMiddleware =
|
|
100
|
+
globalMiddlewareRegistrarState.__zintrust_register_global_middleware__;
|
|
101
|
+
if (typeof registerMiddleware === 'function') {
|
|
102
|
+
registerMiddleware(middleware);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
globalMiddlewareRegistrarState.__zintrust_pending_global_middlewares__ ??= [];
|
|
107
|
+
globalMiddlewareRegistrarState.__zintrust_pending_global_middlewares__.push(middleware);
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const isObjectValue = (value: unknown): value is Record<string, unknown> => {
|
|
112
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const parseEnvList = (rawValue: string): string[] | undefined => {
|
|
116
|
+
const value = rawValue.trim();
|
|
117
|
+
if (value === '') return undefined;
|
|
118
|
+
|
|
119
|
+
if (value.startsWith('[')) {
|
|
120
|
+
try {
|
|
121
|
+
const parsed = JSON.parse(value) as unknown;
|
|
122
|
+
if (Array.isArray(parsed)) {
|
|
123
|
+
return parsed
|
|
124
|
+
.filter((entry): entry is string => typeof entry === 'string')
|
|
125
|
+
.map((entry) => entry.trim())
|
|
126
|
+
.filter((entry) => entry !== '');
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
// fall through to CSV parsing
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return value
|
|
134
|
+
.split(',')
|
|
135
|
+
.map((entry) => entry.trim())
|
|
136
|
+
.filter((entry) => entry !== '');
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const parseEnvBool = (rawValue: string): boolean | undefined => {
|
|
140
|
+
const value = rawValue.trim().toLowerCase();
|
|
141
|
+
if (value === '') return undefined;
|
|
142
|
+
if (['1', 'true', 'yes', 'on'].includes(value)) return true;
|
|
143
|
+
if (['0', 'false', 'no', 'off'].includes(value)) return false;
|
|
144
|
+
return undefined;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const resolveTraceStartupOverrides = async (
|
|
148
|
+
core: CoreApi
|
|
149
|
+
): Promise<TraceConfigOverrides | undefined> => {
|
|
150
|
+
const traceConfigFile = core.StartupConfigFile?.Trace;
|
|
151
|
+
if (typeof traceConfigFile !== 'string' || traceConfigFile.trim() === '') return undefined;
|
|
152
|
+
|
|
153
|
+
const registry = core.StartupConfigFileRegistry;
|
|
154
|
+
if (registry?.has?.(traceConfigFile) !== true && typeof registry?.preload === 'function') {
|
|
155
|
+
await registry.preload([traceConfigFile]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const overrides = registry?.get<unknown>(traceConfigFile);
|
|
159
|
+
return isObjectValue(overrides) ? (overrides as TraceConfigOverrides) : undefined;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const buildTraceRedactionOverrides = (input: {
|
|
163
|
+
startupOverrides?: TraceConfigOverrides;
|
|
164
|
+
redactionBody?: string[];
|
|
165
|
+
redactionHeaders?: string[];
|
|
166
|
+
redactionKeys?: string[];
|
|
167
|
+
redactionQuery?: string[];
|
|
168
|
+
}): TraceConfigOverrides['redaction'] | undefined => {
|
|
169
|
+
const redaction: Partial<NonNullable<TraceConfigOverrides['redaction']>> = {
|
|
170
|
+
...(isObjectValue(input.startupOverrides?.redaction) ? input.startupOverrides?.redaction : {}),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
if (input.redactionKeys === undefined) {
|
|
174
|
+
// no-op
|
|
175
|
+
} else {
|
|
176
|
+
redaction.keys = input.redactionKeys;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (input.redactionHeaders === undefined) {
|
|
180
|
+
// no-op
|
|
181
|
+
} else {
|
|
182
|
+
redaction.headers = input.redactionHeaders;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (input.redactionBody === undefined) {
|
|
186
|
+
// no-op
|
|
187
|
+
} else {
|
|
188
|
+
redaction.body = input.redactionBody;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (input.redactionQuery === undefined) {
|
|
192
|
+
// no-op
|
|
193
|
+
} else {
|
|
194
|
+
redaction.query = input.redactionQuery;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return Object.keys(redaction).length > 0
|
|
198
|
+
? (redaction as NonNullable<TraceConfigOverrides['redaction']>)
|
|
199
|
+
: undefined;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
type TraceEnvApi = NonNullable<CoreApi['Env']>;
|
|
203
|
+
|
|
204
|
+
type TraceEnvValues = {
|
|
205
|
+
connectionRaw: string;
|
|
206
|
+
observeConnectionRaw: string;
|
|
207
|
+
pruneAfterHoursRaw: string;
|
|
208
|
+
slowQueryThresholdRaw: string;
|
|
209
|
+
logMinLevelRaw: string;
|
|
210
|
+
traceProxyRaw: string;
|
|
211
|
+
traceProxyUrlRaw: string;
|
|
212
|
+
traceProxyPathRaw: string;
|
|
213
|
+
traceProxyKeyIdRaw: string;
|
|
214
|
+
traceProxySecretRaw: string;
|
|
215
|
+
traceProxyTimeoutRaw: string;
|
|
216
|
+
traceServiceTagRaw: string;
|
|
217
|
+
appNameRaw: string;
|
|
218
|
+
appKeyRaw: string;
|
|
219
|
+
captureCachePayloadsRaw: string;
|
|
220
|
+
captureQueryBindingsRaw: string;
|
|
221
|
+
contentDispatchDriverRaw: string;
|
|
222
|
+
contentDispatchQueueRaw: string;
|
|
223
|
+
contentDispatchEnqueueTimeoutRaw: string;
|
|
224
|
+
contentDispatchWorkerEnabledRaw: string;
|
|
225
|
+
contentDispatchWorkerIntervalRaw: string;
|
|
226
|
+
contentDispatchWorkerDurationRaw: string;
|
|
227
|
+
contentDispatchWorkerConcurrencyRaw: string;
|
|
228
|
+
redactionKeys?: string[];
|
|
229
|
+
redactionHeaders?: string[];
|
|
230
|
+
redactionBody?: string[];
|
|
231
|
+
redactionQuery?: string[];
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const readTraceEnvValues = (Env: TraceEnvApi): TraceEnvValues => {
|
|
235
|
+
return {
|
|
236
|
+
connectionRaw: Env.get('TRACE_DB_CONNECTION', '').trim(),
|
|
237
|
+
observeConnectionRaw: Env.get('TRACE_QUERY_CONNECTION', '').trim(),
|
|
238
|
+
pruneAfterHoursRaw: Env.get('TRACE_PRUNE_HOURS', '').trim(),
|
|
239
|
+
slowQueryThresholdRaw: Env.get('TRACE_SLOW_QUERY_MS', '').trim(),
|
|
240
|
+
logMinLevelRaw: Env.get('TRACE_LOG_LEVEL', '').trim(),
|
|
241
|
+
traceProxyRaw: Env.get('TRACE_PROXY', '').trim(),
|
|
242
|
+
traceProxyUrlRaw: Env.get('TRACE_PROXY_URL', '').trim(),
|
|
243
|
+
traceProxyPathRaw: Env.get('TRACE_PROXY_PATH', '').trim(),
|
|
244
|
+
traceProxyKeyIdRaw: Env.get('TRACE_PROXY_KEY_ID', '').trim(),
|
|
245
|
+
traceProxySecretRaw: Env.get('TRACE_PROXY_SECRET', '').trim(),
|
|
246
|
+
traceProxyTimeoutRaw: Env.get('TRACE_PROXY_TIMEOUT_MS', '').trim(),
|
|
247
|
+
traceServiceTagRaw: Env.get('TRACE_SERVICE_TAG', '').trim(),
|
|
248
|
+
appNameRaw: Env.get('APP_NAME', '').trim(),
|
|
249
|
+
appKeyRaw: Env.get('APP_KEY', '').trim(),
|
|
250
|
+
captureCachePayloadsRaw: Env.get('TRACE_CACHE_PAYLOADS', '').trim(),
|
|
251
|
+
captureQueryBindingsRaw: Env.get('TRACE_QUERY_BINDINGS', '').trim(),
|
|
252
|
+
contentDispatchDriverRaw: Env.get('TRACE_CONTENT_QUEUE_DRIVER', '').trim(),
|
|
253
|
+
contentDispatchQueueRaw: Env.get('TRACE_CONTENT_QUEUE_NAME', '').trim(),
|
|
254
|
+
contentDispatchEnqueueTimeoutRaw: Env.get('TRACE_CONTENT_QUEUE_ENQUEUE_TIMEOUT_MS', '').trim(),
|
|
255
|
+
contentDispatchWorkerEnabledRaw: Env.get('TRACE_CONTENT_QUEUE_WORKER_ENABLED', '').trim(),
|
|
256
|
+
contentDispatchWorkerIntervalRaw: Env.get('TRACE_CONTENT_QUEUE_WORKER_INTERVAL_MS', '').trim(),
|
|
257
|
+
contentDispatchWorkerDurationRaw: Env.get(
|
|
258
|
+
'TRACE_CONTENT_QUEUE_WORKER_MAX_DURATION_MS',
|
|
259
|
+
''
|
|
260
|
+
).trim(),
|
|
261
|
+
contentDispatchWorkerConcurrencyRaw: Env.get(
|
|
262
|
+
'TRACE_CONTENT_QUEUE_WORKER_CONCURRENCY',
|
|
263
|
+
''
|
|
264
|
+
).trim(),
|
|
265
|
+
redactionKeys: parseEnvList(Env.get('TRACE_REDACT_KEYS', '')),
|
|
266
|
+
redactionHeaders: parseEnvList(Env.get('TRACE_REDACT_HEADERS', '')),
|
|
267
|
+
redactionBody: parseEnvList(Env.get('TRACE_REDACT_BODY', '')),
|
|
268
|
+
redactionQuery: parseEnvList(Env.get('TRACE_REDACT_QUERY', '')),
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const resolveStringOverride = (
|
|
273
|
+
rawValue: string,
|
|
274
|
+
fallback: string | undefined
|
|
275
|
+
): string | undefined => {
|
|
276
|
+
return rawValue === '' ? fallback : rawValue;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const resolveNumberOverride = (
|
|
280
|
+
rawValue: string,
|
|
281
|
+
fallback: number | undefined
|
|
282
|
+
): number | undefined => {
|
|
283
|
+
return rawValue === '' ? fallback : Number.parseInt(rawValue, 10);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const resolveBooleanOverride = (
|
|
287
|
+
rawValue: string,
|
|
288
|
+
fallback: boolean | undefined
|
|
289
|
+
): boolean | undefined => {
|
|
290
|
+
return parseEnvBool(rawValue) ?? fallback;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const resolveTraceProxyKeyId = (
|
|
294
|
+
startupOverrides: TraceConfigOverrides | undefined,
|
|
295
|
+
values: TraceEnvValues
|
|
296
|
+
): string | undefined => {
|
|
297
|
+
return resolveStringOverride(
|
|
298
|
+
values.traceProxyKeyIdRaw,
|
|
299
|
+
startupOverrides?.proxy?.keyId ?? values.appNameRaw
|
|
300
|
+
);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const resolveTraceProxySecret = (
|
|
304
|
+
startupOverrides: TraceConfigOverrides | undefined,
|
|
305
|
+
values: TraceEnvValues
|
|
306
|
+
): string | undefined => {
|
|
307
|
+
return resolveStringOverride(
|
|
308
|
+
values.traceProxySecretRaw,
|
|
309
|
+
startupOverrides?.proxy?.secret ?? values.appKeyRaw
|
|
310
|
+
);
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const withStringProperty = (key: string, value: string | undefined): Record<string, string> => {
|
|
314
|
+
return typeof value === 'string' && value !== '' ? { [key]: value } : {};
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const withNumberProperty = (key: string, value: number | undefined): Record<string, number> => {
|
|
318
|
+
return typeof value === 'number' && Number.isFinite(value) ? { [key]: value } : {};
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const withBooleanProperty = (key: string, value: boolean | undefined): Record<string, boolean> => {
|
|
322
|
+
return typeof value === 'boolean' ? { [key]: value } : {};
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const resolveTraceServiceTag = (
|
|
326
|
+
startupOverrides: TraceConfigOverrides | undefined,
|
|
327
|
+
values: TraceEnvValues
|
|
328
|
+
): string | undefined => {
|
|
329
|
+
const fallback = (startupOverrides?.serviceTag ?? values.appNameRaw).trim() || undefined;
|
|
330
|
+
return resolveStringOverride(values.traceServiceTagRaw, fallback);
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const resolveContentDispatchWorkerEnabled = (
|
|
334
|
+
startupOverrides: TraceConfigOverrides | undefined,
|
|
335
|
+
values: TraceEnvValues
|
|
336
|
+
): boolean | undefined => {
|
|
337
|
+
return resolveBooleanOverride(
|
|
338
|
+
values.contentDispatchWorkerEnabledRaw,
|
|
339
|
+
startupOverrides?.contentDispatch?.worker?.enabled
|
|
340
|
+
);
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const resolveContentDispatchWorkerInterval = (
|
|
344
|
+
startupOverrides: TraceConfigOverrides | undefined,
|
|
345
|
+
values: TraceEnvValues
|
|
346
|
+
): number | undefined => {
|
|
347
|
+
return resolveNumberOverride(
|
|
348
|
+
values.contentDispatchWorkerIntervalRaw,
|
|
349
|
+
startupOverrides?.contentDispatch?.worker?.intervalMs
|
|
350
|
+
);
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const resolveContentDispatchWorkerDuration = (
|
|
354
|
+
startupOverrides: TraceConfigOverrides | undefined,
|
|
355
|
+
values: TraceEnvValues
|
|
356
|
+
): number | undefined => {
|
|
357
|
+
return resolveNumberOverride(
|
|
358
|
+
values.contentDispatchWorkerDurationRaw,
|
|
359
|
+
startupOverrides?.contentDispatch?.worker?.maxDurationMs
|
|
360
|
+
);
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const resolveContentDispatchWorkerConcurrency = (
|
|
364
|
+
startupOverrides: TraceConfigOverrides | undefined,
|
|
365
|
+
values: TraceEnvValues
|
|
366
|
+
): number | undefined => {
|
|
367
|
+
return resolveNumberOverride(
|
|
368
|
+
values.contentDispatchWorkerConcurrencyRaw,
|
|
369
|
+
startupOverrides?.contentDispatch?.worker?.concurrency
|
|
370
|
+
);
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const buildTraceContentDispatchWorkerConfig = (
|
|
374
|
+
startupOverrides: TraceConfigOverrides | undefined,
|
|
375
|
+
values: TraceEnvValues
|
|
376
|
+
): NonNullable<NonNullable<TraceConfigOverrides['contentDispatch']>['worker']> => {
|
|
377
|
+
const defaultWorker = TraceConfig.defaults().contentDispatch.worker;
|
|
378
|
+
const startupWorker = startupOverrides?.contentDispatch?.worker;
|
|
379
|
+
const contentDispatchWorkerEnabled = resolveContentDispatchWorkerEnabled(
|
|
380
|
+
startupOverrides,
|
|
381
|
+
values
|
|
382
|
+
);
|
|
383
|
+
const contentDispatchWorkerInterval = resolveContentDispatchWorkerInterval(
|
|
384
|
+
startupOverrides,
|
|
385
|
+
values
|
|
386
|
+
);
|
|
387
|
+
const contentDispatchWorkerDuration = resolveContentDispatchWorkerDuration(
|
|
388
|
+
startupOverrides,
|
|
389
|
+
values
|
|
390
|
+
);
|
|
391
|
+
const contentDispatchWorkerConcurrency = resolveContentDispatchWorkerConcurrency(
|
|
392
|
+
startupOverrides,
|
|
393
|
+
values
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
...defaultWorker,
|
|
398
|
+
...startupWorker,
|
|
399
|
+
enabled: contentDispatchWorkerEnabled ?? startupWorker?.enabled ?? defaultWorker.enabled,
|
|
400
|
+
intervalMs:
|
|
401
|
+
contentDispatchWorkerInterval ?? startupWorker?.intervalMs ?? defaultWorker.intervalMs,
|
|
402
|
+
maxDurationMs:
|
|
403
|
+
contentDispatchWorkerDuration ?? startupWorker?.maxDurationMs ?? defaultWorker.maxDurationMs,
|
|
404
|
+
concurrency:
|
|
405
|
+
contentDispatchWorkerConcurrency ?? startupWorker?.concurrency ?? defaultWorker.concurrency,
|
|
406
|
+
};
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const buildTraceProxyConfig = (
|
|
410
|
+
startupOverrides: TraceConfigOverrides | undefined,
|
|
411
|
+
values: TraceEnvValues
|
|
412
|
+
): TraceConfigOverrides['proxy'] => {
|
|
413
|
+
const traceProxyEnabled = resolveBooleanOverride(
|
|
414
|
+
values.traceProxyRaw,
|
|
415
|
+
startupOverrides?.proxy?.enabled
|
|
416
|
+
);
|
|
417
|
+
const traceProxyUrl = resolveStringOverride(
|
|
418
|
+
values.traceProxyUrlRaw,
|
|
419
|
+
startupOverrides?.proxy?.url
|
|
420
|
+
);
|
|
421
|
+
const traceProxyPath = resolveStringOverride(
|
|
422
|
+
values.traceProxyPathRaw,
|
|
423
|
+
startupOverrides?.proxy?.path
|
|
424
|
+
);
|
|
425
|
+
const traceProxyKeyId = resolveTraceProxyKeyId(startupOverrides, values);
|
|
426
|
+
const traceProxySecret = resolveTraceProxySecret(startupOverrides, values);
|
|
427
|
+
const traceProxyTimeout = resolveNumberOverride(
|
|
428
|
+
values.traceProxyTimeoutRaw,
|
|
429
|
+
startupOverrides?.proxy?.timeoutMs
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
...TraceConfig.defaults().proxy,
|
|
434
|
+
...startupOverrides?.proxy,
|
|
435
|
+
...withBooleanProperty('enabled', traceProxyEnabled),
|
|
436
|
+
...withStringProperty('url', traceProxyUrl),
|
|
437
|
+
...withStringProperty('path', traceProxyPath),
|
|
438
|
+
...withStringProperty('keyId', traceProxyKeyId),
|
|
439
|
+
...withStringProperty('secret', traceProxySecret),
|
|
440
|
+
...withNumberProperty('timeoutMs', traceProxyTimeout),
|
|
441
|
+
};
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const buildTraceContentDispatchConfig = (
|
|
445
|
+
startupOverrides: TraceConfigOverrides | undefined,
|
|
446
|
+
values: TraceEnvValues
|
|
447
|
+
): NonNullable<TraceConfigOverrides['contentDispatch']> => {
|
|
448
|
+
const defaultContentDispatch = TraceConfig.defaults().contentDispatch;
|
|
449
|
+
const startupContentDispatch = startupOverrides?.contentDispatch;
|
|
450
|
+
const contentDispatchDriver = resolveStringOverride(
|
|
451
|
+
values.contentDispatchDriverRaw,
|
|
452
|
+
startupContentDispatch?.driver
|
|
453
|
+
);
|
|
454
|
+
const contentDispatchQueueName = resolveStringOverride(
|
|
455
|
+
values.contentDispatchQueueRaw,
|
|
456
|
+
startupContentDispatch?.queueName
|
|
457
|
+
);
|
|
458
|
+
const contentDispatchEnqueueTimeout = resolveNumberOverride(
|
|
459
|
+
values.contentDispatchEnqueueTimeoutRaw,
|
|
460
|
+
startupContentDispatch?.enqueueTimeoutMs
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
...defaultContentDispatch,
|
|
465
|
+
...startupContentDispatch,
|
|
466
|
+
...withStringProperty('driver', contentDispatchDriver),
|
|
467
|
+
...withStringProperty('queueName', contentDispatchQueueName),
|
|
468
|
+
...withNumberProperty('enqueueTimeoutMs', contentDispatchEnqueueTimeout),
|
|
469
|
+
worker: buildTraceContentDispatchWorkerConfig(startupOverrides, values),
|
|
470
|
+
};
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
const buildTraceRuntimeConfig = (
|
|
474
|
+
Env: TraceEnvApi,
|
|
475
|
+
startupOverrides: TraceConfigOverrides | undefined
|
|
476
|
+
): ReturnType<typeof TraceConfig.merge> => {
|
|
477
|
+
const values = readTraceEnvValues(Env);
|
|
478
|
+
const enabled = startupOverrides?.enabled === true || Env.getBool('TRACE_ENABLED', false);
|
|
479
|
+
const connection = resolveStringOverride(values.connectionRaw, startupOverrides?.connection);
|
|
480
|
+
const observeConnection = resolveStringOverride(
|
|
481
|
+
values.observeConnectionRaw,
|
|
482
|
+
startupOverrides?.observeConnection
|
|
483
|
+
);
|
|
484
|
+
const pruneAfterHours = resolveNumberOverride(
|
|
485
|
+
values.pruneAfterHoursRaw,
|
|
486
|
+
startupOverrides?.pruneAfterHours
|
|
487
|
+
);
|
|
488
|
+
const slowQueryThreshold = resolveNumberOverride(
|
|
489
|
+
values.slowQueryThresholdRaw,
|
|
490
|
+
startupOverrides?.slowQueryThreshold
|
|
491
|
+
);
|
|
492
|
+
const logMinLevel = (
|
|
493
|
+
values.logMinLevelRaw === '' ? startupOverrides?.logMinLevel : values.logMinLevelRaw
|
|
494
|
+
) as 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
495
|
+
const captureCachePayloads = resolveBooleanOverride(
|
|
496
|
+
values.captureCachePayloadsRaw,
|
|
497
|
+
startupOverrides?.captureCachePayloads
|
|
498
|
+
);
|
|
499
|
+
const captureQueryBindings = resolveBooleanOverride(
|
|
500
|
+
values.captureQueryBindingsRaw,
|
|
501
|
+
startupOverrides?.captureQueryBindings
|
|
502
|
+
);
|
|
503
|
+
const traceServiceTag = resolveTraceServiceTag(startupOverrides, values);
|
|
504
|
+
const redaction = buildTraceRedactionOverrides({
|
|
505
|
+
startupOverrides,
|
|
506
|
+
redactionBody: values.redactionBody,
|
|
507
|
+
redactionHeaders: values.redactionHeaders,
|
|
508
|
+
redactionKeys: values.redactionKeys,
|
|
509
|
+
redactionQuery: values.redactionQuery,
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
return TraceConfig.merge({
|
|
513
|
+
...startupOverrides,
|
|
514
|
+
enabled,
|
|
515
|
+
connection,
|
|
516
|
+
observeConnection,
|
|
517
|
+
...withStringProperty('serviceTag', traceServiceTag),
|
|
518
|
+
proxy: buildTraceProxyConfig(startupOverrides, values),
|
|
519
|
+
...withNumberProperty('pruneAfterHours', pruneAfterHours),
|
|
520
|
+
...withNumberProperty('slowQueryThreshold', slowQueryThreshold),
|
|
521
|
+
...withBooleanProperty('captureCachePayloads', captureCachePayloads),
|
|
522
|
+
...withBooleanProperty('captureQueryBindings', captureQueryBindings),
|
|
523
|
+
contentDispatch: buildTraceContentDispatchConfig(startupOverrides, values),
|
|
524
|
+
logMinLevel,
|
|
525
|
+
...(redaction === undefined ? {} : { redaction }),
|
|
526
|
+
});
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const createTraceWatcherArgs = async (
|
|
530
|
+
core: CoreApi,
|
|
531
|
+
Env: TraceEnvApi,
|
|
532
|
+
config: ReturnType<typeof TraceConfig.merge>
|
|
533
|
+
): Promise<Pick<ITraceWatcherConfig, 'storage' | 'config' | 'db'>> => {
|
|
534
|
+
const resolvedConnectionName = resolveTraceConnectionName(Env, config.connection);
|
|
535
|
+
const resolvedObservedConnectionName = resolveObservedConnectionName(
|
|
536
|
+
Env,
|
|
537
|
+
config.observeConnection,
|
|
538
|
+
resolvedConnectionName
|
|
539
|
+
);
|
|
540
|
+
globalTraceRegisterState.__zintrust_system_trace_connection_name__ = resolvedConnectionName;
|
|
541
|
+
globalTraceRegisterState.__zintrust_system_trace_observe_connection_name__ =
|
|
542
|
+
resolvedObservedConnectionName;
|
|
543
|
+
|
|
544
|
+
let resolvedStorage;
|
|
545
|
+
|
|
546
|
+
if (config.proxy.enabled) {
|
|
547
|
+
resolvedStorage = ProxyTraceStorage.create({
|
|
548
|
+
baseUrl: config.proxy.url ?? '',
|
|
549
|
+
path: config.proxy.path,
|
|
550
|
+
keyId: config.proxy.keyId ?? '',
|
|
551
|
+
secret: config.proxy.secret ?? '',
|
|
552
|
+
timeoutMs: config.proxy.timeoutMs,
|
|
553
|
+
});
|
|
554
|
+
} else {
|
|
555
|
+
const storageDb = core.useDatabase?.(undefined, resolvedConnectionName);
|
|
556
|
+
|
|
557
|
+
assertTraceConnectionResolved(core, storageDb, {
|
|
558
|
+
connectionName: resolvedConnectionName,
|
|
559
|
+
envKey: 'TRACE_DB_CONNECTION',
|
|
560
|
+
});
|
|
561
|
+
await assertTraceStorageReady(core, storageDb, resolvedConnectionName);
|
|
562
|
+
|
|
563
|
+
resolvedStorage = TraceStorage.resolveStorage(storageDb);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const observedDb = core.useDatabase?.(undefined, resolvedObservedConnectionName);
|
|
567
|
+
|
|
568
|
+
assertTraceConnectionResolved(core, observedDb, {
|
|
569
|
+
connectionName: resolvedObservedConnectionName,
|
|
570
|
+
envKey: 'TRACE_QUERY_CONNECTION',
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
const storage = TraceWriteDiagnostics.wrapStorage(
|
|
574
|
+
TraceContentBudget.wrapStorage(
|
|
575
|
+
TraceContentRedaction.wrapStorage(
|
|
576
|
+
TraceEntryFiltering.wrapStorage(
|
|
577
|
+
TraceServiceTag.wrapStorage(resolvedStorage, config),
|
|
578
|
+
config
|
|
579
|
+
),
|
|
580
|
+
config.redaction
|
|
581
|
+
),
|
|
582
|
+
config
|
|
583
|
+
),
|
|
584
|
+
{
|
|
585
|
+
connectionName: resolvedConnectionName,
|
|
586
|
+
logger: core.Logger,
|
|
587
|
+
}
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
return { storage, config, db: observedDb };
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
const registerTraceWatchers = async (
|
|
594
|
+
watcherArgs: Pick<ITraceWatcherConfig, 'storage' | 'config' | 'db'>
|
|
595
|
+
): Promise<void> => {
|
|
596
|
+
const [
|
|
597
|
+
{ HttpWatcher },
|
|
598
|
+
{ QueryWatcher },
|
|
599
|
+
{ LogWatcher },
|
|
600
|
+
{ ExceptionWatcher },
|
|
601
|
+
{ JobWatcher },
|
|
602
|
+
{ CacheWatcher },
|
|
603
|
+
{ ScheduleWatcher },
|
|
604
|
+
{ MailWatcher },
|
|
605
|
+
{ AuthWatcher },
|
|
606
|
+
{ EventWatcher },
|
|
607
|
+
{ ModelWatcher },
|
|
608
|
+
{ NotificationWatcher },
|
|
609
|
+
{ RedisWatcher },
|
|
610
|
+
{ GateWatcher },
|
|
611
|
+
{ MiddlewareWatcher },
|
|
612
|
+
{ CommandWatcher },
|
|
613
|
+
{ BatchWatcher },
|
|
614
|
+
{ DumpWatcher },
|
|
615
|
+
{ ViewWatcher },
|
|
616
|
+
{ HttpClientWatcher },
|
|
617
|
+
] = await Promise.all([
|
|
618
|
+
import('./watchers/HttpWatcher'),
|
|
619
|
+
import('./watchers/QueryWatcher'),
|
|
620
|
+
import('./watchers/LogWatcher'),
|
|
621
|
+
import('./watchers/ExceptionWatcher'),
|
|
622
|
+
import('./watchers/JobWatcher'),
|
|
623
|
+
import('./watchers/CacheWatcher'),
|
|
624
|
+
import('./watchers/ScheduleWatcher'),
|
|
625
|
+
import('./watchers/MailWatcher'),
|
|
626
|
+
import('./watchers/AuthWatcher'),
|
|
627
|
+
import('./watchers/EventWatcher'),
|
|
628
|
+
import('./watchers/ModelWatcher'),
|
|
629
|
+
import('./watchers/NotificationWatcher'),
|
|
630
|
+
import('./watchers/RedisWatcher'),
|
|
631
|
+
import('./watchers/GateWatcher'),
|
|
632
|
+
import('./watchers/MiddlewareWatcher'),
|
|
633
|
+
import('./watchers/CommandWatcher'),
|
|
634
|
+
import('./watchers/BatchWatcher'),
|
|
635
|
+
import('./watchers/DumpWatcher'),
|
|
636
|
+
import('./watchers/ViewWatcher'),
|
|
637
|
+
import('./watchers/HttpClientWatcher'),
|
|
638
|
+
]);
|
|
639
|
+
|
|
640
|
+
HttpWatcher.register({ ...watcherArgs, registerMiddleware: resolveRegisterMiddleware() });
|
|
641
|
+
QueryWatcher.register(watcherArgs);
|
|
642
|
+
LogWatcher.register(watcherArgs);
|
|
643
|
+
ExceptionWatcher.register(watcherArgs);
|
|
644
|
+
JobWatcher.register(watcherArgs);
|
|
645
|
+
CacheWatcher.register(watcherArgs);
|
|
646
|
+
ScheduleWatcher.register(watcherArgs);
|
|
647
|
+
MailWatcher.register(watcherArgs);
|
|
648
|
+
AuthWatcher.register(watcherArgs);
|
|
649
|
+
EventWatcher.register(watcherArgs);
|
|
650
|
+
ModelWatcher.register(watcherArgs);
|
|
651
|
+
NotificationWatcher.register(watcherArgs);
|
|
652
|
+
RedisWatcher.register(watcherArgs);
|
|
653
|
+
GateWatcher.register(watcherArgs);
|
|
654
|
+
MiddlewareWatcher.register(watcherArgs);
|
|
655
|
+
CommandWatcher.register(watcherArgs);
|
|
656
|
+
BatchWatcher.register(watcherArgs);
|
|
657
|
+
DumpWatcher.register(watcherArgs);
|
|
658
|
+
ViewWatcher.register(watcherArgs);
|
|
659
|
+
HttpClientWatcher.register(watcherArgs);
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
const activateTrace = async (
|
|
663
|
+
core: CoreApi,
|
|
664
|
+
Env: TraceEnvApi,
|
|
665
|
+
startupOverrides: TraceConfigOverrides | undefined
|
|
666
|
+
): Promise<void> => {
|
|
667
|
+
const config = buildTraceRuntimeConfig(Env, startupOverrides);
|
|
668
|
+
if (!config.enabled) return;
|
|
669
|
+
|
|
670
|
+
const watcherArgs = await createTraceWatcherArgs(core, Env, config);
|
|
671
|
+
|
|
672
|
+
if (core.RequestContext) {
|
|
673
|
+
TraceContext.setRequestContextImpl(
|
|
674
|
+
core.RequestContext as {
|
|
675
|
+
current?: () => unknown;
|
|
676
|
+
peek?: () => unknown;
|
|
677
|
+
}
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
await registerTraceWatchers(watcherArgs);
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
const initializeTraceRegister = async (): Promise<void> => {
|
|
685
|
+
const core = (await importCore()) as CoreApi;
|
|
686
|
+
const Env = core.Env;
|
|
687
|
+
const startupOverrides = await resolveTraceStartupOverrides(core);
|
|
688
|
+
|
|
689
|
+
if (!traceAlreadyInitialized && Env) {
|
|
690
|
+
await activateTrace(core, Env, startupOverrides);
|
|
691
|
+
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (!traceAlreadyInitialized) {
|
|
696
|
+
// Running outside a ZinTrust project - skip init silently.
|
|
697
|
+
// eslint-disable-next-line no-console
|
|
698
|
+
console.warn('[trace] @zintrust/core not found - trace will not be activated.');
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
export const registerTraceReady = initializeTraceRegister();
|