@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,190 +0,0 @@
|
|
|
1
|
-
import { ErrorFactory, RemoteSignedJson } from '@zintrust/core';
|
|
2
|
-
import type { ITraceEntry, ITraceStorage } from '../types';
|
|
3
|
-
|
|
4
|
-
type ProxyTraceStorageSettings = {
|
|
5
|
-
baseUrl: string;
|
|
6
|
-
path: string;
|
|
7
|
-
keyId: string;
|
|
8
|
-
secret: string;
|
|
9
|
-
timeoutMs: number;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
type TraceProxyWriteRequest = {
|
|
13
|
-
entry: ITraceEntry;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
type TraceProxyUpdateRequest = {
|
|
17
|
-
uuid: string;
|
|
18
|
-
patch: Partial<Pick<ITraceEntry, 'content' | 'isLatest'>>;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
type TraceProxyMarkFamilyStaleRequest = {
|
|
22
|
-
familyHash: string;
|
|
23
|
-
exceptUuid: string;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const ensureConfigured = (settings: ProxyTraceStorageSettings): void => {
|
|
27
|
-
if (settings.baseUrl.trim() === '') {
|
|
28
|
-
throw ErrorFactory.createConfigError('TRACE_PROXY_URL is required when TRACE_PROXY=true');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (settings.keyId.trim() === '' || settings.secret.trim() === '') {
|
|
32
|
-
throw ErrorFactory.createConfigError(
|
|
33
|
-
'TRACE_PROXY signing credentials are required when TRACE_PROXY=true'
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const normalizePath = (value: string): string => {
|
|
39
|
-
const trimmed = value.trim();
|
|
40
|
-
if (trimmed === '') return '/zin/trace/write';
|
|
41
|
-
return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const trimTrailingSlashes = (value: string): string => {
|
|
45
|
-
let trimmed = value;
|
|
46
|
-
while (trimmed.endsWith('/')) {
|
|
47
|
-
trimmed = trimmed.slice(0, -1);
|
|
48
|
-
}
|
|
49
|
-
return trimmed;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const createUnsupportedReadError = (): Error =>
|
|
53
|
-
ErrorFactory.createConfigError(
|
|
54
|
-
'Trace proxy sender storage does not expose dashboard/query operations. Use the trace server for reads.'
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
type ProxyRequestSettings = {
|
|
58
|
-
baseUrl: string;
|
|
59
|
-
keyId: string;
|
|
60
|
-
secret: string;
|
|
61
|
-
timeoutMs: number;
|
|
62
|
-
signaturePathPrefixToStrip: string;
|
|
63
|
-
missingUrlMessage: string;
|
|
64
|
-
missingCredentialsMessage: string;
|
|
65
|
-
messages: {
|
|
66
|
-
unauthorized: string;
|
|
67
|
-
forbidden: string;
|
|
68
|
-
rateLimited: string;
|
|
69
|
-
rejected: string;
|
|
70
|
-
error: string;
|
|
71
|
-
timedOut: string;
|
|
72
|
-
};
|
|
73
|
-
normalizedPath: string;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const buildSettings = (settings: ProxyTraceStorageSettings): ProxyRequestSettings => {
|
|
77
|
-
ensureConfigured(settings);
|
|
78
|
-
const normalizedPath = normalizePath(settings.path);
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
baseUrl: settings.baseUrl,
|
|
82
|
-
keyId: settings.keyId,
|
|
83
|
-
secret: settings.secret,
|
|
84
|
-
timeoutMs: settings.timeoutMs,
|
|
85
|
-
signaturePathPrefixToStrip: new URL(settings.baseUrl).pathname,
|
|
86
|
-
missingUrlMessage: 'TRACE_PROXY_URL is required when TRACE_PROXY=true',
|
|
87
|
-
missingCredentialsMessage: 'TRACE_PROXY signing credentials are required when TRACE_PROXY=true',
|
|
88
|
-
messages: {
|
|
89
|
-
unauthorized: 'Trace proxy rejected the request credentials',
|
|
90
|
-
forbidden: 'Trace proxy rejected the request signature',
|
|
91
|
-
rateLimited: 'Trace proxy rate-limited the request',
|
|
92
|
-
rejected: 'Trace proxy rejected the request payload',
|
|
93
|
-
error: 'Trace proxy request failed',
|
|
94
|
-
timedOut: 'Trace proxy request timed out',
|
|
95
|
-
},
|
|
96
|
-
normalizedPath,
|
|
97
|
-
};
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const appendSuffix = (path: string, suffix: string): string => {
|
|
101
|
-
const base = trimTrailingSlashes(normalizePath(path));
|
|
102
|
-
const tail = suffix.startsWith('/') ? suffix : `/${suffix}`;
|
|
103
|
-
return `${base}${tail}`;
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const unsupportedQueryEntries: ITraceStorage['queryEntries'] = async () => {
|
|
107
|
-
throw createUnsupportedReadError();
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const unsupportedGetEntry: ITraceStorage['getEntry'] = async () => {
|
|
111
|
-
throw createUnsupportedReadError();
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const unsupportedGetBatch: ITraceStorage['getBatch'] = async () => {
|
|
115
|
-
throw createUnsupportedReadError();
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const unsupportedQueryBatchEntries: ITraceStorage['queryBatchEntries'] = async () => {
|
|
119
|
-
throw createUnsupportedReadError();
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const unsupportedPrune: ITraceStorage['prune'] = async () => {
|
|
123
|
-
throw createUnsupportedReadError();
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const unsupportedClear: ITraceStorage['clear'] = async () => {
|
|
127
|
-
throw createUnsupportedReadError();
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const unsupportedGetMonitoring: ITraceStorage['getMonitoring'] = async () => {
|
|
131
|
-
throw createUnsupportedReadError();
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const unsupportedAddMonitoring: ITraceStorage['addMonitoring'] = async () => {
|
|
135
|
-
throw createUnsupportedReadError();
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const unsupportedRemoveMonitoring: ITraceStorage['removeMonitoring'] = async () => {
|
|
139
|
-
throw createUnsupportedReadError();
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const unsupportedStats: ITraceStorage['stats'] = async () => {
|
|
143
|
-
throw createUnsupportedReadError();
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
export const ProxyTraceStorage = Object.freeze({
|
|
147
|
-
create(settings: ProxyTraceStorageSettings): ITraceStorage {
|
|
148
|
-
const normalized = buildSettings(settings);
|
|
149
|
-
|
|
150
|
-
return Object.freeze({
|
|
151
|
-
async writeEntry(entry: ITraceEntry): Promise<void> {
|
|
152
|
-
await RemoteSignedJson.request<{ ok: true }>(normalized, normalized.normalizedPath, {
|
|
153
|
-
entry,
|
|
154
|
-
} satisfies TraceProxyWriteRequest);
|
|
155
|
-
},
|
|
156
|
-
|
|
157
|
-
async updateEntry(
|
|
158
|
-
uuid: string,
|
|
159
|
-
patch: Partial<Pick<ITraceEntry, 'content' | 'isLatest'>>
|
|
160
|
-
): Promise<void> {
|
|
161
|
-
await RemoteSignedJson.request<{ ok: true }>(
|
|
162
|
-
normalized,
|
|
163
|
-
appendSuffix(normalized.normalizedPath, '/update'),
|
|
164
|
-
{ uuid, patch } satisfies TraceProxyUpdateRequest
|
|
165
|
-
);
|
|
166
|
-
},
|
|
167
|
-
|
|
168
|
-
async markFamilyStale(familyHash: string, exceptUuid: string): Promise<void> {
|
|
169
|
-
await RemoteSignedJson.request<{ ok: true }>(
|
|
170
|
-
normalized,
|
|
171
|
-
appendSuffix(normalized.normalizedPath, '/mark-family-stale'),
|
|
172
|
-
{ familyHash, exceptUuid } satisfies TraceProxyMarkFamilyStaleRequest
|
|
173
|
-
);
|
|
174
|
-
},
|
|
175
|
-
|
|
176
|
-
queryEntries: unsupportedQueryEntries,
|
|
177
|
-
getEntry: unsupportedGetEntry,
|
|
178
|
-
getBatch: unsupportedGetBatch,
|
|
179
|
-
queryBatchEntries: unsupportedQueryBatchEntries,
|
|
180
|
-
prune: unsupportedPrune,
|
|
181
|
-
clear: unsupportedClear,
|
|
182
|
-
getMonitoring: unsupportedGetMonitoring,
|
|
183
|
-
addMonitoring: unsupportedAddMonitoring,
|
|
184
|
-
removeMonitoring: unsupportedRemoveMonitoring,
|
|
185
|
-
stats: unsupportedStats,
|
|
186
|
-
});
|
|
187
|
-
},
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
export default ProxyTraceStorage;
|
|
@@ -1,493 +0,0 @@
|
|
|
1
|
-
import type { ITraceConfig, ITraceEntry, ITraceStorage } from '../types';
|
|
2
|
-
|
|
3
|
-
const DEFAULT_MAX_ENTRY_BYTES = 64 * 1024;
|
|
4
|
-
const DEFAULT_MAX_STRING_BYTES = 16 * 1024;
|
|
5
|
-
const DEFAULT_MAX_ARRAY_ITEMS = 25;
|
|
6
|
-
const DEFAULT_MAX_OBJECT_ENTRIES = 40;
|
|
7
|
-
const DEFAULT_MAX_DEPTH = 6;
|
|
8
|
-
|
|
9
|
-
const DROPPED_FIELD_MESSAGE =
|
|
10
|
-
'[trace] Value dropped because the field exceeded the trace storage size limit.';
|
|
11
|
-
const COMPACTED_CONTENT_MESSAGE =
|
|
12
|
-
'[trace] Trace content was compacted because it exceeded the trace storage size limit.';
|
|
13
|
-
const REPLACED_CONTENT_MESSAGE = 'Trace content exceeded budget and was replaced.';
|
|
14
|
-
|
|
15
|
-
const encoder = new TextEncoder();
|
|
16
|
-
|
|
17
|
-
const serializedSize = (value: unknown): number => {
|
|
18
|
-
try {
|
|
19
|
-
return encoder.encode(JSON.stringify(value)).length;
|
|
20
|
-
} catch {
|
|
21
|
-
return Number.MAX_SAFE_INTEGER;
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const describeValueType = (value: unknown): string => {
|
|
26
|
-
if (Array.isArray(value)) return 'array';
|
|
27
|
-
if (value === null) return 'null';
|
|
28
|
-
return typeof value;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const compactValue = (value: unknown, depth: number): unknown => {
|
|
32
|
-
if (depth >= DEFAULT_MAX_DEPTH) {
|
|
33
|
-
return DROPPED_FIELD_MESSAGE;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (typeof value === 'string') {
|
|
37
|
-
return serializedSize(value) > DEFAULT_MAX_STRING_BYTES ? DROPPED_FIELD_MESSAGE : value;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (Array.isArray(value)) {
|
|
41
|
-
const next = value
|
|
42
|
-
.slice(0, DEFAULT_MAX_ARRAY_ITEMS)
|
|
43
|
-
.map((item) => compactValue(item, depth + 1));
|
|
44
|
-
|
|
45
|
-
if (value.length > DEFAULT_MAX_ARRAY_ITEMS) {
|
|
46
|
-
next.push(
|
|
47
|
-
`[trace] ${String(value.length - DEFAULT_MAX_ARRAY_ITEMS)} additional items were dropped.`
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return next;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (typeof value !== 'object' || value === null) {
|
|
55
|
-
return value;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const entries = Object.entries(value);
|
|
59
|
-
const compactedEntries = entries
|
|
60
|
-
.slice(0, DEFAULT_MAX_OBJECT_ENTRIES)
|
|
61
|
-
.map(([key, entryValue]) => [key, compactValue(entryValue, depth + 1)]);
|
|
62
|
-
|
|
63
|
-
if (entries.length > DEFAULT_MAX_OBJECT_ENTRIES) {
|
|
64
|
-
compactedEntries.push([
|
|
65
|
-
'__traceNotice',
|
|
66
|
-
`[trace] ${String(entries.length - DEFAULT_MAX_OBJECT_ENTRIES)} additional fields were dropped.`,
|
|
67
|
-
]);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return Object.fromEntries(compactedEntries);
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const compactStructuredValueToBudget = (value: unknown): unknown => {
|
|
74
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
75
|
-
return value;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const compacted: Record<string, unknown> = {
|
|
79
|
-
...(value as Record<string, unknown>),
|
|
80
|
-
__traceNotice: COMPACTED_CONTENT_MESSAGE,
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const topLevelCandidates = Object.entries(compacted)
|
|
84
|
-
.filter(([key]) => key !== '__traceNotice')
|
|
85
|
-
.map(([key, entryValue]) => ({ key, size: serializedSize(entryValue) }))
|
|
86
|
-
.sort((left, right) => right.size - left.size);
|
|
87
|
-
|
|
88
|
-
let droppedCount = 0;
|
|
89
|
-
|
|
90
|
-
for (const candidate of topLevelCandidates) {
|
|
91
|
-
if (serializedSize(compacted) <= DEFAULT_MAX_ENTRY_BYTES) {
|
|
92
|
-
break;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
compacted[candidate.key] = DROPPED_FIELD_MESSAGE;
|
|
96
|
-
droppedCount += 1;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (droppedCount > 0) {
|
|
100
|
-
compacted['__traceNotice'] =
|
|
101
|
-
`${COMPACTED_CONTENT_MESSAGE} ${String(droppedCount)} top-level field(s) were dropped.`;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return compacted;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
const fitContentToBudget = (content: unknown): unknown => {
|
|
108
|
-
if (serializedSize(content) <= DEFAULT_MAX_ENTRY_BYTES) {
|
|
109
|
-
return content;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const compacted = compactValue(content, 0);
|
|
113
|
-
if (serializedSize(compacted) <= DEFAULT_MAX_ENTRY_BYTES) {
|
|
114
|
-
return compacted;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (typeof compacted === 'object' && compacted !== null) {
|
|
118
|
-
const budgetCompacted = compactStructuredValueToBudget(compacted);
|
|
119
|
-
if (serializedSize(budgetCompacted) <= DEFAULT_MAX_ENTRY_BYTES) {
|
|
120
|
-
return budgetCompacted;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
__traceNotice: COMPACTED_CONTENT_MESSAGE,
|
|
126
|
-
dropped: true,
|
|
127
|
-
valueType: describeValueType(content),
|
|
128
|
-
};
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const fitEntryToBudget = (entry: ITraceEntry): ITraceEntry => ({
|
|
132
|
-
...entry,
|
|
133
|
-
content: fitContentToBudget(entry.content),
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
const fitPatchToBudget = (
|
|
137
|
-
patch: Partial<Pick<ITraceEntry, 'content' | 'isLatest'>>
|
|
138
|
-
): Partial<Pick<ITraceEntry, 'content' | 'isLatest'>> => {
|
|
139
|
-
if (patch.content === undefined) return patch;
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
...patch,
|
|
143
|
-
content: fitContentToBudget(patch.content),
|
|
144
|
-
};
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
type TraceDispatchMessage =
|
|
148
|
-
| { operation: 'write'; entry: ITraceEntry }
|
|
149
|
-
| {
|
|
150
|
-
operation: 'update';
|
|
151
|
-
uuid: string;
|
|
152
|
-
patch: Partial<Pick<ITraceEntry, 'content' | 'isLatest'>>;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
type QueueApi = {
|
|
156
|
-
get(name?: string): {
|
|
157
|
-
enqueue<T = unknown>(queue: string, payload: T): Promise<string>;
|
|
158
|
-
};
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
type TimeoutManagerApi = {
|
|
162
|
-
withTimeout<T>(
|
|
163
|
-
operation: () => Promise<T>,
|
|
164
|
-
timeoutMs: number,
|
|
165
|
-
operationName: string,
|
|
166
|
-
timeoutHandler?: () => Promise<T>
|
|
167
|
-
): Promise<T>;
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
type QueueWorkerApi = {
|
|
171
|
-
createQueueWorker<TPayload>(options: {
|
|
172
|
-
kindLabel: string;
|
|
173
|
-
defaultQueueName: string;
|
|
174
|
-
maxAttempts: number;
|
|
175
|
-
getLogFields?: (payload: {
|
|
176
|
-
id: string;
|
|
177
|
-
payload: TPayload;
|
|
178
|
-
attempts: number;
|
|
179
|
-
}) => Record<string, unknown>;
|
|
180
|
-
handle(payload: TPayload): Promise<void>;
|
|
181
|
-
}): {
|
|
182
|
-
runOnce(options?: {
|
|
183
|
-
queueName?: string;
|
|
184
|
-
driverName?: string;
|
|
185
|
-
maxItems?: number;
|
|
186
|
-
maxDurationMs?: number;
|
|
187
|
-
concurrency?: number;
|
|
188
|
-
}): Promise<number>;
|
|
189
|
-
};
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
type UnrefableTimer = ReturnType<typeof setInterval> & { unref?: () => void };
|
|
193
|
-
|
|
194
|
-
type TraceContentBudgetRuntime = {
|
|
195
|
-
queue?: QueueApi | null;
|
|
196
|
-
timeoutManager?: TimeoutManagerApi | null;
|
|
197
|
-
queueWorkerApi?: QueueWorkerApi | null;
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
const startedWorkerKeys = new Set<string>();
|
|
201
|
-
|
|
202
|
-
const closePort = (port: MessagePort): void => {
|
|
203
|
-
if (typeof port.close === 'function') {
|
|
204
|
-
port.close();
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const scheduleTask = async (task: () => Promise<void>): Promise<void> => {
|
|
209
|
-
return await new Promise<void>((resolve, reject) => {
|
|
210
|
-
const runTask = (): void => {
|
|
211
|
-
void task().then(resolve).catch(reject);
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
if (typeof MessageChannel === 'function') {
|
|
215
|
-
const channel = new MessageChannel();
|
|
216
|
-
|
|
217
|
-
channel.port1.onmessage = (): void => {
|
|
218
|
-
channel.port1.onmessage = null;
|
|
219
|
-
closePort(channel.port1);
|
|
220
|
-
closePort(channel.port2);
|
|
221
|
-
runTask();
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
channel.port2.postMessage(undefined);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
Promise.resolve().then(runTask).catch(reject);
|
|
229
|
-
});
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const getReplacementContent = (content: unknown): Record<string, unknown> => {
|
|
233
|
-
return {
|
|
234
|
-
__traceNotice: REPLACED_CONTENT_MESSAGE,
|
|
235
|
-
dropped: true,
|
|
236
|
-
valueType: describeValueType(content),
|
|
237
|
-
};
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
const replaceEntryContent = (entry: ITraceEntry): ITraceEntry => ({
|
|
241
|
-
...entry,
|
|
242
|
-
content: getReplacementContent(entry.content),
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
const replacePatchContent = (
|
|
246
|
-
patch: Partial<Pick<ITraceEntry, 'content' | 'isLatest'>>
|
|
247
|
-
): Partial<Pick<ITraceEntry, 'content' | 'isLatest'>> => {
|
|
248
|
-
if (patch.content === undefined) return patch;
|
|
249
|
-
|
|
250
|
-
return {
|
|
251
|
-
...patch,
|
|
252
|
-
content: getReplacementContent(patch.content),
|
|
253
|
-
};
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
const shouldReplaceContent = (content: unknown): boolean => {
|
|
257
|
-
return serializedSize(content) > DEFAULT_MAX_ENTRY_BYTES;
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
const hasQueueDispatch = (config: ITraceConfig): boolean => {
|
|
261
|
-
const driver = config.contentDispatch.driver?.trim();
|
|
262
|
-
return typeof driver === 'string' && driver !== '';
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
const WORKERS_PACKAGE_SPECIFIER = '@zintrust/workers';
|
|
266
|
-
|
|
267
|
-
const getCoreRuntime = async (): Promise<{
|
|
268
|
-
Queue: QueueApi | null;
|
|
269
|
-
TimeoutManager: TimeoutManagerApi | null;
|
|
270
|
-
}> => {
|
|
271
|
-
try {
|
|
272
|
-
const mod = (await import('@zintrust/core')) as unknown as {
|
|
273
|
-
Queue?: QueueApi;
|
|
274
|
-
TimeoutManager?: TimeoutManagerApi;
|
|
275
|
-
};
|
|
276
|
-
return {
|
|
277
|
-
Queue: mod.Queue ?? null,
|
|
278
|
-
TimeoutManager: mod.TimeoutManager ?? null,
|
|
279
|
-
};
|
|
280
|
-
} catch {
|
|
281
|
-
return {
|
|
282
|
-
Queue: null,
|
|
283
|
-
TimeoutManager: null,
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
const getQueueWorkerApi = async (): Promise<QueueWorkerApi | null> => {
|
|
289
|
-
try {
|
|
290
|
-
// @ts-ignore
|
|
291
|
-
const mod = (await import(WORKERS_PACKAGE_SPECIFIER)) as unknown as QueueWorkerApi;
|
|
292
|
-
return typeof mod.createQueueWorker === 'function' ? mod : null;
|
|
293
|
-
} catch {
|
|
294
|
-
return null;
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
const enqueueTraceDispatch = async (
|
|
299
|
-
config: ITraceConfig,
|
|
300
|
-
payload: TraceDispatchMessage,
|
|
301
|
-
runtime?: TraceContentBudgetRuntime
|
|
302
|
-
): Promise<boolean> => {
|
|
303
|
-
const driverName = config.contentDispatch.driver?.trim();
|
|
304
|
-
if (driverName === undefined || driverName === '') return false;
|
|
305
|
-
|
|
306
|
-
const coreRuntime =
|
|
307
|
-
runtime?.queue !== undefined || runtime?.timeoutManager !== undefined
|
|
308
|
-
? {
|
|
309
|
-
Queue: runtime?.queue ?? null,
|
|
310
|
-
TimeoutManager: runtime?.timeoutManager ?? null,
|
|
311
|
-
}
|
|
312
|
-
: await getCoreRuntime();
|
|
313
|
-
const queueApi = coreRuntime.Queue;
|
|
314
|
-
if (queueApi === null) return false;
|
|
315
|
-
|
|
316
|
-
try {
|
|
317
|
-
const driver = queueApi.get(driverName);
|
|
318
|
-
const timeoutMs = Math.max(1, config.contentDispatch.enqueueTimeoutMs);
|
|
319
|
-
if (coreRuntime.TimeoutManager === null) {
|
|
320
|
-
await driver.enqueue(config.contentDispatch.queueName, payload);
|
|
321
|
-
} else {
|
|
322
|
-
await coreRuntime.TimeoutManager.withTimeout(
|
|
323
|
-
() => driver.enqueue(config.contentDispatch.queueName, payload),
|
|
324
|
-
timeoutMs,
|
|
325
|
-
'trace-content-dispatch-enqueue'
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return true;
|
|
330
|
-
} catch {
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
const persistWriteFallback = async (storage: ITraceStorage, entry: ITraceEntry): Promise<void> => {
|
|
336
|
-
await storage.writeEntry(
|
|
337
|
-
shouldReplaceContent(entry.content) ? replaceEntryContent(entry) : entry
|
|
338
|
-
);
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
const persistUpdateFallback = async (
|
|
342
|
-
storage: ITraceStorage,
|
|
343
|
-
uuid: string,
|
|
344
|
-
patch: Partial<Pick<ITraceEntry, 'content' | 'isLatest'>>
|
|
345
|
-
): Promise<void> => {
|
|
346
|
-
await storage.updateEntry(
|
|
347
|
-
uuid,
|
|
348
|
-
patch.content !== undefined && shouldReplaceContent(patch.content)
|
|
349
|
-
? replacePatchContent(patch)
|
|
350
|
-
: patch
|
|
351
|
-
);
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
const processQueuedMessage = async (
|
|
355
|
-
storage: ITraceStorage,
|
|
356
|
-
message: TraceDispatchMessage
|
|
357
|
-
): Promise<void> => {
|
|
358
|
-
if (message.operation === 'write') {
|
|
359
|
-
await storage.writeEntry(fitEntryToBudget(message.entry));
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
await storage.updateEntry(message.uuid, fitPatchToBudget(message.patch));
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
const ensureWorkerTimer = (_key: string, timer: ReturnType<typeof setInterval>): void => {
|
|
367
|
-
const unrefable = timer as UnrefableTimer;
|
|
368
|
-
if (typeof unrefable.unref === 'function') {
|
|
369
|
-
unrefable.unref();
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
const startInternalDispatchWorker = (
|
|
374
|
-
storage: ITraceStorage,
|
|
375
|
-
config: ITraceConfig,
|
|
376
|
-
runtime?: TraceContentBudgetRuntime
|
|
377
|
-
): void => {
|
|
378
|
-
if (!hasQueueDispatch(config) || config.contentDispatch.worker.enabled !== true) return;
|
|
379
|
-
|
|
380
|
-
const driverName = config.contentDispatch.driver?.trim() ?? '';
|
|
381
|
-
const key = `${driverName}:${config.contentDispatch.queueName}`;
|
|
382
|
-
if (startedWorkerKeys.has(key)) return;
|
|
383
|
-
startedWorkerKeys.add(key);
|
|
384
|
-
|
|
385
|
-
void scheduleTask(async () => {
|
|
386
|
-
const workersApi = runtime?.queueWorkerApi ?? (await getQueueWorkerApi());
|
|
387
|
-
if (workersApi === null) {
|
|
388
|
-
startedWorkerKeys.delete(key);
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
let running = false;
|
|
393
|
-
const runWorker = async (): Promise<void> => {
|
|
394
|
-
if (running) return;
|
|
395
|
-
running = true;
|
|
396
|
-
try {
|
|
397
|
-
const worker = workersApi.createQueueWorker<TraceDispatchMessage>({
|
|
398
|
-
kindLabel: 'trace-content-dispatch',
|
|
399
|
-
defaultQueueName: config.contentDispatch.queueName,
|
|
400
|
-
maxAttempts: 1,
|
|
401
|
-
getLogFields: () => ({
|
|
402
|
-
queueName: config.contentDispatch.queueName,
|
|
403
|
-
driverName,
|
|
404
|
-
}),
|
|
405
|
-
handle: async (payload) => {
|
|
406
|
-
await processQueuedMessage(storage, payload);
|
|
407
|
-
},
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
await worker.runOnce({
|
|
411
|
-
queueName: config.contentDispatch.queueName,
|
|
412
|
-
driverName,
|
|
413
|
-
maxDurationMs: Math.max(1, config.contentDispatch.worker.maxDurationMs),
|
|
414
|
-
concurrency: Math.max(1, config.contentDispatch.worker.concurrency),
|
|
415
|
-
});
|
|
416
|
-
} finally {
|
|
417
|
-
running = false;
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
await runWorker();
|
|
422
|
-
|
|
423
|
-
const intervalMs = Math.max(100, config.contentDispatch.worker.intervalMs);
|
|
424
|
-
ensureWorkerTimer(
|
|
425
|
-
key,
|
|
426
|
-
setInterval(() => {
|
|
427
|
-
void runWorker();
|
|
428
|
-
}, intervalMs)
|
|
429
|
-
);
|
|
430
|
-
}).catch(() => {
|
|
431
|
-
startedWorkerKeys.delete(key);
|
|
432
|
-
});
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
const dispatchWrite = async (
|
|
436
|
-
storage: ITraceStorage,
|
|
437
|
-
config: ITraceConfig,
|
|
438
|
-
entry: ITraceEntry,
|
|
439
|
-
runtime?: TraceContentBudgetRuntime
|
|
440
|
-
): Promise<void> => {
|
|
441
|
-
await scheduleTask(async () => {
|
|
442
|
-
if (hasQueueDispatch(config)) {
|
|
443
|
-
const enqueued = await enqueueTraceDispatch(config, { operation: 'write', entry }, runtime);
|
|
444
|
-
if (enqueued) return;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
await persistWriteFallback(storage, entry);
|
|
448
|
-
});
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
const dispatchUpdate = async (
|
|
452
|
-
storage: ITraceStorage,
|
|
453
|
-
config: ITraceConfig,
|
|
454
|
-
uuid: string,
|
|
455
|
-
patch: Partial<Pick<ITraceEntry, 'content' | 'isLatest'>>,
|
|
456
|
-
runtime?: TraceContentBudgetRuntime
|
|
457
|
-
): Promise<void> => {
|
|
458
|
-
await scheduleTask(async () => {
|
|
459
|
-
if (hasQueueDispatch(config)) {
|
|
460
|
-
const enqueued = await enqueueTraceDispatch(
|
|
461
|
-
config,
|
|
462
|
-
{ operation: 'update', uuid, patch },
|
|
463
|
-
runtime
|
|
464
|
-
);
|
|
465
|
-
if (enqueued) return;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
await persistUpdateFallback(storage, uuid, patch);
|
|
469
|
-
});
|
|
470
|
-
};
|
|
471
|
-
|
|
472
|
-
export const TraceContentBudget = Object.freeze({
|
|
473
|
-
wrapStorage(
|
|
474
|
-
storage: ITraceStorage,
|
|
475
|
-
config: ITraceConfig,
|
|
476
|
-
runtime?: TraceContentBudgetRuntime
|
|
477
|
-
): ITraceStorage {
|
|
478
|
-
startInternalDispatchWorker(storage, config, runtime);
|
|
479
|
-
|
|
480
|
-
return Object.freeze({
|
|
481
|
-
...storage,
|
|
482
|
-
writeEntry: async (entry: ITraceEntry): Promise<void> => {
|
|
483
|
-
await dispatchWrite(storage, config, entry, runtime);
|
|
484
|
-
},
|
|
485
|
-
updateEntry: async (
|
|
486
|
-
uuid: string,
|
|
487
|
-
patch: Partial<Pick<ITraceEntry, 'content' | 'isLatest'>>
|
|
488
|
-
): Promise<void> => {
|
|
489
|
-
await dispatchUpdate(storage, config, uuid, patch, runtime);
|
|
490
|
-
},
|
|
491
|
-
});
|
|
492
|
-
},
|
|
493
|
-
});
|