@zintrust/trace 0.4.75 → 0.4.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -15
- package/dist/build-manifest.json +210 -162
- package/dist/config.d.ts +1 -0
- package/dist/config.js +123 -4
- package/dist/dashboard/routes.js +4 -4
- package/dist/dashboard/ui.js +80 -23
- package/dist/index.d.ts +2 -0
- package/dist/index.js +30 -25
- package/dist/migrations/{20260331000001_create_zin_debugger_entries_table.d.ts → 20260331000001_create_zin_trace_entries_table.d.ts} +2 -2
- package/dist/migrations/{20260331000001_create_zin_debugger_entries_table.js → 20260331000001_create_zin_trace_entries_table.js} +5 -5
- package/dist/migrations/{20260331000002_create_zin_debugger_entries_tags_table.d.ts → 20260331000002_create_zin_trace_entries_tags_table.d.ts} +2 -2
- package/dist/migrations/{20260331000002_create_zin_debugger_entries_tags_table.js → 20260331000002_create_zin_trace_entries_tags_table.js} +5 -5
- package/dist/migrations/{20260331000003_create_zin_debugger_monitoring_table.d.ts → 20260331000003_create_zin_trace_monitoring_table.d.ts} +2 -2
- package/dist/migrations/{20260331000003_create_zin_debugger_monitoring_table.js → 20260331000003_create_zin_trace_monitoring_table.js} +4 -4
- package/dist/migrations/20260407193000_widen_trace_created_at_for_sql.d.ts +10 -0
- package/dist/migrations/20260407193000_widen_trace_created_at_for_sql.js +34 -0
- package/dist/migrations/index.d.ts +3 -3
- package/dist/migrations/index.js +5 -4
- package/dist/register.js +130 -32
- package/dist/storage/DebuggerStorage.js +1 -1
- package/dist/storage/TraceContentRedaction.d.ts +4 -0
- package/dist/storage/TraceContentRedaction.js +33 -0
- package/dist/storage/TraceEntryFiltering.d.ts +4 -0
- package/dist/storage/TraceEntryFiltering.js +13 -0
- package/dist/storage/TraceStorage.js +36 -6
- package/dist/storage/TraceWriteDiagnostics.d.ts +19 -0
- package/dist/storage/TraceWriteDiagnostics.js +98 -0
- package/dist/storage/index.js +1 -1
- package/dist/types.d.ts +37 -20
- package/dist/ui.js +1 -1
- package/dist/utils/authTag.js +1 -1
- package/dist/utils/entryFilter.d.ts +4 -0
- package/dist/utils/entryFilter.js +95 -0
- package/dist/utils/redact.d.ts +1 -0
- package/dist/utils/redact.js +43 -9
- package/dist/utils/requestFilter.js +1 -1
- package/dist/watchers/AuthWatcher.js +3 -3
- package/dist/watchers/BatchWatcher.js +3 -3
- package/dist/watchers/CacheWatcher.js +5 -5
- package/dist/watchers/CommandWatcher.js +5 -5
- package/dist/watchers/DumpWatcher.js +3 -3
- package/dist/watchers/EventWatcher.js +4 -4
- package/dist/watchers/ExceptionWatcher.js +6 -6
- package/dist/watchers/GateWatcher.js +3 -3
- package/dist/watchers/HttpClientWatcher.js +6 -6
- package/dist/watchers/HttpWatcher.js +108 -24
- package/dist/watchers/JobWatcher.js +4 -4
- package/dist/watchers/LogWatcher.js +5 -4
- package/dist/watchers/MailWatcher.js +3 -3
- package/dist/watchers/MiddlewareWatcher.js +3 -3
- package/dist/watchers/ModelWatcher.js +3 -3
- package/dist/watchers/NotificationWatcher.js +4 -4
- package/dist/watchers/QueryWatcher.js +5 -5
- package/dist/watchers/RedisWatcher.js +4 -4
- package/dist/watchers/ScheduleWatcher.js +3 -3
- package/dist/watchers/ViewWatcher.js +3 -3
- package/package.json +4 -4
- package/src/config.ts +152 -5
- package/src/dashboard/routes.ts +6 -2
- package/src/dashboard/ui.ts +80 -23
- package/src/index.ts +7 -0
- package/src/register.ts +137 -10
- package/src/storage/TraceContentRedaction.ts +44 -0
- package/src/storage/TraceEntryFiltering.ts +14 -0
- package/src/storage/TraceStorage.ts +52 -5
- package/src/storage/TraceWriteDiagnostics.ts +174 -0
- package/src/types.ts +40 -20
- package/src/utils/entryFilter.ts +108 -0
- package/src/utils/redact.ts +57 -9
- package/src/watchers/CommandWatcher.ts +1 -1
- package/src/watchers/HttpClientWatcher.ts +1 -1
- package/src/watchers/HttpWatcher.ts +132 -21
- package/src/watchers/LogWatcher.ts +27 -27
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { EntryType } from '../types.js';
|
|
2
|
+
const isObjectValue = (value) => {
|
|
3
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
4
|
+
};
|
|
5
|
+
const normalizeTerms = (terms) => {
|
|
6
|
+
if (!Array.isArray(terms))
|
|
7
|
+
return [];
|
|
8
|
+
return terms
|
|
9
|
+
.filter((term) => typeof term === 'string')
|
|
10
|
+
.map((term) => term.trim().toLowerCase())
|
|
11
|
+
.filter((term) => term !== '');
|
|
12
|
+
};
|
|
13
|
+
const matchesRule = (haystack, rule) => {
|
|
14
|
+
if (!rule)
|
|
15
|
+
return true;
|
|
16
|
+
const include = normalizeTerms(rule.include);
|
|
17
|
+
const exclude = normalizeTerms(rule.exclude);
|
|
18
|
+
if (exclude.some((term) => haystack.includes(term)))
|
|
19
|
+
return false;
|
|
20
|
+
if (include.length === 0)
|
|
21
|
+
return true;
|
|
22
|
+
return include.some((term) => haystack.includes(term));
|
|
23
|
+
};
|
|
24
|
+
const toSearchableText = (entry) => {
|
|
25
|
+
const sections = [entry.type, entry.batchId, ...(entry.tags ?? [])];
|
|
26
|
+
try {
|
|
27
|
+
sections.push(JSON.stringify(entry.content) ?? '');
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
sections.push(String(entry.content ?? ''));
|
|
31
|
+
}
|
|
32
|
+
return sections.join(' ').toLowerCase();
|
|
33
|
+
};
|
|
34
|
+
const watcherKeyByEntryType = {
|
|
35
|
+
[EntryType.REQUEST]: 'request',
|
|
36
|
+
[EntryType.QUERY]: 'query',
|
|
37
|
+
[EntryType.EXCEPTION]: 'exception',
|
|
38
|
+
[EntryType.LOG]: 'log',
|
|
39
|
+
[EntryType.JOB]: 'job',
|
|
40
|
+
[EntryType.CACHE]: 'cache',
|
|
41
|
+
[EntryType.SCHEDULE]: 'schedule',
|
|
42
|
+
[EntryType.MAIL]: 'mail',
|
|
43
|
+
[EntryType.AUTH]: 'auth',
|
|
44
|
+
[EntryType.EVENT]: 'event',
|
|
45
|
+
[EntryType.MODEL]: 'model',
|
|
46
|
+
[EntryType.NOTIFICATION]: 'notification',
|
|
47
|
+
[EntryType.REDIS]: 'redis',
|
|
48
|
+
[EntryType.GATE]: 'gate',
|
|
49
|
+
[EntryType.MIDDLEWARE]: 'middleware',
|
|
50
|
+
[EntryType.COMMAND]: 'command',
|
|
51
|
+
[EntryType.BATCH]: 'batch',
|
|
52
|
+
[EntryType.DUMP]: 'dump',
|
|
53
|
+
[EntryType.VIEW]: 'view',
|
|
54
|
+
[EntryType.CLIENT_REQUEST]: 'clientRequest',
|
|
55
|
+
};
|
|
56
|
+
const getRequestMethodRule = (watcher, entry) => {
|
|
57
|
+
if (entry.type !== EntryType.REQUEST)
|
|
58
|
+
return undefined;
|
|
59
|
+
const content = isObjectValue(entry.content) ? entry.content : undefined;
|
|
60
|
+
const methodValue = content?.['method'];
|
|
61
|
+
const method = typeof methodValue === 'string' ? methodValue.trim().toLowerCase() : '';
|
|
62
|
+
if (method === 'get')
|
|
63
|
+
return watcher.get;
|
|
64
|
+
if (method === 'post')
|
|
65
|
+
return watcher.post;
|
|
66
|
+
if (method === 'put')
|
|
67
|
+
return watcher.put;
|
|
68
|
+
if (method === 'patch')
|
|
69
|
+
return watcher.patch;
|
|
70
|
+
if (method === 'delete' || method === 'del')
|
|
71
|
+
return watcher.delete;
|
|
72
|
+
return watcher.all;
|
|
73
|
+
};
|
|
74
|
+
export const TraceEntryFilter = Object.freeze({
|
|
75
|
+
shouldCapture(entry, config) {
|
|
76
|
+
const watcherKey = watcherKeyByEntryType[entry.type];
|
|
77
|
+
const watcher = config.watchers[watcherKey];
|
|
78
|
+
if (watcher === false)
|
|
79
|
+
return false;
|
|
80
|
+
if (!isObjectValue(watcher))
|
|
81
|
+
return true;
|
|
82
|
+
const haystack = toSearchableText(entry);
|
|
83
|
+
if (!matchesRule(haystack, watcher))
|
|
84
|
+
return false;
|
|
85
|
+
if (watcherKey === 'request') {
|
|
86
|
+
const requestWatcher = watcher;
|
|
87
|
+
const methodRule = getRequestMethodRule(requestWatcher, entry);
|
|
88
|
+
if (!matchesRule(haystack, requestWatcher.all))
|
|
89
|
+
return false;
|
|
90
|
+
if (!matchesRule(haystack, methodRule))
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
},
|
|
95
|
+
});
|
package/dist/utils/redact.d.ts
CHANGED
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
* Redaction helpers for @zintrust/trace watchers.
|
|
3
3
|
*/
|
|
4
4
|
export declare const redactHeaders: (headers: Record<string, string>, fields: string[]) => Record<string, string>;
|
|
5
|
+
export declare const redactUnknown: (value: unknown, fields: string[]) => unknown;
|
|
5
6
|
export declare const redactObject: (obj: Record<string, unknown>, fields: string[]) => Record<string, unknown>;
|
|
6
7
|
export declare const redactString: (value: string, fields: string[]) => string;
|
package/dist/utils/redact.js
CHANGED
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Redaction helpers for @zintrust/trace watchers.
|
|
3
3
|
*/
|
|
4
|
-
const REDACTED = '
|
|
4
|
+
const REDACTED = '****';
|
|
5
|
+
const isArrayValue = (value) => Array.isArray(value);
|
|
6
|
+
const isObjectValue = (value) => {
|
|
7
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
+
};
|
|
9
|
+
const normalizeFields = (fields) => {
|
|
10
|
+
const normalized = new Set();
|
|
11
|
+
for (const field of fields) {
|
|
12
|
+
if (typeof field !== 'string')
|
|
13
|
+
continue;
|
|
14
|
+
const key = field.trim().toLowerCase();
|
|
15
|
+
if (key !== '')
|
|
16
|
+
normalized.add(key);
|
|
17
|
+
}
|
|
18
|
+
return normalized;
|
|
19
|
+
};
|
|
20
|
+
const redactUnknownValue = (value, fields, seen) => {
|
|
21
|
+
if (isArrayValue(value)) {
|
|
22
|
+
return value.map((item) => redactUnknownValue(item, fields, seen));
|
|
23
|
+
}
|
|
24
|
+
if (!isObjectValue(value)) {
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
if (seen.has(value)) {
|
|
28
|
+
return '[Circular]';
|
|
29
|
+
}
|
|
30
|
+
seen.add(value);
|
|
31
|
+
const out = {};
|
|
32
|
+
for (const [key, entryValue] of Object.entries(value)) {
|
|
33
|
+
out[key] = fields.has(key.toLowerCase())
|
|
34
|
+
? REDACTED
|
|
35
|
+
: redactUnknownValue(entryValue, fields, seen);
|
|
36
|
+
}
|
|
37
|
+
seen.delete(value);
|
|
38
|
+
return out;
|
|
39
|
+
};
|
|
5
40
|
const redactQuerySegment = (segment, fields) => {
|
|
6
41
|
const separatorIndex = segment.indexOf('=');
|
|
7
42
|
if (separatorIndex <= 0)
|
|
@@ -13,23 +48,22 @@ const redactQuerySegment = (segment, fields) => {
|
|
|
13
48
|
return `${key}=${REDACTED}`;
|
|
14
49
|
};
|
|
15
50
|
export const redactHeaders = (headers, fields) => {
|
|
16
|
-
const lower =
|
|
51
|
+
const lower = normalizeFields(fields);
|
|
17
52
|
const out = {};
|
|
18
53
|
for (const [k, v] of Object.entries(headers)) {
|
|
19
54
|
out[k] = lower.has(k.toLowerCase()) ? REDACTED : v;
|
|
20
55
|
}
|
|
21
56
|
return out;
|
|
22
57
|
};
|
|
58
|
+
export const redactUnknown = (value, fields) => {
|
|
59
|
+
return redactUnknownValue(value, normalizeFields(fields), new WeakSet());
|
|
60
|
+
};
|
|
23
61
|
export const redactObject = (obj, fields) => {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
27
|
-
out[k] = lower.has(k.toLowerCase()) ? REDACTED : v;
|
|
28
|
-
}
|
|
29
|
-
return out;
|
|
62
|
+
const redacted = redactUnknown(obj, fields);
|
|
63
|
+
return isObjectValue(redacted) ? redacted : {};
|
|
30
64
|
};
|
|
31
65
|
export const redactString = (value, fields) => {
|
|
32
|
-
const lower =
|
|
66
|
+
const lower = normalizeFields(fields);
|
|
33
67
|
if (value === '')
|
|
34
68
|
return value;
|
|
35
69
|
let output = '';
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* AuthWatcher — records login/logout/failed auth events.
|
|
3
3
|
* Credentials are never stored; only the outcome.
|
|
4
4
|
*/
|
|
5
|
-
import { TraceContext } from '../context';
|
|
6
|
-
import { EntryType } from '../types';
|
|
7
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
5
|
+
import { TraceContext } from '../context.js';
|
|
6
|
+
import { EntryType } from '../types.js';
|
|
7
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
8
8
|
let _storage = null;
|
|
9
9
|
let _ignoreRoutes = [];
|
|
10
10
|
const emit = (event, userId) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import { EntryType } from '../types';
|
|
3
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
1
|
+
import { TraceContext } from '../context.js';
|
|
2
|
+
import { EntryType } from '../types.js';
|
|
3
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
4
4
|
let _storage = null;
|
|
5
5
|
let _ignoreRoutes = [];
|
|
6
6
|
const emit = (name, total, processed, failed, status) => {
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* CacheWatcher — records cache operations.
|
|
3
3
|
* Call CacheWatcher.emit() from within your cache driver instrumentation.
|
|
4
4
|
*/
|
|
5
|
-
import { TraceContext } from '../context';
|
|
6
|
-
import { EntryType } from '../types';
|
|
7
|
-
import { AuthTag } from '../utils/authTag';
|
|
8
|
-
import { redactString } from '../utils/redact';
|
|
9
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
5
|
+
import { TraceContext } from '../context.js';
|
|
6
|
+
import { EntryType } from '../types.js';
|
|
7
|
+
import { AuthTag } from '../utils/authTag.js';
|
|
8
|
+
import { redactString } from '../utils/redact.js';
|
|
9
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
10
10
|
let _storage = null;
|
|
11
11
|
let _redactionFields = [];
|
|
12
12
|
let _ignoreRoutes = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import { EntryType } from '../types';
|
|
3
|
-
import { redactObject } from '../utils/redact';
|
|
4
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
1
|
+
import { TraceContext } from '../context.js';
|
|
2
|
+
import { EntryType } from '../types.js';
|
|
3
|
+
import { redactObject } from '../utils/redact.js';
|
|
4
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
5
5
|
let _storage = null;
|
|
6
6
|
let _redactKeys = [];
|
|
7
7
|
let _ignoreRoutes = [];
|
|
@@ -39,7 +39,7 @@ export const CommandWatcher = Object.freeze({
|
|
|
39
39
|
if (config.watchers.command === false)
|
|
40
40
|
return () => undefined;
|
|
41
41
|
_storage = storage;
|
|
42
|
-
_redactKeys = config.redaction?.body ?? [];
|
|
42
|
+
_redactKeys = [...(config.redaction?.keys ?? []), ...(config.redaction?.body ?? [])];
|
|
43
43
|
_ignoreRoutes = config.ignoreRoutes;
|
|
44
44
|
return () => {
|
|
45
45
|
_storage = null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import { EntryType } from '../types';
|
|
3
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
1
|
+
import { TraceContext } from '../context.js';
|
|
2
|
+
import { EntryType } from '../types.js';
|
|
3
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
4
4
|
let _storage = null;
|
|
5
5
|
let _enabled = false;
|
|
6
6
|
let _ignoreRoutes = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import { EntryType } from '../types';
|
|
3
|
-
import { AuthTag } from '../utils/authTag';
|
|
4
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
1
|
+
import { TraceContext } from '../context.js';
|
|
2
|
+
import { EntryType } from '../types.js';
|
|
3
|
+
import { AuthTag } from '../utils/authTag.js';
|
|
4
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
5
5
|
let _storage = null;
|
|
6
6
|
let _ignoreRoutes = [];
|
|
7
7
|
const emit = (name, listenerCount, payload) => {
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* from within its error handler, or the register() side-effect adds a
|
|
5
5
|
* process-level unhandledRejection/uncaughtException listener as fallback.
|
|
6
6
|
*/
|
|
7
|
-
import { TraceContext } from '../context';
|
|
8
|
-
import { EntryType } from '../types';
|
|
9
|
-
import { AuthTag } from '../utils/authTag';
|
|
10
|
-
import { familyHash } from '../utils/familyHash';
|
|
11
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
12
|
-
import { parseStackFrameLine } from '../utils/stackFrame';
|
|
7
|
+
import { TraceContext } from '../context.js';
|
|
8
|
+
import { EntryType } from '../types.js';
|
|
9
|
+
import { AuthTag } from '../utils/authTag.js';
|
|
10
|
+
import { familyHash } from '../utils/familyHash.js';
|
|
11
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
12
|
+
import { parseStackFrameLine } from '../utils/stackFrame.js';
|
|
13
13
|
const getLinePreview = (_file, _line) => {
|
|
14
14
|
return {};
|
|
15
15
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import { EntryType } from '../types';
|
|
3
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
1
|
+
import { TraceContext } from '../context.js';
|
|
2
|
+
import { EntryType } from '../types.js';
|
|
3
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
4
4
|
let _storage = null;
|
|
5
5
|
let _ignoreRoutes = [];
|
|
6
6
|
const emit = (ability, result, userId, subject) => {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import { EntryType } from '../types';
|
|
3
|
-
import { AuthTag } from '../utils/authTag';
|
|
4
|
-
import { redactHeaders } from '../utils/redact';
|
|
5
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
1
|
+
import { TraceContext } from '../context.js';
|
|
2
|
+
import { EntryType } from '../types.js';
|
|
3
|
+
import { AuthTag } from '../utils/authTag.js';
|
|
4
|
+
import { redactHeaders } from '../utils/redact.js';
|
|
5
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
6
6
|
let _storage = null;
|
|
7
7
|
let _redactHeaderNames = [];
|
|
8
8
|
let _ignoreRoutes = [];
|
|
@@ -40,7 +40,7 @@ export const HttpClientWatcher = Object.freeze({
|
|
|
40
40
|
if (config.watchers.clientRequest === false)
|
|
41
41
|
return () => undefined;
|
|
42
42
|
_storage = storage;
|
|
43
|
-
_redactHeaderNames = config.redaction?.headers ?? [];
|
|
43
|
+
_redactHeaderNames = [...(config.redaction?.keys ?? []), ...(config.redaction?.headers ?? [])];
|
|
44
44
|
_ignoreRoutes = config.ignoreRoutes;
|
|
45
45
|
return () => {
|
|
46
46
|
_storage = null;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import { EntryType } from '../types';
|
|
3
|
-
import { AuthTag } from '../utils/authTag';
|
|
4
|
-
import { redactHeaders, redactObject } from '../utils/redact';
|
|
5
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
1
|
+
import { TraceContext } from '../context.js';
|
|
2
|
+
import { EntryType } from '../types.js';
|
|
3
|
+
import { AuthTag } from '../utils/authTag.js';
|
|
4
|
+
import { redactHeaders, redactObject, redactUnknown } from '../utils/redact.js';
|
|
5
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
6
6
|
const normalizeHeaders = (headers) => {
|
|
7
7
|
if (!headers)
|
|
8
8
|
return {};
|
|
@@ -14,16 +14,91 @@ const normalizeHeaders = (headers) => {
|
|
|
14
14
|
return [];
|
|
15
15
|
}));
|
|
16
16
|
};
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const normalizeHeaderValue = (value) => {
|
|
18
|
+
return Array.isArray(value) ? value.join(', ') : value;
|
|
19
|
+
};
|
|
20
|
+
const registerCompletionHandler = (response, onComplete) => {
|
|
21
|
+
const raw = response.getRaw();
|
|
22
|
+
if (typeof raw.once !== 'function')
|
|
23
|
+
return () => undefined;
|
|
24
|
+
let completed = false;
|
|
25
|
+
const cleanup = () => {
|
|
26
|
+
if (typeof raw.off === 'function') {
|
|
27
|
+
raw.off('finish', markCompleted);
|
|
28
|
+
raw.off('close', markCompleted);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const markCompleted = () => {
|
|
32
|
+
if (completed)
|
|
33
|
+
return;
|
|
34
|
+
completed = true;
|
|
35
|
+
cleanup();
|
|
36
|
+
onComplete();
|
|
37
|
+
};
|
|
38
|
+
raw.once('finish', markCompleted);
|
|
39
|
+
raw.once('close', markCompleted);
|
|
40
|
+
return cleanup;
|
|
41
|
+
};
|
|
42
|
+
const captureResponse = (response, config) => {
|
|
43
|
+
const headers = {};
|
|
44
|
+
const redactFields = [...config.redaction.keys, ...config.redaction.body];
|
|
45
|
+
const originalSetHeader = response.setHeader;
|
|
46
|
+
const originalJson = response.json;
|
|
47
|
+
const originalText = response.text;
|
|
48
|
+
const originalHtml = response.html;
|
|
49
|
+
const originalSend = response.send;
|
|
50
|
+
const capture = {
|
|
51
|
+
headers,
|
|
52
|
+
body: undefined,
|
|
53
|
+
restore() {
|
|
54
|
+
response.setHeader = originalSetHeader;
|
|
55
|
+
response.json = originalJson;
|
|
56
|
+
response.text = originalText;
|
|
57
|
+
response.html = originalHtml;
|
|
58
|
+
response.send = originalSend;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
response.setHeader = function setHeader(name, value) {
|
|
62
|
+
headers[name] = normalizeHeaderValue(value);
|
|
63
|
+
return originalSetHeader.call(this, name, value);
|
|
64
|
+
};
|
|
65
|
+
response.json = function json(data) {
|
|
66
|
+
capture.body = redactUnknown(data, redactFields);
|
|
67
|
+
originalJson.call(this, data);
|
|
68
|
+
};
|
|
69
|
+
response.text = function text(value) {
|
|
70
|
+
capture.body = value;
|
|
71
|
+
originalText.call(this, value);
|
|
72
|
+
};
|
|
73
|
+
response.html = function html(value) {
|
|
74
|
+
capture.body = value;
|
|
75
|
+
originalHtml.call(this, value);
|
|
76
|
+
};
|
|
77
|
+
response.send = function send(data) {
|
|
78
|
+
capture.body = typeof data === 'string' ? data : `[binary ${data.length} bytes]`;
|
|
79
|
+
originalSend.call(this, data);
|
|
80
|
+
};
|
|
81
|
+
return capture;
|
|
82
|
+
};
|
|
83
|
+
const buildEntry = (req, res, start, config, responseCapture) => {
|
|
84
|
+
const headers = redactHeaders(normalizeHeaders(req.headers), [
|
|
85
|
+
...config.redaction.keys,
|
|
86
|
+
...config.redaction.headers,
|
|
87
|
+
]);
|
|
88
|
+
const payload = req.body
|
|
89
|
+
? redactObject(req.body, [...config.redaction.keys, ...config.redaction.body])
|
|
90
|
+
: {};
|
|
20
91
|
return {
|
|
21
92
|
method: req.getMethod(),
|
|
22
93
|
uri: req.getPath(),
|
|
23
94
|
headers,
|
|
24
95
|
payload,
|
|
25
96
|
responseStatus: res.getStatus(),
|
|
26
|
-
responseHeaders:
|
|
97
|
+
responseHeaders: redactHeaders(responseCapture.headers, [
|
|
98
|
+
...config.redaction.keys,
|
|
99
|
+
...config.redaction.headers,
|
|
100
|
+
]),
|
|
101
|
+
responseBody: responseCapture.body,
|
|
27
102
|
duration: Date.now() - start,
|
|
28
103
|
memory: TraceContext.getMemory(),
|
|
29
104
|
middleware: [],
|
|
@@ -48,22 +123,31 @@ export const HttpWatcher = Object.freeze({
|
|
|
48
123
|
return next();
|
|
49
124
|
const start = TraceContext.now();
|
|
50
125
|
const batchId = TraceContext.getBatchId();
|
|
126
|
+
const responseCapture = captureResponse(response, config);
|
|
127
|
+
let didPersist = false;
|
|
128
|
+
const persistEntry = () => {
|
|
129
|
+
if (didPersist)
|
|
130
|
+
return;
|
|
131
|
+
didPersist = true;
|
|
132
|
+
const content = buildEntry(request, response, start, config, responseCapture);
|
|
133
|
+
const tags = AuthTag.append([]);
|
|
134
|
+
if (content.responseStatus >= 500)
|
|
135
|
+
tags.push('failed');
|
|
136
|
+
responseCapture.restore();
|
|
137
|
+
storage
|
|
138
|
+
.writeEntry({
|
|
139
|
+
uuid: crypto.randomUUID(),
|
|
140
|
+
batchId,
|
|
141
|
+
type: EntryType.REQUEST,
|
|
142
|
+
content,
|
|
143
|
+
tags,
|
|
144
|
+
isLatest: true,
|
|
145
|
+
createdAt: TraceContext.now(),
|
|
146
|
+
})
|
|
147
|
+
.catch(() => undefined); // fire-and-forget
|
|
148
|
+
};
|
|
149
|
+
registerCompletionHandler(response, persistEntry);
|
|
51
150
|
await next();
|
|
52
|
-
const content = buildEntry(request, response, start, config);
|
|
53
|
-
const tags = AuthTag.append([]);
|
|
54
|
-
if (content.responseStatus >= 500)
|
|
55
|
-
tags.push('failed');
|
|
56
|
-
storage
|
|
57
|
-
.writeEntry({
|
|
58
|
-
uuid: crypto.randomUUID(),
|
|
59
|
-
batchId,
|
|
60
|
-
type: EntryType.REQUEST,
|
|
61
|
-
content,
|
|
62
|
-
tags,
|
|
63
|
-
isLatest: true,
|
|
64
|
-
createdAt: TraceContext.now(),
|
|
65
|
-
})
|
|
66
|
-
.catch(() => undefined); // fire-and-forget
|
|
67
151
|
};
|
|
68
152
|
registerMiddleware(middleware);
|
|
69
153
|
return () => undefined;
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Subsystems must call JobWatcher.onDispatch / onProcess / onFail from
|
|
4
4
|
* within their queue implementation for full tracking.
|
|
5
5
|
*/
|
|
6
|
-
import { TraceContext } from '../context';
|
|
7
|
-
import { EntryType } from '../types';
|
|
8
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
9
|
-
import { parseStackFrameLine } from '../utils/stackFrame';
|
|
6
|
+
import { TraceContext } from '../context.js';
|
|
7
|
+
import { EntryType } from '../types.js';
|
|
8
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
9
|
+
import { parseStackFrameLine } from '../utils/stackFrame.js';
|
|
10
10
|
// Module-level storage ref so emit helpers can be called from outside.
|
|
11
11
|
let _storage = null;
|
|
12
12
|
let _ignoreRoutes = [];
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* LogWatcher — captures Logger output via Logger.addSink().
|
|
3
3
|
*/
|
|
4
4
|
import { Logger } from '@zintrust/core';
|
|
5
|
-
import { TraceContext } from '../context';
|
|
6
|
-
import { EntryType } from '../types';
|
|
7
|
-
import { AuthTag } from '../utils/authTag';
|
|
8
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
5
|
+
import { TraceContext } from '../context.js';
|
|
6
|
+
import { EntryType } from '../types.js';
|
|
7
|
+
import { AuthTag } from '../utils/authTag.js';
|
|
8
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
9
|
+
// type LoggerSink = (level: string, message: string, context?: Record<string, unknown>) => void;
|
|
9
10
|
const LEVEL_PRIORITY = {
|
|
10
11
|
debug: 0,
|
|
11
12
|
info: 1,
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* MailWatcher — records mail dispatch intent.
|
|
3
3
|
* Body is never captured; only to/subject/template.
|
|
4
4
|
*/
|
|
5
|
-
import { TraceContext } from '../context';
|
|
6
|
-
import { EntryType } from '../types';
|
|
7
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
5
|
+
import { TraceContext } from '../context.js';
|
|
6
|
+
import { EntryType } from '../types.js';
|
|
7
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
8
8
|
let _storage = null;
|
|
9
9
|
let _ignoreRoutes = [];
|
|
10
10
|
const emit = (to, subject, template) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import { EntryType } from '../types';
|
|
3
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
1
|
+
import { TraceContext } from '../context.js';
|
|
2
|
+
import { EntryType } from '../types.js';
|
|
3
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
4
4
|
let _storage = null;
|
|
5
5
|
let _ignoreRoutes = [];
|
|
6
6
|
const emit = (name, event, duration) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import { EntryType } from '../types';
|
|
3
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
1
|
+
import { TraceContext } from '../context.js';
|
|
2
|
+
import { EntryType } from '../types.js';
|
|
3
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
4
4
|
let _storage = null;
|
|
5
5
|
let _ignoreRoutes = [];
|
|
6
6
|
const emit = (action, model, id, changes) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import { EntryType } from '../types';
|
|
3
|
-
import { AuthTag } from '../utils/authTag';
|
|
4
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
1
|
+
import { TraceContext } from '../context.js';
|
|
2
|
+
import { EntryType } from '../types.js';
|
|
3
|
+
import { AuthTag } from '../utils/authTag.js';
|
|
4
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
5
5
|
let _storage = null;
|
|
6
6
|
let _ignoreRoutes = [];
|
|
7
7
|
const emit = (notification, channels, notifiable) => {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* QueryWatcher — hooks into Database.onAfterQuery to record SQL entries.
|
|
3
3
|
*/
|
|
4
|
-
import { TraceContext } from '../context';
|
|
5
|
-
import { TraceStorage } from '../storage';
|
|
6
|
-
import { EntryType } from '../types';
|
|
7
|
-
import { AuthTag } from '../utils/authTag';
|
|
8
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
4
|
+
import { TraceContext } from '../context.js';
|
|
5
|
+
import { TraceStorage } from '../storage/index.js';
|
|
6
|
+
import { EntryType } from '../types.js';
|
|
7
|
+
import { AuthTag } from '../utils/authTag.js';
|
|
8
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
9
9
|
const bindingsInterpolated = (sql, params) => {
|
|
10
10
|
// Inline params for display only — safe, not for re-execution.
|
|
11
11
|
let i = 0;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import { EntryType } from '../types';
|
|
3
|
-
import { AuthTag } from '../utils/authTag';
|
|
4
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
1
|
+
import { TraceContext } from '../context.js';
|
|
2
|
+
import { EntryType } from '../types.js';
|
|
3
|
+
import { AuthTag } from '../utils/authTag.js';
|
|
4
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
5
5
|
let _storage = null;
|
|
6
6
|
let _ignoreRoutes = [];
|
|
7
7
|
/** Emit a redis command trace. Key/value payload is intentionally omitted for security. */
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ScheduleWatcher — records scheduled task runs and outcomes.
|
|
3
3
|
*/
|
|
4
|
-
import { TraceContext } from '../context';
|
|
5
|
-
import { EntryType } from '../types';
|
|
6
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
4
|
+
import { TraceContext } from '../context.js';
|
|
5
|
+
import { EntryType } from '../types.js';
|
|
6
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
7
7
|
let _storage = null;
|
|
8
8
|
let _ignoreRoutes = [];
|
|
9
9
|
const emit = (name, expression, status, duration, output) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { TraceContext } from '../context';
|
|
2
|
-
import { EntryType } from '../types';
|
|
3
|
-
import { RequestFilter } from '../utils/requestFilter';
|
|
1
|
+
import { TraceContext } from '../context.js';
|
|
2
|
+
import { EntryType } from '../types.js';
|
|
3
|
+
import { RequestFilter } from '../utils/requestFilter.js';
|
|
4
4
|
let _storage = null;
|
|
5
5
|
let _ignoreRoutes = [];
|
|
6
6
|
const emit = (template, duration) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/trace",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.77",
|
|
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.77"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"telescope"
|
|
54
54
|
],
|
|
55
55
|
"scripts": {
|
|
56
|
-
"build": "
|
|
56
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.migrations.json && node ../../scripts/fix-dist-esm-imports.mjs dist",
|
|
57
57
|
"prepublishOnly": "npm run build"
|
|
58
58
|
}
|
|
59
|
-
}
|
|
59
|
+
}
|