@zintrust/trace 1.6.6 → 1.6.7
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/package.json +2 -3
- package/src/TraceConnection.ts +0 -182
- package/src/cli-register.ts +0 -63
- package/src/config.ts +0 -383
- package/src/context.ts +0 -101
- package/src/dashboard/handlers.ts +0 -353
- package/src/dashboard/routes.ts +0 -114
- package/src/dashboard/ui.ts +0 -1262
- package/src/dashboard/zintrust-debuger.svg +0 -30
- package/src/index.ts +0 -102
- package/src/ingest/TraceIngestGateway.ts +0 -414
- package/src/plugin.ts +0 -9
- package/src/register.ts +0 -702
- package/src/storage/ProxyTraceStorage.ts +0 -190
- package/src/storage/TraceContentBudget.ts +0 -493
- package/src/storage/TraceContentRedaction.ts +0 -44
- package/src/storage/TraceEntryFiltering.ts +0 -50
- package/src/storage/TraceServiceTag.ts +0 -56
- package/src/storage/TraceStorage.ts +0 -543
- package/src/storage/TraceWriteDiagnostics.ts +0 -289
- package/src/storage/index.ts +0 -4
- package/src/types.ts +0 -430
- package/src/ui.ts +0 -9
- package/src/utils/authTag.ts +0 -20
- package/src/utils/entryFilter.ts +0 -131
- package/src/utils/familyHash.ts +0 -8
- package/src/utils/redact.ts +0 -112
- package/src/utils/requestFilter.ts +0 -79
- package/src/utils/stackFrame.ts +0 -44
- package/src/watchers/AuthWatcher.ts +0 -53
- package/src/watchers/BatchWatcher.ts +0 -55
- package/src/watchers/CacheWatcher.ts +0 -72
- package/src/watchers/CommandWatcher.ts +0 -58
- package/src/watchers/DumpWatcher.ts +0 -45
- package/src/watchers/EventWatcher.ts +0 -46
- package/src/watchers/ExceptionWatcher.ts +0 -130
- package/src/watchers/GateWatcher.ts +0 -53
- package/src/watchers/HttpClientWatcher.ts +0 -219
- package/src/watchers/HttpWatcher.ts +0 -249
- package/src/watchers/JobWatcher.ts +0 -124
- package/src/watchers/LogWatcher.ts +0 -120
- package/src/watchers/MailWatcher.ts +0 -65
- package/src/watchers/MiddlewareWatcher.ts +0 -54
- package/src/watchers/ModelWatcher.ts +0 -60
- package/src/watchers/NotificationWatcher.ts +0 -60
- package/src/watchers/QueryWatcher.ts +0 -105
- package/src/watchers/RedisWatcher.ts +0 -42
- package/src/watchers/ScheduleWatcher.ts +0 -57
- package/src/watchers/ViewWatcher.ts +0 -40
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JobWatcher — records job dispatch, completion, and failure.
|
|
3
|
-
* Subsystems must call JobWatcher.onDispatch / onProcess / onFail from
|
|
4
|
-
* within their queue implementation for full tracking.
|
|
5
|
-
*/
|
|
6
|
-
import { TraceContext } from '../context';
|
|
7
|
-
import type { ITraceWatcher, ITraceWatcherConfig, JobContent } from '../types';
|
|
8
|
-
import { EntryType } from '../types';
|
|
9
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
10
|
-
import { parseStackFrameLine } from '../utils/stackFrame';
|
|
11
|
-
|
|
12
|
-
// Module-level storage ref so emit helpers can be called from outside.
|
|
13
|
-
let _storage: ITraceWatcherConfig['storage'] | null = null;
|
|
14
|
-
let _ignoreRoutes: string[] = [];
|
|
15
|
-
let _ignorePaths: string[] = [];
|
|
16
|
-
const MAX_TRACKED_JOBS = 1000;
|
|
17
|
-
|
|
18
|
-
type PendingJob = { uuid: string; content: JobContent };
|
|
19
|
-
|
|
20
|
-
const pendingJobs = new Map<string, PendingJob[]>();
|
|
21
|
-
|
|
22
|
-
const trackPendingJob = (name: string, job: PendingJob): void => {
|
|
23
|
-
const jobs = pendingJobs.get(name) ?? [];
|
|
24
|
-
jobs.push(job);
|
|
25
|
-
if (jobs.length > MAX_TRACKED_JOBS) {
|
|
26
|
-
jobs.shift();
|
|
27
|
-
}
|
|
28
|
-
pendingJobs.set(name, jobs);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const takePendingJob = (name: string): PendingJob | null => {
|
|
32
|
-
const jobs = pendingJobs.get(name);
|
|
33
|
-
if (!jobs || jobs.length === 0) return null;
|
|
34
|
-
|
|
35
|
-
const job = jobs.shift() ?? null;
|
|
36
|
-
if (jobs.length === 0) {
|
|
37
|
-
pendingJobs.delete(name);
|
|
38
|
-
} else {
|
|
39
|
-
pendingJobs.set(name, jobs);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return job;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const emitDispatch = (name: string, queue: string, connection: string, data?: unknown): void => {
|
|
46
|
-
if (!_storage) return;
|
|
47
|
-
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes, _ignorePaths)) return;
|
|
48
|
-
const uuid = crypto.randomUUID();
|
|
49
|
-
const content: JobContent = {
|
|
50
|
-
status: 'pending',
|
|
51
|
-
connection,
|
|
52
|
-
queue,
|
|
53
|
-
name,
|
|
54
|
-
data,
|
|
55
|
-
hostname: TraceContext.getHostname(),
|
|
56
|
-
};
|
|
57
|
-
_storage
|
|
58
|
-
.writeEntry({
|
|
59
|
-
uuid,
|
|
60
|
-
batchId: TraceContext.getBatchId(),
|
|
61
|
-
type: EntryType.JOB,
|
|
62
|
-
content,
|
|
63
|
-
tags: [name],
|
|
64
|
-
isLatest: true,
|
|
65
|
-
createdAt: TraceContext.now(),
|
|
66
|
-
})
|
|
67
|
-
.catch(() => undefined);
|
|
68
|
-
|
|
69
|
-
trackPendingJob(name, { uuid, content });
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const emitProcessed = (name: string): void => {
|
|
73
|
-
if (!_storage) return;
|
|
74
|
-
const pendingJob = takePendingJob(name);
|
|
75
|
-
if (pendingJob === null) return;
|
|
76
|
-
|
|
77
|
-
const patch: JobContent = { ...pendingJob.content, status: 'processed' };
|
|
78
|
-
void _storage.updateEntry(pendingJob.uuid, { content: patch }).catch(() => undefined);
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const emitFailed = (name: string, error: Error): void => {
|
|
82
|
-
if (!_storage) return;
|
|
83
|
-
const pendingJob = takePendingJob(name);
|
|
84
|
-
if (pendingJob === null) return;
|
|
85
|
-
|
|
86
|
-
const patch: JobContent = {
|
|
87
|
-
...pendingJob.content,
|
|
88
|
-
status: 'failed',
|
|
89
|
-
exception: {
|
|
90
|
-
message: error.message,
|
|
91
|
-
trace: (error.stack ?? '')
|
|
92
|
-
.split('\n')
|
|
93
|
-
.slice(1)
|
|
94
|
-
.map(parseStackFrameLine)
|
|
95
|
-
.filter((trace): trace is { file: string; line: number } => trace !== null)
|
|
96
|
-
.slice(0, 10),
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
void _storage.updateEntry(pendingJob.uuid, { content: patch }).catch(() => undefined);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
export const JobWatcher: ITraceWatcher & {
|
|
104
|
-
onDispatch: typeof emitDispatch;
|
|
105
|
-
onProcessed: typeof emitProcessed;
|
|
106
|
-
onFailed: typeof emitFailed;
|
|
107
|
-
} = Object.freeze({
|
|
108
|
-
onDispatch: emitDispatch,
|
|
109
|
-
onProcessed: emitProcessed,
|
|
110
|
-
onFailed: emitFailed,
|
|
111
|
-
|
|
112
|
-
register({ storage, config }: ITraceWatcherConfig): () => void {
|
|
113
|
-
if (config.watchers.job === false) return () => undefined;
|
|
114
|
-
_storage = storage;
|
|
115
|
-
_ignoreRoutes = config.ignoreRoutes;
|
|
116
|
-
_ignorePaths = config.ignorePaths;
|
|
117
|
-
return () => {
|
|
118
|
-
_storage = null;
|
|
119
|
-
_ignoreRoutes = [];
|
|
120
|
-
_ignorePaths = [];
|
|
121
|
-
pendingJobs.clear();
|
|
122
|
-
};
|
|
123
|
-
},
|
|
124
|
-
});
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LogWatcher — captures Logger output via Logger.addSink().
|
|
3
|
-
*/
|
|
4
|
-
import { Logger } from '@zintrust/core';
|
|
5
|
-
import { TraceContext } from '../context';
|
|
6
|
-
import type { ITraceWatcher, ITraceWatcherConfig, LogContent } from '../types';
|
|
7
|
-
import { EntryType } from '../types';
|
|
8
|
-
import { AuthTag } from '../utils/authTag';
|
|
9
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
10
|
-
|
|
11
|
-
const LEVEL_PRIORITY: Record<string, number> = {
|
|
12
|
-
debug: 0,
|
|
13
|
-
info: 1,
|
|
14
|
-
warn: 2,
|
|
15
|
-
error: 3,
|
|
16
|
-
fatal: 4,
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const TRACE_INFRASTRUCTURE_LOG_MESSAGES = new Set<string>([
|
|
20
|
-
'[MySQLProxyAdapter] Proxy request failed',
|
|
21
|
-
'[trace] Trace storage write degraded',
|
|
22
|
-
]);
|
|
23
|
-
|
|
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
|
-
const loggerWithTraceSkip = Logger as typeof Logger & {
|
|
65
|
-
shouldSkipTraceLogContext?: (ctx?: Record<string, unknown>) => boolean;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
if (loggerWithTraceSkip.shouldSkipTraceLogContext?.(context) === true) {
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
TRACE_INFRASTRUCTURE_LOG_MESSAGES.has(message.trim()) ||
|
|
74
|
-
isTraceStorageQueryLog(message, context)
|
|
75
|
-
);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
export const LogWatcher: ITraceWatcher = Object.freeze({
|
|
79
|
-
register({ storage, config }: ITraceWatcherConfig): () => void {
|
|
80
|
-
if (config.watchers.log === false) return () => undefined;
|
|
81
|
-
|
|
82
|
-
const minPriority = LEVEL_PRIORITY[config.logMinLevel] ?? 1;
|
|
83
|
-
|
|
84
|
-
const loggerWithSink = Logger;
|
|
85
|
-
|
|
86
|
-
if (typeof loggerWithSink.addSink !== 'function') {
|
|
87
|
-
return () => undefined;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const unsubscribe = loggerWithSink.addSink(
|
|
91
|
-
(level: string, message: string, context?: Record<string, unknown>) => {
|
|
92
|
-
if ((LEVEL_PRIORITY[level] ?? 0) < minPriority) return;
|
|
93
|
-
if (RequestFilter.shouldIgnoreCurrentRequest(config.ignoreRoutes, config.ignorePaths))
|
|
94
|
-
return;
|
|
95
|
-
if (shouldSkipTraceInfrastructureLog(message, context)) return;
|
|
96
|
-
|
|
97
|
-
const content: LogContent = {
|
|
98
|
-
level,
|
|
99
|
-
message,
|
|
100
|
-
context: context ?? undefined,
|
|
101
|
-
hostname: TraceContext.getHostname(),
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
storage
|
|
105
|
-
.writeEntry({
|
|
106
|
-
uuid: crypto.randomUUID(),
|
|
107
|
-
batchId: TraceContext.getBatchId(),
|
|
108
|
-
type: EntryType.LOG,
|
|
109
|
-
content,
|
|
110
|
-
tags: AuthTag.append([]),
|
|
111
|
-
isLatest: true,
|
|
112
|
-
createdAt: TraceContext.now(),
|
|
113
|
-
})
|
|
114
|
-
.catch(() => undefined);
|
|
115
|
-
}
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
return unsubscribe;
|
|
119
|
-
},
|
|
120
|
-
});
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MailWatcher — records mail dispatch intent and rendered content.
|
|
3
|
-
*/
|
|
4
|
-
import { TraceContext } from '../context';
|
|
5
|
-
import type { ITraceWatcher, ITraceWatcherConfig, MailContent } from '../types';
|
|
6
|
-
import { EntryType } from '../types';
|
|
7
|
-
import { redactUnknown } from '../utils/redact';
|
|
8
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
9
|
-
|
|
10
|
-
let _storage: ITraceWatcherConfig['storage'] | null = null;
|
|
11
|
-
let _redactionFields: string[] = [];
|
|
12
|
-
let _ignoreRoutes: string[] = [];
|
|
13
|
-
let _ignorePaths: string[] = [];
|
|
14
|
-
|
|
15
|
-
const emit = (
|
|
16
|
-
to: string,
|
|
17
|
-
subject: string,
|
|
18
|
-
template?: string,
|
|
19
|
-
text?: string,
|
|
20
|
-
html?: string
|
|
21
|
-
): void => {
|
|
22
|
-
if (!_storage) return;
|
|
23
|
-
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes, _ignorePaths)) return;
|
|
24
|
-
const content: MailContent = {
|
|
25
|
-
to,
|
|
26
|
-
subject,
|
|
27
|
-
template,
|
|
28
|
-
...(typeof text === 'string' && text !== ''
|
|
29
|
-
? { text: redactUnknown(text, _redactionFields) as string }
|
|
30
|
-
: {}),
|
|
31
|
-
...(typeof html === 'string' && html !== ''
|
|
32
|
-
? { html: redactUnknown(html, _redactionFields) as string }
|
|
33
|
-
: {}),
|
|
34
|
-
hostname: TraceContext.getHostname(),
|
|
35
|
-
};
|
|
36
|
-
_storage
|
|
37
|
-
.writeEntry({
|
|
38
|
-
uuid: crypto.randomUUID(),
|
|
39
|
-
batchId: TraceContext.getBatchId(),
|
|
40
|
-
type: EntryType.MAIL,
|
|
41
|
-
content,
|
|
42
|
-
tags: [],
|
|
43
|
-
isLatest: true,
|
|
44
|
-
createdAt: TraceContext.now(),
|
|
45
|
-
})
|
|
46
|
-
.catch(() => undefined);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export const MailWatcher: ITraceWatcher & { emit: typeof emit } = Object.freeze({
|
|
50
|
-
emit,
|
|
51
|
-
|
|
52
|
-
register({ storage, config }: ITraceWatcherConfig): () => void {
|
|
53
|
-
if (config.watchers.mail === false) return () => undefined;
|
|
54
|
-
_storage = storage;
|
|
55
|
-
_redactionFields = [...config.redaction.keys, ...config.redaction.body];
|
|
56
|
-
_ignoreRoutes = config.ignoreRoutes;
|
|
57
|
-
_ignorePaths = config.ignorePaths;
|
|
58
|
-
return () => {
|
|
59
|
-
_storage = null;
|
|
60
|
-
_redactionFields = [];
|
|
61
|
-
_ignoreRoutes = [];
|
|
62
|
-
_ignorePaths = [];
|
|
63
|
-
};
|
|
64
|
-
},
|
|
65
|
-
});
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import type { ITraceWatcher, ITraceWatcherConfig, MiddlewareContent } from '../types';
|
|
3
|
-
import { EntryType } from '../types';
|
|
4
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
5
|
-
|
|
6
|
-
let _storage: ITraceWatcherConfig['storage'] | null = null;
|
|
7
|
-
let _ignoreRoutes: string[] = [];
|
|
8
|
-
let _ignorePaths: string[] = [];
|
|
9
|
-
|
|
10
|
-
type GlobalMiddlewareTraceState = {
|
|
11
|
-
__zintrust_trace_middleware_emit__?: typeof emit;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const emit = (name: string, event: MiddlewareContent['event'], duration?: number): void => {
|
|
15
|
-
if (!_storage) return;
|
|
16
|
-
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes, _ignorePaths)) return;
|
|
17
|
-
const content: MiddlewareContent = {
|
|
18
|
-
name,
|
|
19
|
-
event,
|
|
20
|
-
duration,
|
|
21
|
-
hostname: TraceContext.getHostname(),
|
|
22
|
-
};
|
|
23
|
-
_storage
|
|
24
|
-
.writeEntry({
|
|
25
|
-
uuid: crypto.randomUUID(),
|
|
26
|
-
batchId: TraceContext.getBatchId(),
|
|
27
|
-
type: EntryType.MIDDLEWARE,
|
|
28
|
-
content,
|
|
29
|
-
tags: [name, event],
|
|
30
|
-
isLatest: true,
|
|
31
|
-
createdAt: TraceContext.now(),
|
|
32
|
-
})
|
|
33
|
-
.catch(() => undefined);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export const MiddlewareWatcher: ITraceWatcher & { emit: typeof emit } = Object.freeze({
|
|
37
|
-
emit,
|
|
38
|
-
register({ storage, config }: ITraceWatcherConfig): () => void {
|
|
39
|
-
if (config.watchers.middleware === false) return () => undefined;
|
|
40
|
-
_storage = storage;
|
|
41
|
-
_ignoreRoutes = config.ignoreRoutes;
|
|
42
|
-
_ignorePaths = config.ignorePaths;
|
|
43
|
-
(globalThis as unknown as GlobalMiddlewareTraceState).__zintrust_trace_middleware_emit__ = emit;
|
|
44
|
-
return () => {
|
|
45
|
-
const globalState = globalThis as unknown as GlobalMiddlewareTraceState;
|
|
46
|
-
if (globalState.__zintrust_trace_middleware_emit__ === emit) {
|
|
47
|
-
delete globalState.__zintrust_trace_middleware_emit__;
|
|
48
|
-
}
|
|
49
|
-
_storage = null;
|
|
50
|
-
_ignoreRoutes = [];
|
|
51
|
-
_ignorePaths = [];
|
|
52
|
-
};
|
|
53
|
-
},
|
|
54
|
-
});
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import type { ITraceWatcher, ITraceWatcherConfig, ModelContent } from '../types';
|
|
3
|
-
import { EntryType } from '../types';
|
|
4
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
5
|
-
|
|
6
|
-
let _storage: ITraceWatcherConfig['storage'] | null = null;
|
|
7
|
-
let _ignoreRoutes: string[] = [];
|
|
8
|
-
let _ignorePaths: string[] = [];
|
|
9
|
-
|
|
10
|
-
type GlobalModelTraceState = {
|
|
11
|
-
__zintrust_trace_model_emit__?: typeof emit;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const emit = (
|
|
15
|
-
action: ModelContent['action'],
|
|
16
|
-
model: string,
|
|
17
|
-
id?: string | number,
|
|
18
|
-
changes?: Record<string, unknown>
|
|
19
|
-
): void => {
|
|
20
|
-
if (!_storage) return;
|
|
21
|
-
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes, _ignorePaths)) return;
|
|
22
|
-
const content: ModelContent = {
|
|
23
|
-
action,
|
|
24
|
-
model,
|
|
25
|
-
id,
|
|
26
|
-
changes,
|
|
27
|
-
hostname: TraceContext.getHostname(),
|
|
28
|
-
};
|
|
29
|
-
_storage
|
|
30
|
-
.writeEntry({
|
|
31
|
-
uuid: crypto.randomUUID(),
|
|
32
|
-
batchId: TraceContext.getBatchId(),
|
|
33
|
-
type: EntryType.MODEL,
|
|
34
|
-
content,
|
|
35
|
-
tags: [model],
|
|
36
|
-
isLatest: true,
|
|
37
|
-
createdAt: TraceContext.now(),
|
|
38
|
-
})
|
|
39
|
-
.catch(() => undefined);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export const ModelWatcher: ITraceWatcher & { emit: typeof emit } = Object.freeze({
|
|
43
|
-
emit,
|
|
44
|
-
register({ storage, config }: ITraceWatcherConfig): () => void {
|
|
45
|
-
if (config.watchers.model === false) return () => undefined;
|
|
46
|
-
_storage = storage;
|
|
47
|
-
_ignoreRoutes = config.ignoreRoutes;
|
|
48
|
-
_ignorePaths = config.ignorePaths;
|
|
49
|
-
(globalThis as unknown as GlobalModelTraceState).__zintrust_trace_model_emit__ = emit;
|
|
50
|
-
return () => {
|
|
51
|
-
const globalState = globalThis as unknown as GlobalModelTraceState;
|
|
52
|
-
if (globalState.__zintrust_trace_model_emit__ === emit) {
|
|
53
|
-
delete globalState.__zintrust_trace_model_emit__;
|
|
54
|
-
}
|
|
55
|
-
_storage = null;
|
|
56
|
-
_ignoreRoutes = [];
|
|
57
|
-
_ignorePaths = [];
|
|
58
|
-
};
|
|
59
|
-
},
|
|
60
|
-
});
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import type { ITraceWatcher, ITraceWatcherConfig, NotificationContent } from '../types';
|
|
3
|
-
import { EntryType } from '../types';
|
|
4
|
-
import { AuthTag } from '../utils/authTag';
|
|
5
|
-
import { redactUnknown } from '../utils/redact';
|
|
6
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
7
|
-
|
|
8
|
-
let _storage: ITraceWatcherConfig['storage'] | null = null;
|
|
9
|
-
let _redactionFields: string[] = [];
|
|
10
|
-
let _ignoreRoutes: string[] = [];
|
|
11
|
-
let _ignorePaths: string[] = [];
|
|
12
|
-
|
|
13
|
-
const emit = (
|
|
14
|
-
notification: string,
|
|
15
|
-
channels: string[],
|
|
16
|
-
notifiable?: string,
|
|
17
|
-
message?: string,
|
|
18
|
-
payload?: unknown
|
|
19
|
-
): void => {
|
|
20
|
-
if (!_storage) return;
|
|
21
|
-
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes, _ignorePaths)) return;
|
|
22
|
-
const content: NotificationContent = {
|
|
23
|
-
notification,
|
|
24
|
-
channels,
|
|
25
|
-
notifiable,
|
|
26
|
-
...(typeof message === 'string' && message !== ''
|
|
27
|
-
? { message: redactUnknown(message, _redactionFields) as string }
|
|
28
|
-
: {}),
|
|
29
|
-
...(payload === undefined ? {} : { payload: redactUnknown(payload, _redactionFields) }),
|
|
30
|
-
hostname: TraceContext.getHostname(),
|
|
31
|
-
};
|
|
32
|
-
_storage
|
|
33
|
-
.writeEntry({
|
|
34
|
-
uuid: crypto.randomUUID(),
|
|
35
|
-
batchId: TraceContext.getBatchId(),
|
|
36
|
-
type: EntryType.NOTIFICATION,
|
|
37
|
-
content,
|
|
38
|
-
tags: AuthTag.append([notification, ...channels]),
|
|
39
|
-
isLatest: true,
|
|
40
|
-
createdAt: TraceContext.now(),
|
|
41
|
-
})
|
|
42
|
-
.catch(() => undefined);
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export const NotificationWatcher: ITraceWatcher & { emit: typeof emit } = Object.freeze({
|
|
46
|
-
emit,
|
|
47
|
-
register({ storage, config }: ITraceWatcherConfig): () => void {
|
|
48
|
-
if (config.watchers.notification === false) return () => undefined;
|
|
49
|
-
_storage = storage;
|
|
50
|
-
_redactionFields = [...config.redaction.keys, ...config.redaction.body];
|
|
51
|
-
_ignoreRoutes = config.ignoreRoutes;
|
|
52
|
-
_ignorePaths = config.ignorePaths;
|
|
53
|
-
return () => {
|
|
54
|
-
_storage = null;
|
|
55
|
-
_redactionFields = [];
|
|
56
|
-
_ignoreRoutes = [];
|
|
57
|
-
_ignorePaths = [];
|
|
58
|
-
};
|
|
59
|
-
},
|
|
60
|
-
});
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* QueryWatcher — hooks into Database.onAfterQuery to record SQL entries.
|
|
3
|
-
*/
|
|
4
|
-
import { TraceContext } from '../context';
|
|
5
|
-
import { TraceStorage } from '../storage';
|
|
6
|
-
import type { ITraceWatcher, ITraceWatcherConfig, QueryContent } from '../types';
|
|
7
|
-
import { EntryType } from '../types';
|
|
8
|
-
import { AuthTag } from '../utils/authTag';
|
|
9
|
-
|
|
10
|
-
let _storage: ITraceWatcherConfig['storage'] | null = null;
|
|
11
|
-
let _config: ITraceWatcherConfig['config'] | null = null;
|
|
12
|
-
|
|
13
|
-
const bindingsInterpolated = (sql: string, params: unknown[]): string => {
|
|
14
|
-
// Inline params for display only — safe, not for re-execution.
|
|
15
|
-
let i = 0;
|
|
16
|
-
return sql.replaceAll('?', () => {
|
|
17
|
-
const val = params[i++];
|
|
18
|
-
if (val === null || val === undefined) return 'NULL';
|
|
19
|
-
if (typeof val === 'string') return `'${val.replaceAll("'", "''")}'`;
|
|
20
|
-
return String(val);
|
|
21
|
-
});
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const isTraceStorageQuery = (sql: string): boolean => {
|
|
25
|
-
const normalized = sql.toLowerCase();
|
|
26
|
-
return (
|
|
27
|
-
normalized.includes('zin_trace_entries') ||
|
|
28
|
-
normalized.includes('zin_trace_entries_tags') ||
|
|
29
|
-
normalized.includes('zin_trace_monitoring')
|
|
30
|
-
);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const emit = (query: string, params: unknown[], duration: number, connection = 'default'): void => {
|
|
34
|
-
if (_storage === null || _config === null) return;
|
|
35
|
-
if (isTraceStorageQuery(query)) return;
|
|
36
|
-
|
|
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
|
-
|
|
44
|
-
const content: QueryContent = {
|
|
45
|
-
connection,
|
|
46
|
-
sql,
|
|
47
|
-
statement: query,
|
|
48
|
-
...(includeBindings ? { bindings: [...params] } : {}),
|
|
49
|
-
bindingsIncluded: includeBindings,
|
|
50
|
-
time: roundedDuration,
|
|
51
|
-
duration: roundedDuration,
|
|
52
|
-
slow,
|
|
53
|
-
hash,
|
|
54
|
-
hostname: TraceContext.getHostname(),
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const tags = AuthTag.append([]);
|
|
58
|
-
if (slow) tags.push('slow');
|
|
59
|
-
|
|
60
|
-
_storage
|
|
61
|
-
.writeEntry({
|
|
62
|
-
uuid: crypto.randomUUID(),
|
|
63
|
-
batchId,
|
|
64
|
-
familyHash: hash,
|
|
65
|
-
type: EntryType.QUERY,
|
|
66
|
-
content,
|
|
67
|
-
tags,
|
|
68
|
-
isLatest: true,
|
|
69
|
-
createdAt: TraceContext.now(),
|
|
70
|
-
})
|
|
71
|
-
.catch(() => undefined);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
export const QueryWatcher: ITraceWatcher & { emit: typeof emit } = Object.freeze({
|
|
75
|
-
emit,
|
|
76
|
-
|
|
77
|
-
register({ storage, config, db: injectedDb }: ITraceWatcherConfig): () => void {
|
|
78
|
-
if (config.watchers.query === false) return () => undefined;
|
|
79
|
-
if (!injectedDb) return () => undefined; // no db available
|
|
80
|
-
|
|
81
|
-
_storage = storage;
|
|
82
|
-
_config = config;
|
|
83
|
-
const db = injectedDb;
|
|
84
|
-
|
|
85
|
-
const handler = (query: string, params: unknown[], duration: number): void => {
|
|
86
|
-
emit(query, params, duration);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
(
|
|
90
|
-
db as {
|
|
91
|
-
onAfterQuery?: (h: (sql: string, params: unknown[], duration: number) => void) => void;
|
|
92
|
-
}
|
|
93
|
-
).onAfterQuery?.(handler);
|
|
94
|
-
|
|
95
|
-
return () => {
|
|
96
|
-
_storage = null;
|
|
97
|
-
_config = null;
|
|
98
|
-
(
|
|
99
|
-
db as {
|
|
100
|
-
offAfterQuery?: (h: (sql: string, params: unknown[], duration: number) => void) => void;
|
|
101
|
-
}
|
|
102
|
-
).offAfterQuery?.(handler);
|
|
103
|
-
};
|
|
104
|
-
},
|
|
105
|
-
});
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import type { ITraceWatcher, ITraceWatcherConfig, RedisContent } from '../types';
|
|
3
|
-
import { EntryType } from '../types';
|
|
4
|
-
import { AuthTag } from '../utils/authTag';
|
|
5
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
6
|
-
|
|
7
|
-
let _storage: ITraceWatcherConfig['storage'] | null = null;
|
|
8
|
-
let _ignoreRoutes: string[] = [];
|
|
9
|
-
let _ignorePaths: string[] = [];
|
|
10
|
-
|
|
11
|
-
/** Emit a redis command trace. Key/value payload is intentionally omitted for security. */
|
|
12
|
-
const emit = (command: string, duration: number): void => {
|
|
13
|
-
if (!_storage) return;
|
|
14
|
-
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes, _ignorePaths)) return;
|
|
15
|
-
const content: RedisContent = { command, duration, hostname: TraceContext.getHostname() };
|
|
16
|
-
_storage
|
|
17
|
-
.writeEntry({
|
|
18
|
-
uuid: crypto.randomUUID(),
|
|
19
|
-
batchId: TraceContext.getBatchId(),
|
|
20
|
-
type: EntryType.REDIS,
|
|
21
|
-
content,
|
|
22
|
-
tags: AuthTag.append([command.toUpperCase()]),
|
|
23
|
-
isLatest: true,
|
|
24
|
-
createdAt: TraceContext.now(),
|
|
25
|
-
})
|
|
26
|
-
.catch(() => undefined);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const RedisWatcher: ITraceWatcher & { emit: typeof emit } = Object.freeze({
|
|
30
|
-
emit,
|
|
31
|
-
register({ storage, config }: ITraceWatcherConfig): () => void {
|
|
32
|
-
if (config.watchers.redis === false) return () => undefined;
|
|
33
|
-
_storage = storage;
|
|
34
|
-
_ignoreRoutes = config.ignoreRoutes;
|
|
35
|
-
_ignorePaths = config.ignorePaths;
|
|
36
|
-
return () => {
|
|
37
|
-
_storage = null;
|
|
38
|
-
_ignoreRoutes = [];
|
|
39
|
-
_ignorePaths = [];
|
|
40
|
-
};
|
|
41
|
-
},
|
|
42
|
-
});
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ScheduleWatcher — records scheduled task runs and outcomes.
|
|
3
|
-
*/
|
|
4
|
-
import { TraceContext } from '../context';
|
|
5
|
-
import type { ITraceWatcher, ITraceWatcherConfig, ScheduleContent } from '../types';
|
|
6
|
-
import { EntryType } from '../types';
|
|
7
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
8
|
-
|
|
9
|
-
let _storage: ITraceWatcherConfig['storage'] | null = null;
|
|
10
|
-
let _ignoreRoutes: string[] = [];
|
|
11
|
-
let _ignorePaths: string[] = [];
|
|
12
|
-
|
|
13
|
-
const emit = (
|
|
14
|
-
name: string,
|
|
15
|
-
expression: string,
|
|
16
|
-
status: ScheduleContent['status'],
|
|
17
|
-
duration: number,
|
|
18
|
-
output?: string
|
|
19
|
-
): void => {
|
|
20
|
-
if (!_storage) return;
|
|
21
|
-
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes, _ignorePaths)) return;
|
|
22
|
-
const content: ScheduleContent = {
|
|
23
|
-
name,
|
|
24
|
-
expression,
|
|
25
|
-
status,
|
|
26
|
-
duration,
|
|
27
|
-
output,
|
|
28
|
-
hostname: TraceContext.getHostname(),
|
|
29
|
-
};
|
|
30
|
-
_storage
|
|
31
|
-
.writeEntry({
|
|
32
|
-
uuid: crypto.randomUUID(),
|
|
33
|
-
batchId: TraceContext.getBatchId(),
|
|
34
|
-
type: EntryType.SCHEDULE,
|
|
35
|
-
content,
|
|
36
|
-
tags: status === 'failed' ? ['failed'] : [],
|
|
37
|
-
isLatest: true,
|
|
38
|
-
createdAt: TraceContext.now(),
|
|
39
|
-
})
|
|
40
|
-
.catch(() => undefined);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export const ScheduleWatcher: ITraceWatcher & { emit: typeof emit } = Object.freeze({
|
|
44
|
-
emit,
|
|
45
|
-
|
|
46
|
-
register({ storage, config }: ITraceWatcherConfig): () => void {
|
|
47
|
-
if (config.watchers.schedule === false) return () => undefined;
|
|
48
|
-
_storage = storage;
|
|
49
|
-
_ignoreRoutes = config.ignoreRoutes;
|
|
50
|
-
_ignorePaths = config.ignorePaths;
|
|
51
|
-
return () => {
|
|
52
|
-
_storage = null;
|
|
53
|
-
_ignoreRoutes = [];
|
|
54
|
-
_ignorePaths = [];
|
|
55
|
-
};
|
|
56
|
-
},
|
|
57
|
-
});
|