@zintrust/trace 0.4.94 → 0.4.96
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 +40 -28
- package/dist/config.js +83 -6
- package/dist/dashboard/ui.js +40 -13
- package/dist/storage/DebuggerStorage.d.ts +13 -0
- package/dist/storage/DebuggerStorage.js +195 -0
- package/dist/storage/TraceContentBudget.js +67 -15
- package/dist/types.d.ts +13 -1
- package/dist/utils/entryFilter.js +20 -0
- package/dist/watchers/HttpClientWatcher.d.ts +1 -1
- package/dist/watchers/HttpClientWatcher.js +95 -18
- package/dist/watchers/HttpWatcher.js +7 -1
- package/dist/watchers/MiddlewareWatcher.js +5 -0
- package/dist/watchers/ModelWatcher.js +5 -0
- package/package.json +2 -2
- package/src/config.ts +136 -6
- package/src/dashboard/ui.ts +40 -13
- package/src/storage/TraceContentBudget.ts +98 -17
- package/src/types.ts +15 -1
- package/src/utils/entryFilter.ts +23 -0
- package/src/watchers/HttpClientWatcher.ts +125 -19
- package/src/watchers/HttpWatcher.ts +8 -1
- package/src/watchers/MiddlewareWatcher.ts +9 -0
- package/src/watchers/ModelWatcher.ts +9 -0
|
@@ -21,6 +21,58 @@ const describeValueType = (value) => {
|
|
|
21
21
|
return 'null';
|
|
22
22
|
return typeof value;
|
|
23
23
|
};
|
|
24
|
+
const chooseLargerCandidate = (left, right) => {
|
|
25
|
+
if (left === null)
|
|
26
|
+
return right;
|
|
27
|
+
if (right === null)
|
|
28
|
+
return left;
|
|
29
|
+
return right.size > left.size ? right : left;
|
|
30
|
+
};
|
|
31
|
+
const fallbackCandidate = (value, path) => {
|
|
32
|
+
return path.length === 0 ? null : { path, size: serializedSize(value) };
|
|
33
|
+
};
|
|
34
|
+
const findLargestDroppablePathInArray = (value, path) => {
|
|
35
|
+
let best = null;
|
|
36
|
+
for (const [index, item] of value.entries()) {
|
|
37
|
+
best = chooseLargerCandidate(best, findLargestDroppablePath(item, [...path, index]));
|
|
38
|
+
}
|
|
39
|
+
return best ?? fallbackCandidate(value, path);
|
|
40
|
+
};
|
|
41
|
+
const findLargestDroppablePathInObject = (value, path) => {
|
|
42
|
+
let best = null;
|
|
43
|
+
for (const [key, entryValue] of Object.entries(value)) {
|
|
44
|
+
if (key === '__traceNotice')
|
|
45
|
+
continue;
|
|
46
|
+
best = chooseLargerCandidate(best, findLargestDroppablePath(entryValue, [...path, key]));
|
|
47
|
+
}
|
|
48
|
+
return best ?? fallbackCandidate(value, path);
|
|
49
|
+
};
|
|
50
|
+
const findLargestDroppablePath = (value, path = []) => {
|
|
51
|
+
if (Array.isArray(value))
|
|
52
|
+
return findLargestDroppablePathInArray(value, path);
|
|
53
|
+
if (typeof value === 'object' && value !== null) {
|
|
54
|
+
return findLargestDroppablePathInObject(value, path);
|
|
55
|
+
}
|
|
56
|
+
return fallbackCandidate(value, path);
|
|
57
|
+
};
|
|
58
|
+
const replaceAtPath = (value, path, replacement) => {
|
|
59
|
+
if (path.length === 0)
|
|
60
|
+
return replacement;
|
|
61
|
+
const [segment, ...rest] = path;
|
|
62
|
+
if (Array.isArray(value) && typeof segment === 'number') {
|
|
63
|
+
const next = value.slice();
|
|
64
|
+
next[segment] = replaceAtPath(next[segment], rest, replacement);
|
|
65
|
+
return next;
|
|
66
|
+
}
|
|
67
|
+
if (typeof value === 'object' && value !== null && typeof segment === 'string') {
|
|
68
|
+
const current = value;
|
|
69
|
+
return {
|
|
70
|
+
...current,
|
|
71
|
+
[segment]: replaceAtPath(current[segment], rest, replacement),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
};
|
|
24
76
|
const compactValue = (value, depth) => {
|
|
25
77
|
if (depth >= DEFAULT_MAX_DEPTH) {
|
|
26
78
|
return DROPPED_FIELD_MESSAGE;
|
|
@@ -52,18 +104,18 @@ const compactValue = (value, depth) => {
|
|
|
52
104
|
}
|
|
53
105
|
return Object.fromEntries(compactedEntries);
|
|
54
106
|
};
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (
|
|
107
|
+
const compactStructuredValueToBudget = (value) => {
|
|
108
|
+
let compacted = typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
109
|
+
? {
|
|
110
|
+
...value,
|
|
111
|
+
__traceNotice: COMPACTED_CONTENT_MESSAGE,
|
|
112
|
+
}
|
|
113
|
+
: value;
|
|
114
|
+
while (serializedSize(compacted) > DEFAULT_MAX_ENTRY_BYTES) {
|
|
115
|
+
const candidate = findLargestDroppablePath(compacted);
|
|
116
|
+
if (candidate === null)
|
|
65
117
|
break;
|
|
66
|
-
compacted
|
|
118
|
+
compacted = replaceAtPath(compacted, candidate.path, DROPPED_FIELD_MESSAGE);
|
|
67
119
|
}
|
|
68
120
|
return compacted;
|
|
69
121
|
};
|
|
@@ -75,10 +127,10 @@ const fitContentToBudget = (content) => {
|
|
|
75
127
|
if (serializedSize(compacted) <= DEFAULT_MAX_ENTRY_BYTES) {
|
|
76
128
|
return compacted;
|
|
77
129
|
}
|
|
78
|
-
if (typeof compacted === 'object' && compacted !== null
|
|
79
|
-
const
|
|
80
|
-
if (serializedSize(
|
|
81
|
-
return
|
|
130
|
+
if (typeof compacted === 'object' && compacted !== null) {
|
|
131
|
+
const budgetCompacted = compactStructuredValueToBudget(compacted);
|
|
132
|
+
if (serializedSize(budgetCompacted) <= DEFAULT_MAX_ENTRY_BYTES) {
|
|
133
|
+
return budgetCompacted;
|
|
82
134
|
}
|
|
83
135
|
}
|
|
84
136
|
return {
|
package/dist/types.d.ts
CHANGED
|
@@ -189,6 +189,7 @@ export interface ViewContent {
|
|
|
189
189
|
hostname: string;
|
|
190
190
|
}
|
|
191
191
|
export interface ClientRequestContent {
|
|
192
|
+
source?: string;
|
|
192
193
|
method: string;
|
|
193
194
|
url: string;
|
|
194
195
|
requestHeaders: Record<string, string>;
|
|
@@ -201,6 +202,7 @@ export interface ClientRequestContent {
|
|
|
201
202
|
hostname: string;
|
|
202
203
|
}
|
|
203
204
|
export interface ClientRequestTraceInput {
|
|
205
|
+
source?: string;
|
|
204
206
|
method: string;
|
|
205
207
|
url: string;
|
|
206
208
|
requestHeaders: Record<string, string>;
|
|
@@ -268,6 +270,12 @@ export type TraceFilterRule = {
|
|
|
268
270
|
include?: string[];
|
|
269
271
|
exclude?: string[];
|
|
270
272
|
};
|
|
273
|
+
export type TraceClientRequestCaptureRule = TraceFilterRule & {
|
|
274
|
+
requestHeaders?: boolean;
|
|
275
|
+
requestBody?: boolean;
|
|
276
|
+
responseHeaders?: boolean;
|
|
277
|
+
responseBody?: boolean;
|
|
278
|
+
};
|
|
271
279
|
export type TraceRequestWatcherConfig = TraceFilterRule & {
|
|
272
280
|
all?: TraceFilterRule;
|
|
273
281
|
get?: TraceFilterRule;
|
|
@@ -276,8 +284,12 @@ export type TraceRequestWatcherConfig = TraceFilterRule & {
|
|
|
276
284
|
patch?: TraceFilterRule;
|
|
277
285
|
delete?: TraceFilterRule;
|
|
278
286
|
};
|
|
287
|
+
export type TraceClientRequestWatcherConfig = TraceClientRequestCaptureRule & {
|
|
288
|
+
sources?: Record<string, TraceClientRequestCaptureRule>;
|
|
289
|
+
};
|
|
279
290
|
export type TraceWatcherToggle = boolean | TraceFilterRule;
|
|
280
291
|
export type TraceRequestWatcherToggle = boolean | TraceRequestWatcherConfig;
|
|
292
|
+
export type TraceClientRequestWatcherToggle = boolean | TraceClientRequestWatcherConfig;
|
|
281
293
|
export type WatcherToggles = {
|
|
282
294
|
request?: TraceRequestWatcherToggle;
|
|
283
295
|
query?: TraceWatcherToggle;
|
|
@@ -298,7 +310,7 @@ export type WatcherToggles = {
|
|
|
298
310
|
batch?: TraceWatcherToggle;
|
|
299
311
|
dump?: TraceWatcherToggle;
|
|
300
312
|
view?: TraceWatcherToggle;
|
|
301
|
-
clientRequest?:
|
|
313
|
+
clientRequest?: TraceClientRequestWatcherToggle;
|
|
302
314
|
};
|
|
303
315
|
export interface ITraceConfig {
|
|
304
316
|
enabled: boolean;
|
|
@@ -13,6 +13,8 @@ const normalizeTerms = (terms) => {
|
|
|
13
13
|
const matchesRule = (haystack, rule) => {
|
|
14
14
|
if (!rule)
|
|
15
15
|
return true;
|
|
16
|
+
if (rule.enabled === false)
|
|
17
|
+
return false;
|
|
16
18
|
const include = normalizeTerms(rule.include);
|
|
17
19
|
const exclude = normalizeTerms(rule.exclude);
|
|
18
20
|
if (exclude.some((term) => haystack.includes(term)))
|
|
@@ -71,6 +73,16 @@ const getRequestMethodRule = (watcher, entry) => {
|
|
|
71
73
|
return watcher.delete;
|
|
72
74
|
return watcher.all;
|
|
73
75
|
};
|
|
76
|
+
const getClientRequestSourceRule = (watcher, entry) => {
|
|
77
|
+
if (entry.type !== EntryType.CLIENT_REQUEST)
|
|
78
|
+
return undefined;
|
|
79
|
+
const content = isObjectValue(entry.content) ? entry.content : undefined;
|
|
80
|
+
const sourceValue = content?.['source'];
|
|
81
|
+
const source = typeof sourceValue === 'string' ? sourceValue.trim().toLowerCase() : '';
|
|
82
|
+
if (source === '')
|
|
83
|
+
return undefined;
|
|
84
|
+
return watcher.sources?.[source];
|
|
85
|
+
};
|
|
74
86
|
export const TraceEntryFilter = Object.freeze({
|
|
75
87
|
shouldCapture(entry, config) {
|
|
76
88
|
const watcherKey = watcherKeyByEntryType[entry.type];
|
|
@@ -90,6 +102,14 @@ export const TraceEntryFilter = Object.freeze({
|
|
|
90
102
|
if (!matchesRule(haystack, methodRule))
|
|
91
103
|
return false;
|
|
92
104
|
}
|
|
105
|
+
if (watcherKey === 'clientRequest') {
|
|
106
|
+
const clientRequestWatcher = watcher;
|
|
107
|
+
const sourceRule = getClientRequestSourceRule(clientRequestWatcher, entry);
|
|
108
|
+
if (sourceRule?.enabled === false)
|
|
109
|
+
return false;
|
|
110
|
+
if (!matchesRule(haystack, sourceRule))
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
93
113
|
return true;
|
|
94
114
|
},
|
|
95
115
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ClientRequestTraceInput, ITraceWatcher } from '../types';
|
|
2
|
-
declare const emit: ({ method, url, requestHeaders, responseStatus, duration, requestBody, responseHeaders, responseBody, error, }: ClientRequestTraceInput) => void;
|
|
2
|
+
declare const emit: ({ source, method, url, requestHeaders, responseStatus, duration, requestBody, responseHeaders, responseBody, error, }: ClientRequestTraceInput) => void;
|
|
3
3
|
export declare const HttpClientWatcher: ITraceWatcher & {
|
|
4
4
|
emit: typeof emit;
|
|
5
5
|
};
|
|
@@ -7,32 +7,104 @@ let _storage = null;
|
|
|
7
7
|
let _redactHeaderNames = [];
|
|
8
8
|
let _redactBodyFields = [];
|
|
9
9
|
let _ignoreRoutes = [];
|
|
10
|
-
|
|
10
|
+
let _clientRequestWatcher;
|
|
11
|
+
const isObjectValue = (value) => {
|
|
12
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
13
|
+
};
|
|
14
|
+
const resolveSource = (value) => {
|
|
15
|
+
if (typeof value !== 'string')
|
|
16
|
+
return undefined;
|
|
17
|
+
const normalized = value.trim().toLowerCase();
|
|
18
|
+
return normalized === '' ? undefined : normalized;
|
|
19
|
+
};
|
|
20
|
+
const resolveSourceRule = (source) => {
|
|
21
|
+
if (source === undefined)
|
|
22
|
+
return undefined;
|
|
23
|
+
return _clientRequestWatcher?.sources?.[source];
|
|
24
|
+
};
|
|
25
|
+
const shouldCaptureField = (field, sourceRule) => {
|
|
26
|
+
const scoped = sourceRule?.[field];
|
|
27
|
+
if (typeof scoped === 'boolean')
|
|
28
|
+
return scoped;
|
|
29
|
+
const global = _clientRequestWatcher?.[field];
|
|
30
|
+
if (typeof global === 'boolean')
|
|
31
|
+
return global;
|
|
32
|
+
return true;
|
|
33
|
+
};
|
|
34
|
+
const buildRequestHeaders = (requestHeaders, sourceRule) => {
|
|
35
|
+
return shouldCaptureField('requestHeaders', sourceRule)
|
|
36
|
+
? { requestHeaders: redactHeaders(requestHeaders, _redactHeaderNames) }
|
|
37
|
+
: { requestHeaders: {} };
|
|
38
|
+
};
|
|
39
|
+
const buildRequestBody = (requestBody, sourceRule) => {
|
|
40
|
+
if (requestBody === undefined)
|
|
41
|
+
return {};
|
|
42
|
+
if (!shouldCaptureField('requestBody', sourceRule))
|
|
43
|
+
return {};
|
|
44
|
+
return { requestBody: redactUnknown(requestBody, _redactBodyFields) };
|
|
45
|
+
};
|
|
46
|
+
const buildResponseHeaders = (responseHeaders, sourceRule) => {
|
|
47
|
+
if (responseHeaders === undefined)
|
|
48
|
+
return {};
|
|
49
|
+
if (!shouldCaptureField('responseHeaders', sourceRule))
|
|
50
|
+
return {};
|
|
51
|
+
return { responseHeaders: redactHeaders(responseHeaders, _redactHeaderNames) };
|
|
52
|
+
};
|
|
53
|
+
const buildResponseBody = (responseBody, sourceRule) => {
|
|
54
|
+
if (responseBody === undefined)
|
|
55
|
+
return {};
|
|
56
|
+
if (!shouldCaptureField('responseBody', sourceRule))
|
|
57
|
+
return {};
|
|
58
|
+
return { responseBody: redactUnknown(responseBody, _redactBodyFields) };
|
|
59
|
+
};
|
|
60
|
+
const buildClientRequestContent = (input, sourceRule, normalizedSource) => {
|
|
61
|
+
return {
|
|
62
|
+
...(normalizedSource === undefined ? {} : { source: normalizedSource }),
|
|
63
|
+
method: input.method.toUpperCase(),
|
|
64
|
+
url: input.url,
|
|
65
|
+
...buildRequestHeaders(input.requestHeaders, sourceRule),
|
|
66
|
+
...buildRequestBody(input.requestBody, sourceRule),
|
|
67
|
+
...(input.responseStatus === undefined ? {} : { responseStatus: input.responseStatus }),
|
|
68
|
+
...buildResponseHeaders(input.responseHeaders, sourceRule),
|
|
69
|
+
...buildResponseBody(input.responseBody, sourceRule),
|
|
70
|
+
...(typeof input.error === 'string' && input.error !== '' ? { error: input.error } : {}),
|
|
71
|
+
duration: input.duration,
|
|
72
|
+
hostname: TraceContext.getHostname(),
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
const isWatcherEnabled = (value) => {
|
|
76
|
+
if (value === false)
|
|
77
|
+
return false;
|
|
78
|
+
if (isObjectValue(value) && value.enabled === false)
|
|
79
|
+
return false;
|
|
80
|
+
return true;
|
|
81
|
+
};
|
|
82
|
+
const emit = ({ source, method, url, requestHeaders, responseStatus, duration, requestBody, responseHeaders, responseBody, error, }) => {
|
|
11
83
|
if (!_storage)
|
|
12
84
|
return;
|
|
13
85
|
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes))
|
|
14
86
|
return;
|
|
87
|
+
const normalizedSource = resolveSource(source);
|
|
88
|
+
const sourceRule = resolveSourceRule(normalizedSource);
|
|
89
|
+
if (sourceRule?.enabled === false)
|
|
90
|
+
return;
|
|
15
91
|
const tags = AuthTag.append([method.toUpperCase()]);
|
|
16
92
|
if ((responseStatus ?? 0) >= 400 || error)
|
|
17
93
|
tags.push('failed');
|
|
18
|
-
|
|
19
|
-
|
|
94
|
+
if (normalizedSource !== undefined)
|
|
95
|
+
tags.push(normalizedSource);
|
|
96
|
+
const content = buildClientRequestContent({
|
|
97
|
+
source,
|
|
98
|
+
method,
|
|
20
99
|
url,
|
|
21
|
-
requestHeaders
|
|
22
|
-
|
|
23
|
-
? {}
|
|
24
|
-
: { requestBody: redactUnknown(requestBody, _redactBodyFields) }),
|
|
25
|
-
...(responseStatus === undefined ? {} : { responseStatus }),
|
|
26
|
-
...(responseHeaders === undefined
|
|
27
|
-
? {}
|
|
28
|
-
: { responseHeaders: redactHeaders(responseHeaders, _redactHeaderNames) }),
|
|
29
|
-
...(responseBody === undefined
|
|
30
|
-
? {}
|
|
31
|
-
: { responseBody: redactUnknown(responseBody, _redactBodyFields) }),
|
|
32
|
-
...(typeof error === 'string' && error !== '' ? { error } : {}),
|
|
100
|
+
requestHeaders,
|
|
101
|
+
responseStatus,
|
|
33
102
|
duration,
|
|
34
|
-
|
|
35
|
-
|
|
103
|
+
requestBody,
|
|
104
|
+
responseHeaders,
|
|
105
|
+
responseBody,
|
|
106
|
+
error,
|
|
107
|
+
}, sourceRule, normalizedSource);
|
|
36
108
|
_storage
|
|
37
109
|
.writeEntry({
|
|
38
110
|
uuid: crypto.randomUUID(),
|
|
@@ -48,14 +120,19 @@ const emit = ({ method, url, requestHeaders, responseStatus, duration, requestBo
|
|
|
48
120
|
export const HttpClientWatcher = Object.freeze({
|
|
49
121
|
emit,
|
|
50
122
|
register({ storage, config }) {
|
|
51
|
-
if (config.watchers.clientRequest
|
|
123
|
+
if (!isWatcherEnabled(config.watchers.clientRequest))
|
|
52
124
|
return () => undefined;
|
|
53
125
|
_storage = storage;
|
|
126
|
+
_clientRequestWatcher =
|
|
127
|
+
typeof config.watchers.clientRequest === 'object' && config.watchers.clientRequest !== null
|
|
128
|
+
? config.watchers.clientRequest
|
|
129
|
+
: undefined;
|
|
54
130
|
_redactHeaderNames = [...(config.redaction?.keys ?? []), ...(config.redaction?.headers ?? [])];
|
|
55
131
|
_redactBodyFields = [...(config.redaction?.keys ?? []), ...(config.redaction?.body ?? [])];
|
|
56
132
|
_ignoreRoutes = config.ignoreRoutes;
|
|
57
133
|
return () => {
|
|
58
134
|
_storage = null;
|
|
135
|
+
_clientRequestWatcher = undefined;
|
|
59
136
|
_redactBodyFields = [];
|
|
60
137
|
_ignoreRoutes = [];
|
|
61
138
|
};
|
|
@@ -17,6 +17,12 @@ const normalizeHeaders = (headers) => {
|
|
|
17
17
|
const normalizeHeaderValue = (value) => {
|
|
18
18
|
return Array.isArray(value) ? value.join(', ') : value;
|
|
19
19
|
};
|
|
20
|
+
const resolveRouteMiddleware = (req) => {
|
|
21
|
+
const middleware = req.context?.['traceRouteMiddleware'];
|
|
22
|
+
return Array.isArray(middleware)
|
|
23
|
+
? middleware.filter((value) => typeof value === 'string')
|
|
24
|
+
: [];
|
|
25
|
+
};
|
|
20
26
|
const resolveRequestPayload = (req, config) => {
|
|
21
27
|
const redactFields = [...config.redaction.keys, ...config.redaction.body];
|
|
22
28
|
const requestBody = typeof req.getBody === 'function' ? req.getBody() : req.body;
|
|
@@ -108,7 +114,7 @@ const buildEntry = (req, res, start, config, responseCapture) => {
|
|
|
108
114
|
responseBody: responseCapture.body,
|
|
109
115
|
duration: Date.now() - start,
|
|
110
116
|
memory: TraceContext.getMemory(),
|
|
111
|
-
middleware:
|
|
117
|
+
middleware: resolveRouteMiddleware(req),
|
|
112
118
|
hostname: TraceContext.getHostname(),
|
|
113
119
|
userId: TraceContext.getUserId(),
|
|
114
120
|
};
|
|
@@ -33,7 +33,12 @@ export const MiddlewareWatcher = Object.freeze({
|
|
|
33
33
|
return () => undefined;
|
|
34
34
|
_storage = storage;
|
|
35
35
|
_ignoreRoutes = config.ignoreRoutes;
|
|
36
|
+
globalThis.__zintrust_trace_middleware_emit__ = emit;
|
|
36
37
|
return () => {
|
|
38
|
+
const globalState = globalThis;
|
|
39
|
+
if (globalState.__zintrust_trace_middleware_emit__ === emit) {
|
|
40
|
+
delete globalState.__zintrust_trace_middleware_emit__;
|
|
41
|
+
}
|
|
37
42
|
_storage = null;
|
|
38
43
|
_ignoreRoutes = [];
|
|
39
44
|
};
|
|
@@ -34,7 +34,12 @@ export const ModelWatcher = Object.freeze({
|
|
|
34
34
|
return () => undefined;
|
|
35
35
|
_storage = storage;
|
|
36
36
|
_ignoreRoutes = config.ignoreRoutes;
|
|
37
|
+
globalThis.__zintrust_trace_model_emit__ = emit;
|
|
37
38
|
return () => {
|
|
39
|
+
const globalState = globalThis;
|
|
40
|
+
if (globalState.__zintrust_trace_model_emit__ === emit) {
|
|
41
|
+
delete globalState.__zintrust_trace_model_emit__;
|
|
42
|
+
}
|
|
38
43
|
_storage = null;
|
|
39
44
|
_ignoreRoutes = [];
|
|
40
45
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/trace",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.96",
|
|
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.95"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
package/src/config.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type {
|
|
5
5
|
ITraceConfig,
|
|
6
|
+
TraceClientRequestCaptureRule,
|
|
7
|
+
TraceClientRequestWatcherToggle,
|
|
6
8
|
TraceConfigOverrides,
|
|
7
9
|
TraceFilterRule,
|
|
8
10
|
TraceRequestWatcherConfig,
|
|
@@ -25,19 +27,44 @@ const isObjectValue = (value: unknown): value is Record<string, unknown> => {
|
|
|
25
27
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
26
28
|
};
|
|
27
29
|
|
|
30
|
+
const resolveEnabled = (
|
|
31
|
+
base?: TraceFilterRule,
|
|
32
|
+
override?: TraceFilterRule
|
|
33
|
+
): boolean | undefined => {
|
|
34
|
+
return override?.enabled ?? base?.enabled;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const hasMergedRuleValues = (
|
|
38
|
+
include: string[],
|
|
39
|
+
exclude: string[],
|
|
40
|
+
enabled: boolean | undefined
|
|
41
|
+
): boolean => {
|
|
42
|
+
return include.length > 0 || exclude.length > 0 || enabled !== undefined;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const buildFilterRule = (input: {
|
|
46
|
+
include: string[];
|
|
47
|
+
exclude: string[];
|
|
48
|
+
enabled: boolean | undefined;
|
|
49
|
+
}): TraceFilterRule => {
|
|
50
|
+
return Object.freeze({
|
|
51
|
+
...(input.enabled === undefined ? {} : { enabled: input.enabled }),
|
|
52
|
+
...(input.include.length > 0 ? { include: input.include } : {}),
|
|
53
|
+
...(input.exclude.length > 0 ? { exclude: input.exclude } : {}),
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
28
57
|
const mergeFilterRule = (
|
|
29
58
|
base?: TraceFilterRule,
|
|
30
59
|
override?: TraceFilterRule
|
|
31
60
|
): TraceFilterRule | undefined => {
|
|
32
61
|
const include = mergeStringLists(base?.include ?? [], override?.include);
|
|
33
62
|
const exclude = mergeStringLists(base?.exclude ?? [], override?.exclude);
|
|
63
|
+
const enabled = resolveEnabled(base, override);
|
|
34
64
|
|
|
35
|
-
if (include
|
|
65
|
+
if (!hasMergedRuleValues(include, exclude, enabled)) return undefined;
|
|
36
66
|
|
|
37
|
-
return
|
|
38
|
-
...(include.length > 0 ? { include } : {}),
|
|
39
|
-
...(exclude.length > 0 ? { exclude } : {}),
|
|
40
|
-
});
|
|
67
|
+
return buildFilterRule({ include, exclude, enabled });
|
|
41
68
|
};
|
|
42
69
|
|
|
43
70
|
const mergeWatcherToggle = (
|
|
@@ -51,6 +78,109 @@ const mergeWatcherToggle = (
|
|
|
51
78
|
return mergeFilterRule(baseRule, override);
|
|
52
79
|
};
|
|
53
80
|
|
|
81
|
+
type ClientRequestCaptureFlags = Pick<
|
|
82
|
+
TraceClientRequestCaptureRule,
|
|
83
|
+
'requestHeaders' | 'requestBody' | 'responseHeaders' | 'responseBody'
|
|
84
|
+
>;
|
|
85
|
+
|
|
86
|
+
const resolveClientRequestCaptureFlags = (
|
|
87
|
+
base?: TraceClientRequestCaptureRule,
|
|
88
|
+
override?: TraceClientRequestCaptureRule
|
|
89
|
+
): ClientRequestCaptureFlags => {
|
|
90
|
+
return {
|
|
91
|
+
requestHeaders: override?.requestHeaders ?? base?.requestHeaders,
|
|
92
|
+
requestBody: override?.requestBody ?? base?.requestBody,
|
|
93
|
+
responseHeaders: override?.responseHeaders ?? base?.responseHeaders,
|
|
94
|
+
responseBody: override?.responseBody ?? base?.responseBody,
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const hasClientRequestCaptureFlags = (flags: ClientRequestCaptureFlags): boolean => {
|
|
99
|
+
return Object.values(flags).some((value) => value !== undefined);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const buildClientRequestCaptureRule = (
|
|
103
|
+
mergedRule: TraceFilterRule | undefined,
|
|
104
|
+
flags: ClientRequestCaptureFlags
|
|
105
|
+
): TraceClientRequestCaptureRule => {
|
|
106
|
+
const baseRule = mergedRule ? { ...mergedRule } : {};
|
|
107
|
+
|
|
108
|
+
return Object.freeze({
|
|
109
|
+
...baseRule,
|
|
110
|
+
...(flags.requestHeaders === undefined ? {} : { requestHeaders: flags.requestHeaders }),
|
|
111
|
+
...(flags.requestBody === undefined ? {} : { requestBody: flags.requestBody }),
|
|
112
|
+
...(flags.responseHeaders === undefined ? {} : { responseHeaders: flags.responseHeaders }),
|
|
113
|
+
...(flags.responseBody === undefined ? {} : { responseBody: flags.responseBody }),
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const mergeClientRequestCaptureRule = (
|
|
118
|
+
base?: TraceClientRequestCaptureRule,
|
|
119
|
+
override?: TraceClientRequestCaptureRule
|
|
120
|
+
): TraceClientRequestCaptureRule | undefined => {
|
|
121
|
+
const mergedRule = mergeFilterRule(base, override);
|
|
122
|
+
const flags = resolveClientRequestCaptureFlags(base, override);
|
|
123
|
+
|
|
124
|
+
if (mergedRule === undefined && !hasClientRequestCaptureFlags(flags)) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return buildClientRequestCaptureRule(mergedRule, flags);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const collectClientRequestSourceKeys = (
|
|
132
|
+
base?: TraceClientRequestWatcherToggle,
|
|
133
|
+
override?: Exclude<TraceClientRequestWatcherToggle, boolean>
|
|
134
|
+
): string[] => {
|
|
135
|
+
const overrideSources = override?.sources ?? {};
|
|
136
|
+
const sourceKeys = new Set<string>([
|
|
137
|
+
...Object.keys(isObjectValue(base) ? base.sources ?? {} : {}),
|
|
138
|
+
...Object.keys(overrideSources),
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
return [...sourceKeys];
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const mergeClientRequestSources = (
|
|
145
|
+
base?: TraceClientRequestWatcherToggle,
|
|
146
|
+
override?: Exclude<TraceClientRequestWatcherToggle, boolean>
|
|
147
|
+
): Record<string, TraceClientRequestCaptureRule> | undefined => {
|
|
148
|
+
if (override === undefined) return undefined;
|
|
149
|
+
|
|
150
|
+
const sources: Record<string, TraceClientRequestCaptureRule> = {};
|
|
151
|
+
|
|
152
|
+
for (const key of collectClientRequestSourceKeys(base, override)) {
|
|
153
|
+
const baseSources = isObjectValue(base) ? base.sources : undefined;
|
|
154
|
+
const sourceRule = mergeClientRequestCaptureRule(baseSources?.[key], override.sources?.[key]);
|
|
155
|
+
if (sourceRule !== undefined) {
|
|
156
|
+
sources[key] = sourceRule;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return Object.keys(sources).length === 0 ? undefined : sources;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const mergeClientRequestWatcherToggle = (
|
|
164
|
+
base?: TraceClientRequestWatcherToggle,
|
|
165
|
+
override?: TraceClientRequestWatcherToggle
|
|
166
|
+
): TraceClientRequestWatcherToggle | undefined => {
|
|
167
|
+
if (override === undefined) return base;
|
|
168
|
+
if (override === false || override === true) return override;
|
|
169
|
+
|
|
170
|
+
const baseConfig = isObjectValue(base) ? base : undefined;
|
|
171
|
+
const merged = mergeClientRequestCaptureRule(baseConfig, override) ?? {};
|
|
172
|
+
const sources = mergeClientRequestSources(base, override);
|
|
173
|
+
|
|
174
|
+
if (sources === undefined) {
|
|
175
|
+
return merged;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return Object.freeze({
|
|
179
|
+
...merged,
|
|
180
|
+
sources,
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
|
|
54
184
|
const REQUEST_METHOD_KEYS = ['all', 'get', 'post', 'put', 'patch', 'delete'] as const;
|
|
55
185
|
|
|
56
186
|
const mergeRequestWatcherToggle = (
|
|
@@ -99,7 +229,7 @@ const mergeWatchers = (
|
|
|
99
229
|
batch: mergeWatcherToggle(base.batch, override.batch),
|
|
100
230
|
dump: mergeWatcherToggle(base.dump, override.dump),
|
|
101
231
|
view: mergeWatcherToggle(base.view, override.view),
|
|
102
|
-
clientRequest:
|
|
232
|
+
clientRequest: mergeClientRequestWatcherToggle(base.clientRequest, override.clientRequest),
|
|
103
233
|
};
|
|
104
234
|
};
|
|
105
235
|
|