@reproapp/node-sdk 0.0.9 → 0.0.12
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/index.d.ts +21 -1
- package/dist/index.js +248 -20
- package/package.json +2 -2
- package/src/index.ts +293 -31
- package/test/kafka-runtime-privacy-policy.test.js +61 -22
- package/test/sdk-background-priority.test.js +35 -0
package/dist/index.d.ts
CHANGED
|
@@ -67,9 +67,12 @@ type TraceEventRecord = {
|
|
|
67
67
|
spanId?: string | number | null;
|
|
68
68
|
parentSpanId?: string | number | null;
|
|
69
69
|
args?: any;
|
|
70
|
+
argsValueCapture?: TraceValueCaptureRef;
|
|
70
71
|
returnValue?: any;
|
|
72
|
+
returnValueCapture?: TraceValueCaptureRef;
|
|
71
73
|
threw?: boolean;
|
|
72
74
|
error?: any;
|
|
75
|
+
errorValueCapture?: TraceValueCaptureRef;
|
|
73
76
|
unawaited?: boolean;
|
|
74
77
|
};
|
|
75
78
|
type PendingTraceEventRecord = TraceEventRecord & {
|
|
@@ -82,9 +85,14 @@ type PendingTraceEventRecord = TraceEventRecord & {
|
|
|
82
85
|
__reproSourceFile?: string | null;
|
|
83
86
|
__reproTraceValueEntries?: TraceValueBatchEntry[];
|
|
84
87
|
};
|
|
88
|
+
type TraceValueCaptureRef = {
|
|
89
|
+
status: 'stored' | 'truncated-for-storage' | 'skipped-non-meaningful';
|
|
90
|
+
valueId?: string;
|
|
91
|
+
projectedKind?: string;
|
|
92
|
+
};
|
|
85
93
|
type TraceValueBatchEntry = {
|
|
86
94
|
id: string;
|
|
87
|
-
target: 'db.pk' | 'db.before' | 'db.after' | 'db.query' | 'db.resultMeta' | 'db.error' | 'request.headers' | 'request.body' | 'request.params' | 'request.query' | 'response.body';
|
|
95
|
+
target: 'db.pk' | 'db.before' | 'db.after' | 'db.query' | 'db.resultMeta' | 'db.error' | 'request.headers' | 'request.body' | 'request.params' | 'request.query' | 'response.body' | 'trace.args' | 'trace.returnValue' | 'trace.error';
|
|
88
96
|
value: any;
|
|
89
97
|
truncatedForStorage?: boolean;
|
|
90
98
|
projectedKind?: string;
|
|
@@ -254,6 +262,8 @@ export type ReproTracingInitOptions = TracerInitOpts & {
|
|
|
254
262
|
export declare function initReproTracing(opts?: ReproTracingInitOptions): TracerApi | null;
|
|
255
263
|
/** Optional helper if users want to check it. */
|
|
256
264
|
export declare function isReproTracingEnabled(): boolean;
|
|
265
|
+
declare function sanitizeTraceValueForPrivacy(value: any): any;
|
|
266
|
+
declare function sanitizeMaterializedTraceValue(value: any): any;
|
|
257
267
|
export declare function __materializePendingTraceEventsForWorker(payload: TraceMaterializationWorkerPayload): Promise<PendingTraceEventRecord[]>;
|
|
258
268
|
type NormalizedMaskRule = {
|
|
259
269
|
when?: ReproMaskWhen;
|
|
@@ -283,6 +293,7 @@ type InlinePrivacyMaterializationResult = {
|
|
|
283
293
|
};
|
|
284
294
|
};
|
|
285
295
|
declare function materializeInlinePrivacyValueAsync(target: RuntimePrivacySurface, value: any, cfg: ReproMiddlewareConfig, req: MaskRequestContext, trace: TraceEventForFilter | null, masking: NormalizedMaskingConfig | null, privacy: NormalizedRuntimePrivacyPolicy | null, db?: RuntimePrivacyDbContext | null): Promise<InlinePrivacyMaterializationResult>;
|
|
296
|
+
declare function getEventTraceValueEntries(event: TraceEventRecord | PendingTraceEventRecord): TraceValueBatchEntry[] | undefined;
|
|
286
297
|
declare function estimateTraceBatchSerializedBytes(batch: TraceEventRecord[], requestRid: string, actionId: string | null | undefined, batchIndex: number): number;
|
|
287
298
|
declare function chunkTraceEventsForTransport(events: TraceEventRecord[], requestRid: string, actionId: string | null | undefined): TraceEventRecord[][];
|
|
288
299
|
export type ReproMiddlewareConfig = IngestClientConfig & {
|
|
@@ -344,6 +355,12 @@ export declare function initRepro(cfg: ReproInitConfig): Promise<void>;
|
|
|
344
355
|
/** @internal Test hooks for runtime privacy policy wiring. */
|
|
345
356
|
export declare const __reproTestHooks: {
|
|
346
357
|
shareRuntimePrivacyStateForTest: typeof shareRuntimePrivacyState;
|
|
358
|
+
setFullValueCaptureEnabledForTest(enabled: boolean): void;
|
|
359
|
+
scheduleSdkBackgroundWorkForTest(work: () => Promise<void> | void, options?: {
|
|
360
|
+
priority?: 'high' | 'normal';
|
|
361
|
+
}): void;
|
|
362
|
+
drainSdkBackgroundQueueForTest(): Promise<void>;
|
|
363
|
+
resetSdkBackgroundQueuesForTest(): void;
|
|
347
364
|
getRuntimePrivacyPolicyForTest(cfg: ReproMiddlewareConfig): NormalizedRuntimePrivacyPolicy | null;
|
|
348
365
|
setRuntimePrivacyPolicyForTest(cfg: ReproMiddlewareConfig, policy: NormalizedRuntimePrivacyPolicy | null): void;
|
|
349
366
|
recordKafkaTraceEventAsyncForTest: typeof recordKafkaTraceEventAsync;
|
|
@@ -352,5 +369,8 @@ export declare const __reproTestHooks: {
|
|
|
352
369
|
wrapKafkaEachMessageHandlerForTest: typeof wrapKafkaEachMessageHandler;
|
|
353
370
|
chunkTraceEventsForTransportForTest: typeof chunkTraceEventsForTransport;
|
|
354
371
|
estimateTraceBatchSerializedBytesForTest: typeof estimateTraceBatchSerializedBytes;
|
|
372
|
+
sanitizeTraceValueForPrivacyForTest: typeof sanitizeTraceValueForPrivacy;
|
|
373
|
+
sanitizeMaterializedTraceValueForTest: typeof sanitizeMaterializedTraceValue;
|
|
374
|
+
getEventTraceValueEntriesForTest: typeof getEventTraceValueEntries;
|
|
355
375
|
};
|
|
356
376
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -863,6 +863,7 @@ let activeClientRequestCount = 0;
|
|
|
863
863
|
let oldestActiveClientRequestAt = null;
|
|
864
864
|
let lastClientActivityAt = 0;
|
|
865
865
|
let sdkBackgroundQuietUntil = 0;
|
|
866
|
+
const sdkBackgroundHighQueue = [];
|
|
866
867
|
const sdkBackgroundQueue = [];
|
|
867
868
|
let sdkBackgroundTimer = null;
|
|
868
869
|
let sdkBackgroundDraining = false;
|
|
@@ -1155,8 +1156,13 @@ function endClientRequest() {
|
|
|
1155
1156
|
scheduleSdkBackgroundDrain();
|
|
1156
1157
|
}
|
|
1157
1158
|
}
|
|
1158
|
-
function scheduleSdkBackgroundWork(work) {
|
|
1159
|
-
|
|
1159
|
+
function scheduleSdkBackgroundWork(work, options = {}) {
|
|
1160
|
+
if (options.priority === 'high') {
|
|
1161
|
+
sdkBackgroundHighQueue.push(work);
|
|
1162
|
+
}
|
|
1163
|
+
else {
|
|
1164
|
+
sdkBackgroundQueue.push(work);
|
|
1165
|
+
}
|
|
1160
1166
|
scheduleSdkBackgroundDrain();
|
|
1161
1167
|
}
|
|
1162
1168
|
function scheduleSdkBackgroundDrain(delayMs = 0) {
|
|
@@ -1209,12 +1215,12 @@ async function drainSdkBackgroundQueue() {
|
|
|
1209
1215
|
}
|
|
1210
1216
|
sdkBackgroundDraining = true;
|
|
1211
1217
|
try {
|
|
1212
|
-
while (sdkBackgroundQueue.length > 0) {
|
|
1218
|
+
while (sdkBackgroundHighQueue.length > 0 || sdkBackgroundQueue.length > 0) {
|
|
1213
1219
|
if (shouldDeferSdkBackgroundWork()) {
|
|
1214
1220
|
scheduleSdkBackgroundDrain();
|
|
1215
1221
|
return;
|
|
1216
1222
|
}
|
|
1217
|
-
const work = sdkBackgroundQueue.shift();
|
|
1223
|
+
const work = sdkBackgroundHighQueue.shift() ?? sdkBackgroundQueue.shift();
|
|
1218
1224
|
if (!work)
|
|
1219
1225
|
continue;
|
|
1220
1226
|
try {
|
|
@@ -1228,7 +1234,7 @@ async function drainSdkBackgroundQueue() {
|
|
|
1228
1234
|
}
|
|
1229
1235
|
finally {
|
|
1230
1236
|
sdkBackgroundDraining = false;
|
|
1231
|
-
if (sdkBackgroundQueue.length > 0) {
|
|
1237
|
+
if (sdkBackgroundHighQueue.length > 0 || sdkBackgroundQueue.length > 0) {
|
|
1232
1238
|
scheduleSdkBackgroundDrain();
|
|
1233
1239
|
}
|
|
1234
1240
|
else {
|
|
@@ -2032,14 +2038,26 @@ function sanitizeTraceValue(value, depth = 0, seen = new WeakMap(), options = {}
|
|
|
2032
2038
|
return circularReference(existingPath);
|
|
2033
2039
|
seen.set(value, valuePath);
|
|
2034
2040
|
if (!options.disableTruncation && depth >= TRACE_VALUE_MAX_DEPTH) {
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2041
|
+
if (Array.isArray(value)) {
|
|
2042
|
+
return {
|
|
2043
|
+
__type: 'Array',
|
|
2044
|
+
length: value.length,
|
|
2045
|
+
__truncated: `depth>${TRACE_VALUE_MAX_DEPTH}`,
|
|
2046
|
+
};
|
|
2038
2047
|
}
|
|
2039
2048
|
const ctor = value?.constructor?.name;
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2049
|
+
const keys = Object.keys(value);
|
|
2050
|
+
const summary = {
|
|
2051
|
+
__truncated: `depth>${TRACE_VALUE_MAX_DEPTH}`,
|
|
2052
|
+
__keys: keys.slice(0, TRACE_VALUE_MAX_KEYS),
|
|
2053
|
+
};
|
|
2054
|
+
if (keys.length > TRACE_VALUE_MAX_KEYS) {
|
|
2055
|
+
summary.__truncatedKeys = keys.length - TRACE_VALUE_MAX_KEYS;
|
|
2056
|
+
}
|
|
2057
|
+
if (ctor && ctor !== 'Object') {
|
|
2058
|
+
summary.__class = ctor;
|
|
2059
|
+
}
|
|
2060
|
+
return summary;
|
|
2043
2061
|
}
|
|
2044
2062
|
if (Array.isArray(value)) {
|
|
2045
2063
|
const sourceItems = options.disableTruncation ? value : value.slice(0, TRACE_VALUE_MAX_ITEMS);
|
|
@@ -2105,9 +2123,62 @@ function sanitizeTraceArgs(values) {
|
|
|
2105
2123
|
function sanitizeTraceValueForPrivacy(value) {
|
|
2106
2124
|
return sanitizeTraceValue(value, 0, new WeakMap(), { preserveLongStrings: true, disableTruncation: true });
|
|
2107
2125
|
}
|
|
2126
|
+
function sanitizeInlinePrivacyValue(value) {
|
|
2127
|
+
return sanitizeTraceValue(value, 0, new WeakMap(), { preserveLongStrings: true, disableTruncation: true });
|
|
2128
|
+
}
|
|
2108
2129
|
function sanitizeMaterializedTraceValue(value) {
|
|
2109
2130
|
return sanitizeTraceValue(value, 0, new WeakMap(), { preserveLongStrings: true, disableTruncation: true });
|
|
2110
2131
|
}
|
|
2132
|
+
function compactMaterializedTracePreview(value, depth = 0) {
|
|
2133
|
+
if (value === undefined || value === null)
|
|
2134
|
+
return value;
|
|
2135
|
+
if (depth >= TRACE_VALUE_MAX_DEPTH) {
|
|
2136
|
+
return makeOmittedValue(`depth>${TRACE_VALUE_MAX_DEPTH}`);
|
|
2137
|
+
}
|
|
2138
|
+
const type = typeof value;
|
|
2139
|
+
if (type === 'string') {
|
|
2140
|
+
if (value.length <= TRACE_VALUE_MAX_STRING)
|
|
2141
|
+
return value;
|
|
2142
|
+
return value.slice(0, TRACE_VALUE_MAX_STRING);
|
|
2143
|
+
}
|
|
2144
|
+
if (type === 'number' || type === 'boolean')
|
|
2145
|
+
return value;
|
|
2146
|
+
if (type === 'bigint')
|
|
2147
|
+
return value.toString();
|
|
2148
|
+
if (type === 'symbol')
|
|
2149
|
+
return value.toString();
|
|
2150
|
+
if (type === 'function')
|
|
2151
|
+
return `[Function${value.name ? ` ${value.name}` : ''}]`;
|
|
2152
|
+
if (type !== 'object')
|
|
2153
|
+
return String(value);
|
|
2154
|
+
if (Array.isArray(value)) {
|
|
2155
|
+
return value
|
|
2156
|
+
.slice(0, TRACE_VALUE_MAX_ITEMS)
|
|
2157
|
+
.map((item) => compactMaterializedTracePreview(item, depth + 1));
|
|
2158
|
+
}
|
|
2159
|
+
const out = {};
|
|
2160
|
+
const keys = Object.keys(value).slice(0, TRACE_VALUE_MAX_KEYS);
|
|
2161
|
+
for (const key of keys) {
|
|
2162
|
+
try {
|
|
2163
|
+
out[key] = compactMaterializedTracePreview(value[key], depth + 1);
|
|
2164
|
+
}
|
|
2165
|
+
catch (err) {
|
|
2166
|
+
out[key] = `[Cannot serialize: ${err?.message || 'unknown error'}]`;
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
return out;
|
|
2170
|
+
}
|
|
2171
|
+
function limitMaterializedTraceInlineValue(value) {
|
|
2172
|
+
const inlineValue = sanitizeMaterializedTraceValue(value);
|
|
2173
|
+
if (!traceInlineValueExceedsLimit(inlineValue)) {
|
|
2174
|
+
return inlineValue;
|
|
2175
|
+
}
|
|
2176
|
+
const compactPreview = compactMaterializedTracePreview(inlineValue);
|
|
2177
|
+
if (!traceInlineValueExceedsLimit(compactPreview)) {
|
|
2178
|
+
return compactPreview;
|
|
2179
|
+
}
|
|
2180
|
+
return makeOmittedValue('inline-size-limit');
|
|
2181
|
+
}
|
|
2111
2182
|
function sanitizeTraceArgsForPrivacy(values) {
|
|
2112
2183
|
if (!Array.isArray(values))
|
|
2113
2184
|
return [sanitizeTraceValueForPrivacy(values)];
|
|
@@ -2121,6 +2192,14 @@ function normalizeRawTraceArgs(values) {
|
|
|
2121
2192
|
function hasMeaningfulRawTraceError(error) {
|
|
2122
2193
|
return error !== undefined && error !== null;
|
|
2123
2194
|
}
|
|
2195
|
+
function attachTraceValueEntry(event, entry) {
|
|
2196
|
+
const current = event[TRACE_FULL_VALUE_ENTRY_SYMBOL];
|
|
2197
|
+
if (Array.isArray(current)) {
|
|
2198
|
+
current.push(entry);
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
event[TRACE_FULL_VALUE_ENTRY_SYMBOL] = [entry];
|
|
2202
|
+
}
|
|
2124
2203
|
async function materializePendingTraceEvents(events, params) {
|
|
2125
2204
|
for (const evt of events) {
|
|
2126
2205
|
const pending = evt.__reproPending;
|
|
@@ -2130,14 +2209,71 @@ async function materializePendingTraceEvents(events, params) {
|
|
|
2130
2209
|
const candidate = pending.candidate ?? traceEventCandidateFromRecord(evt);
|
|
2131
2210
|
const omitJsonBuiltinValues = shouldOmitJsonBuiltinTraceValues(candidate);
|
|
2132
2211
|
const activePrivacy = params.resolvePrivacy();
|
|
2212
|
+
const captureContext = {
|
|
2213
|
+
runtimeConfig: params.cfg,
|
|
2214
|
+
captureHeaders: params.cfg.captureHeaders,
|
|
2215
|
+
maskReq: params.maskReq,
|
|
2216
|
+
trace: candidate,
|
|
2217
|
+
masking: params.masking,
|
|
2218
|
+
privacy: activePrivacy,
|
|
2219
|
+
};
|
|
2220
|
+
const traceValueMetadata = {
|
|
2221
|
+
fn: evt.fn,
|
|
2222
|
+
file: evt.file,
|
|
2223
|
+
line: evt.line ?? null,
|
|
2224
|
+
spanId: evt.spanId ?? null,
|
|
2225
|
+
parentSpanId: evt.parentSpanId ?? null,
|
|
2226
|
+
};
|
|
2133
2227
|
if (!omitJsonBuiltinValues && pending.argsRaw !== undefined) {
|
|
2134
|
-
|
|
2228
|
+
const argsMaterialized = await materializeTracePrivacyValueAsync('trace.args', sanitizeTraceArgsForPrivacy(pending.argsRaw), params.cfg, params.maskReq, candidate, params.masking, activePrivacy);
|
|
2229
|
+
evt.args = limitMaterializedTraceInlineValue(argsMaterialized);
|
|
2230
|
+
const argsValueCapture = await maybeCaptureTraceValueAsync({
|
|
2231
|
+
target: 'trace.args',
|
|
2232
|
+
rawValue: pending.argsRaw,
|
|
2233
|
+
previewValue: evt.args,
|
|
2234
|
+
capture: captureContext,
|
|
2235
|
+
metadata: traceValueMetadata,
|
|
2236
|
+
});
|
|
2237
|
+
if (argsValueCapture?.entry) {
|
|
2238
|
+
attachTraceValueEntry(evt, argsValueCapture.entry);
|
|
2239
|
+
}
|
|
2240
|
+
if (argsValueCapture?.captureRef) {
|
|
2241
|
+
evt.argsValueCapture = argsValueCapture.captureRef;
|
|
2242
|
+
}
|
|
2135
2243
|
}
|
|
2136
2244
|
if (!omitJsonBuiltinValues && pending.returnValueRaw !== undefined) {
|
|
2137
|
-
|
|
2245
|
+
const returnValueMaterialized = await materializeTracePrivacyValueAsync('trace.returnValue', sanitizeTraceValueForPrivacy(pending.returnValueRaw), params.cfg, params.maskReq, candidate, params.masking, activePrivacy);
|
|
2246
|
+
evt.returnValue = limitMaterializedTraceInlineValue(returnValueMaterialized);
|
|
2247
|
+
const returnValueCapture = await maybeCaptureTraceValueAsync({
|
|
2248
|
+
target: 'trace.returnValue',
|
|
2249
|
+
rawValue: pending.returnValueRaw,
|
|
2250
|
+
previewValue: evt.returnValue,
|
|
2251
|
+
capture: captureContext,
|
|
2252
|
+
metadata: traceValueMetadata,
|
|
2253
|
+
});
|
|
2254
|
+
if (returnValueCapture?.entry) {
|
|
2255
|
+
attachTraceValueEntry(evt, returnValueCapture.entry);
|
|
2256
|
+
}
|
|
2257
|
+
if (returnValueCapture?.captureRef) {
|
|
2258
|
+
evt.returnValueCapture = returnValueCapture.captureRef;
|
|
2259
|
+
}
|
|
2138
2260
|
}
|
|
2139
2261
|
if (pending.errorRaw !== undefined) {
|
|
2140
|
-
|
|
2262
|
+
const errorMaterialized = await materializeTracePrivacyValueAsync('trace.error', sanitizeTraceValueForPrivacy(pending.errorRaw), params.cfg, params.maskReq, candidate, params.masking, activePrivacy);
|
|
2263
|
+
evt.error = limitMaterializedTraceInlineValue(errorMaterialized);
|
|
2264
|
+
const errorValueCapture = await maybeCaptureTraceValueAsync({
|
|
2265
|
+
target: 'trace.error',
|
|
2266
|
+
rawValue: pending.errorRaw,
|
|
2267
|
+
previewValue: evt.error,
|
|
2268
|
+
capture: captureContext,
|
|
2269
|
+
metadata: traceValueMetadata,
|
|
2270
|
+
});
|
|
2271
|
+
if (errorValueCapture?.entry) {
|
|
2272
|
+
attachTraceValueEntry(evt, errorValueCapture.entry);
|
|
2273
|
+
}
|
|
2274
|
+
if (errorValueCapture?.captureRef) {
|
|
2275
|
+
evt.errorValueCapture = errorValueCapture.captureRef;
|
|
2276
|
+
}
|
|
2141
2277
|
}
|
|
2142
2278
|
}
|
|
2143
2279
|
return events;
|
|
@@ -3689,6 +3825,12 @@ async function maybeCaptureRequestValueAsync(params, sink) {
|
|
|
3689
3825
|
}
|
|
3690
3826
|
return result.captureRef;
|
|
3691
3827
|
}
|
|
3828
|
+
function maybeCaptureTraceValue(params) {
|
|
3829
|
+
return createCapturedValueEntry(params);
|
|
3830
|
+
}
|
|
3831
|
+
async function maybeCaptureTraceValueAsync(params) {
|
|
3832
|
+
return await createCapturedValueEntryAsync(params);
|
|
3833
|
+
}
|
|
3692
3834
|
function maybeCaptureDbValue(params, sink) {
|
|
3693
3835
|
const result = createCapturedValueEntry(params);
|
|
3694
3836
|
if (!result)
|
|
@@ -3860,7 +4002,7 @@ function reproMiddleware(cfg) {
|
|
|
3860
4002
|
}
|
|
3861
4003
|
catch { }
|
|
3862
4004
|
if (flushPayload) {
|
|
3863
|
-
const scheduleFlushPayload = () => scheduleSdkBackgroundWork(flushPayload);
|
|
4005
|
+
const scheduleFlushPayload = () => scheduleSdkBackgroundWork(flushPayload, { priority: 'high' });
|
|
3864
4006
|
if (sessionDrainWait) {
|
|
3865
4007
|
sessionDrainWait.then(scheduleFlushPayload).catch(scheduleFlushPayload);
|
|
3866
4008
|
}
|
|
@@ -5176,7 +5318,7 @@ function cloneKafkaTelemetryValue(value) {
|
|
|
5176
5318
|
}
|
|
5177
5319
|
}
|
|
5178
5320
|
async function materializeKafkaRequestBody(cfg, maskReq, body) {
|
|
5179
|
-
return materializeInlinePrivacyValueAsync('request.body',
|
|
5321
|
+
return materializeInlinePrivacyValueAsync('request.body', sanitizeInlinePrivacyValue(body), cfg, maskReq, null, normalizeMaskingConfig(cfg.masking), getRuntimePrivacyState(cfg).policy ?? null);
|
|
5180
5322
|
}
|
|
5181
5323
|
function summarizeKafkaError(error) {
|
|
5182
5324
|
const message = typeof error?.message === 'string'
|
|
@@ -5267,14 +5409,71 @@ function recordKafkaTraceEvent(raw, sink, cfg, maskReq) {
|
|
|
5267
5409
|
};
|
|
5268
5410
|
const privacy = getRuntimePrivacyState(cfg).policy ?? null;
|
|
5269
5411
|
const masking = normalizeMaskingConfig(cfg.masking);
|
|
5412
|
+
const captureContext = {
|
|
5413
|
+
runtimeConfig: cfg,
|
|
5414
|
+
captureHeaders: cfg.captureHeaders,
|
|
5415
|
+
maskReq,
|
|
5416
|
+
trace: candidate,
|
|
5417
|
+
masking,
|
|
5418
|
+
privacy,
|
|
5419
|
+
};
|
|
5420
|
+
const traceValueMetadata = {
|
|
5421
|
+
fn: evt.fn,
|
|
5422
|
+
file: evt.file,
|
|
5423
|
+
line: evt.line ?? null,
|
|
5424
|
+
spanId: evt.spanId ?? null,
|
|
5425
|
+
parentSpanId: evt.parentSpanId ?? null,
|
|
5426
|
+
};
|
|
5270
5427
|
if (!omitJsonBuiltinValues && raw.args !== undefined) {
|
|
5271
|
-
|
|
5428
|
+
const argsMaterialized = materializeTracePrivacyValue('trace.args', sanitizeTraceArgsForPrivacy(raw.args), cfg, maskReq, candidate, masking, privacy);
|
|
5429
|
+
evt.args = limitMaterializedTraceInlineValue(argsMaterialized);
|
|
5430
|
+
const argsValueCapture = maybeCaptureTraceValue({
|
|
5431
|
+
target: 'trace.args',
|
|
5432
|
+
rawValue: normalizeRawTraceArgs(raw.args),
|
|
5433
|
+
previewValue: evt.args,
|
|
5434
|
+
capture: captureContext,
|
|
5435
|
+
metadata: traceValueMetadata,
|
|
5436
|
+
});
|
|
5437
|
+
if (argsValueCapture?.entry) {
|
|
5438
|
+
attachTraceValueEntry(evt, argsValueCapture.entry);
|
|
5439
|
+
}
|
|
5440
|
+
if (argsValueCapture?.captureRef) {
|
|
5441
|
+
evt.argsValueCapture = argsValueCapture.captureRef;
|
|
5442
|
+
}
|
|
5272
5443
|
}
|
|
5273
5444
|
if (!omitJsonBuiltinValues && raw.returnValue !== undefined) {
|
|
5274
|
-
|
|
5445
|
+
const returnValueMaterialized = materializeTracePrivacyValue('trace.returnValue', sanitizeTraceValueForPrivacy(raw.returnValue), cfg, maskReq, candidate, masking, privacy);
|
|
5446
|
+
evt.returnValue = limitMaterializedTraceInlineValue(returnValueMaterialized);
|
|
5447
|
+
const returnValueCapture = maybeCaptureTraceValue({
|
|
5448
|
+
target: 'trace.returnValue',
|
|
5449
|
+
rawValue: raw.returnValue,
|
|
5450
|
+
previewValue: evt.returnValue,
|
|
5451
|
+
capture: captureContext,
|
|
5452
|
+
metadata: traceValueMetadata,
|
|
5453
|
+
});
|
|
5454
|
+
if (returnValueCapture?.entry) {
|
|
5455
|
+
attachTraceValueEntry(evt, returnValueCapture.entry);
|
|
5456
|
+
}
|
|
5457
|
+
if (returnValueCapture?.captureRef) {
|
|
5458
|
+
evt.returnValueCapture = returnValueCapture.captureRef;
|
|
5459
|
+
}
|
|
5275
5460
|
}
|
|
5276
5461
|
if (hasMeaningfulRawTraceError(raw.error)) {
|
|
5277
|
-
|
|
5462
|
+
const errorMaterialized = materializeTracePrivacyValue('trace.error', sanitizeTraceValueForPrivacy(raw.error), cfg, maskReq, candidate, masking, privacy);
|
|
5463
|
+
evt.error = limitMaterializedTraceInlineValue(errorMaterialized);
|
|
5464
|
+
const errorValueCapture = maybeCaptureTraceValue({
|
|
5465
|
+
target: 'trace.error',
|
|
5466
|
+
rawValue: raw.error,
|
|
5467
|
+
previewValue: evt.error,
|
|
5468
|
+
capture: captureContext,
|
|
5469
|
+
metadata: traceValueMetadata,
|
|
5470
|
+
});
|
|
5471
|
+
if (errorValueCapture?.entry) {
|
|
5472
|
+
attachTraceValueEntry(evt, errorValueCapture.entry);
|
|
5473
|
+
}
|
|
5474
|
+
if (errorValueCapture?.captureRef) {
|
|
5475
|
+
evt.errorValueCapture = errorValueCapture.captureRef;
|
|
5476
|
+
}
|
|
5278
5477
|
}
|
|
5279
5478
|
if (raw.threw !== undefined)
|
|
5280
5479
|
evt.threw = raw.threw === true;
|
|
@@ -5367,6 +5566,7 @@ function buildKafkaTraceEntries(actionId, requestRid, events) {
|
|
|
5367
5566
|
return batches.map((batch, index) => ({
|
|
5368
5567
|
actionId: actionId ?? null,
|
|
5369
5568
|
trace: batch,
|
|
5569
|
+
traceValues: collectBatchTraceValueEntries(batch, index),
|
|
5370
5570
|
traceBatch: {
|
|
5371
5571
|
rid: requestRid,
|
|
5372
5572
|
index,
|
|
@@ -5404,7 +5604,7 @@ function scheduleKafkaTraceFlush(cfg, kafkaCtx, actionId, requestRid, rawTraceEv
|
|
|
5404
5604
|
post(cfg, sid, { entries });
|
|
5405
5605
|
}
|
|
5406
5606
|
});
|
|
5407
|
-
});
|
|
5607
|
+
}, { priority: 'high' });
|
|
5408
5608
|
}
|
|
5409
5609
|
function patchKafkaProducerInstance(producer, cfg) {
|
|
5410
5610
|
if (!producer || producer.__repro_kafka_producer_patched)
|
|
@@ -6667,6 +6867,31 @@ exports.initRepro = initRepro;
|
|
|
6667
6867
|
/** @internal Test hooks for runtime privacy policy wiring. */
|
|
6668
6868
|
exports.__reproTestHooks = {
|
|
6669
6869
|
shareRuntimePrivacyStateForTest: shareRuntimePrivacyState,
|
|
6870
|
+
setFullValueCaptureEnabledForTest(enabled) {
|
|
6871
|
+
__FULL_VALUE_CAPTURE_ENABLED = enabled === true;
|
|
6872
|
+
},
|
|
6873
|
+
scheduleSdkBackgroundWorkForTest(work, options) {
|
|
6874
|
+
scheduleSdkBackgroundWork(work, options);
|
|
6875
|
+
},
|
|
6876
|
+
async drainSdkBackgroundQueueForTest() {
|
|
6877
|
+
await drainSdkBackgroundQueue();
|
|
6878
|
+
},
|
|
6879
|
+
resetSdkBackgroundQueuesForTest() {
|
|
6880
|
+
sdkBackgroundHighQueue.length = 0;
|
|
6881
|
+
sdkBackgroundQueue.length = 0;
|
|
6882
|
+
if (sdkBackgroundTimer) {
|
|
6883
|
+
try {
|
|
6884
|
+
clearTimeout(sdkBackgroundTimer);
|
|
6885
|
+
}
|
|
6886
|
+
catch { }
|
|
6887
|
+
sdkBackgroundTimer = null;
|
|
6888
|
+
}
|
|
6889
|
+
sdkBackgroundDraining = false;
|
|
6890
|
+
activeClientRequestCount = 0;
|
|
6891
|
+
oldestActiveClientRequestAt = null;
|
|
6892
|
+
lastClientActivityAt = 0;
|
|
6893
|
+
sdkBackgroundQuietUntil = 0;
|
|
6894
|
+
},
|
|
6670
6895
|
getRuntimePrivacyPolicyForTest(cfg) {
|
|
6671
6896
|
return getRuntimePrivacyState(cfg).policy;
|
|
6672
6897
|
},
|
|
@@ -6679,4 +6904,7 @@ exports.__reproTestHooks = {
|
|
|
6679
6904
|
wrapKafkaEachMessageHandlerForTest: wrapKafkaEachMessageHandler,
|
|
6680
6905
|
chunkTraceEventsForTransportForTest: chunkTraceEventsForTransport,
|
|
6681
6906
|
estimateTraceBatchSerializedBytesForTest: estimateTraceBatchSerializedBytes,
|
|
6907
|
+
sanitizeTraceValueForPrivacyForTest: sanitizeTraceValueForPrivacy,
|
|
6908
|
+
sanitizeMaterializedTraceValueForTest: sanitizeMaterializedTraceValue,
|
|
6909
|
+
getEventTraceValueEntriesForTest: getEventTraceValueEntries,
|
|
6682
6910
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reproapp/node-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"description": "Repro Nest SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"build": "tsc -p tsconfig.json",
|
|
13
13
|
"dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
|
|
14
14
|
"prepublishOnly": "npm run build",
|
|
15
|
-
"test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js"
|
|
15
|
+
"test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
18
|
"express": "^5.1.0",
|
package/src/index.ts
CHANGED
|
@@ -351,9 +351,12 @@ type TraceEventRecord = {
|
|
|
351
351
|
spanId?: string | number | null;
|
|
352
352
|
parentSpanId?: string | number | null;
|
|
353
353
|
args?: any;
|
|
354
|
+
argsValueCapture?: TraceValueCaptureRef;
|
|
354
355
|
returnValue?: any;
|
|
356
|
+
returnValueCapture?: TraceValueCaptureRef;
|
|
355
357
|
threw?: boolean;
|
|
356
358
|
error?: any;
|
|
359
|
+
errorValueCapture?: TraceValueCaptureRef;
|
|
357
360
|
unawaited?: boolean;
|
|
358
361
|
};
|
|
359
362
|
|
|
@@ -387,7 +390,10 @@ type TraceValueBatchEntry = {
|
|
|
387
390
|
| 'request.body'
|
|
388
391
|
| 'request.params'
|
|
389
392
|
| 'request.query'
|
|
390
|
-
| 'response.body'
|
|
393
|
+
| 'response.body'
|
|
394
|
+
| 'trace.args'
|
|
395
|
+
| 'trace.returnValue'
|
|
396
|
+
| 'trace.error';
|
|
391
397
|
value: any;
|
|
392
398
|
truncatedForStorage?: boolean;
|
|
393
399
|
projectedKind?: string;
|
|
@@ -1231,6 +1237,7 @@ let activeClientRequestCount = 0;
|
|
|
1231
1237
|
let oldestActiveClientRequestAt: number | null = null;
|
|
1232
1238
|
let lastClientActivityAt = 0;
|
|
1233
1239
|
let sdkBackgroundQuietUntil = 0;
|
|
1240
|
+
const sdkBackgroundHighQueue: Array<() => Promise<void> | void> = [];
|
|
1234
1241
|
const sdkBackgroundQueue: Array<() => Promise<void> | void> = [];
|
|
1235
1242
|
let sdkBackgroundTimer: NodeJS.Timeout | null = null;
|
|
1236
1243
|
let sdkBackgroundDraining = false;
|
|
@@ -1499,8 +1506,15 @@ function endClientRequest(): void {
|
|
|
1499
1506
|
}
|
|
1500
1507
|
}
|
|
1501
1508
|
|
|
1502
|
-
function scheduleSdkBackgroundWork(
|
|
1503
|
-
|
|
1509
|
+
function scheduleSdkBackgroundWork(
|
|
1510
|
+
work: () => Promise<void> | void,
|
|
1511
|
+
options: { priority?: 'high' | 'normal' } = {},
|
|
1512
|
+
): void {
|
|
1513
|
+
if (options.priority === 'high') {
|
|
1514
|
+
sdkBackgroundHighQueue.push(work);
|
|
1515
|
+
} else {
|
|
1516
|
+
sdkBackgroundQueue.push(work);
|
|
1517
|
+
}
|
|
1504
1518
|
scheduleSdkBackgroundDrain();
|
|
1505
1519
|
}
|
|
1506
1520
|
|
|
@@ -1549,12 +1563,12 @@ async function drainSdkBackgroundQueue(): Promise<void> {
|
|
|
1549
1563
|
|
|
1550
1564
|
sdkBackgroundDraining = true;
|
|
1551
1565
|
try {
|
|
1552
|
-
while (sdkBackgroundQueue.length > 0) {
|
|
1566
|
+
while (sdkBackgroundHighQueue.length > 0 || sdkBackgroundQueue.length > 0) {
|
|
1553
1567
|
if (shouldDeferSdkBackgroundWork()) {
|
|
1554
1568
|
scheduleSdkBackgroundDrain();
|
|
1555
1569
|
return;
|
|
1556
1570
|
}
|
|
1557
|
-
const work = sdkBackgroundQueue.shift();
|
|
1571
|
+
const work = sdkBackgroundHighQueue.shift() ?? sdkBackgroundQueue.shift();
|
|
1558
1572
|
if (!work) continue;
|
|
1559
1573
|
try {
|
|
1560
1574
|
await drainQueuedIngestBeforeBackgroundWork();
|
|
@@ -1565,7 +1579,7 @@ async function drainSdkBackgroundQueue(): Promise<void> {
|
|
|
1565
1579
|
}
|
|
1566
1580
|
} finally {
|
|
1567
1581
|
sdkBackgroundDraining = false;
|
|
1568
|
-
if (sdkBackgroundQueue.length > 0) {
|
|
1582
|
+
if (sdkBackgroundHighQueue.length > 0 || sdkBackgroundQueue.length > 0) {
|
|
1569
1583
|
scheduleSdkBackgroundDrain();
|
|
1570
1584
|
} else {
|
|
1571
1585
|
kickIngestQueueDrain();
|
|
@@ -2429,14 +2443,26 @@ function sanitizeTraceValue(
|
|
|
2429
2443
|
seen.set(value, valuePath);
|
|
2430
2444
|
|
|
2431
2445
|
if (!options.disableTruncation && depth >= TRACE_VALUE_MAX_DEPTH) {
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2446
|
+
if (Array.isArray(value)) {
|
|
2447
|
+
return {
|
|
2448
|
+
__type: 'Array',
|
|
2449
|
+
length: value.length,
|
|
2450
|
+
__truncated: `depth>${TRACE_VALUE_MAX_DEPTH}`,
|
|
2451
|
+
};
|
|
2435
2452
|
}
|
|
2436
2453
|
const ctor = value?.constructor?.name;
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2454
|
+
const keys = Object.keys(value);
|
|
2455
|
+
const summary: Record<string, any> = {
|
|
2456
|
+
__truncated: `depth>${TRACE_VALUE_MAX_DEPTH}`,
|
|
2457
|
+
__keys: keys.slice(0, TRACE_VALUE_MAX_KEYS),
|
|
2458
|
+
};
|
|
2459
|
+
if (keys.length > TRACE_VALUE_MAX_KEYS) {
|
|
2460
|
+
summary.__truncatedKeys = keys.length - TRACE_VALUE_MAX_KEYS;
|
|
2461
|
+
}
|
|
2462
|
+
if (ctor && ctor !== 'Object') {
|
|
2463
|
+
summary.__class = ctor;
|
|
2464
|
+
}
|
|
2465
|
+
return summary;
|
|
2440
2466
|
}
|
|
2441
2467
|
|
|
2442
2468
|
if (Array.isArray(value)) {
|
|
@@ -2505,10 +2531,61 @@ function sanitizeTraceValueForPrivacy(value: any): any {
|
|
|
2505
2531
|
return sanitizeTraceValue(value, 0, new WeakMap(), { preserveLongStrings: true, disableTruncation: true });
|
|
2506
2532
|
}
|
|
2507
2533
|
|
|
2534
|
+
function sanitizeInlinePrivacyValue(value: any): any {
|
|
2535
|
+
return sanitizeTraceValue(value, 0, new WeakMap(), { preserveLongStrings: true, disableTruncation: true });
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2508
2538
|
function sanitizeMaterializedTraceValue(value: any): any {
|
|
2509
2539
|
return sanitizeTraceValue(value, 0, new WeakMap(), { preserveLongStrings: true, disableTruncation: true });
|
|
2510
2540
|
}
|
|
2511
2541
|
|
|
2542
|
+
function compactMaterializedTracePreview(value: any, depth = 0): any {
|
|
2543
|
+
if (value === undefined || value === null) return value;
|
|
2544
|
+
if (depth >= TRACE_VALUE_MAX_DEPTH) {
|
|
2545
|
+
return makeOmittedValue(`depth>${TRACE_VALUE_MAX_DEPTH}`);
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
const type = typeof value;
|
|
2549
|
+
if (type === 'string') {
|
|
2550
|
+
if (value.length <= TRACE_VALUE_MAX_STRING) return value;
|
|
2551
|
+
return value.slice(0, TRACE_VALUE_MAX_STRING);
|
|
2552
|
+
}
|
|
2553
|
+
if (type === 'number' || type === 'boolean') return value;
|
|
2554
|
+
if (type === 'bigint') return value.toString();
|
|
2555
|
+
if (type === 'symbol') return value.toString();
|
|
2556
|
+
if (type === 'function') return `[Function${value.name ? ` ${value.name}` : ''}]`;
|
|
2557
|
+
if (type !== 'object') return String(value);
|
|
2558
|
+
|
|
2559
|
+
if (Array.isArray(value)) {
|
|
2560
|
+
return value
|
|
2561
|
+
.slice(0, TRACE_VALUE_MAX_ITEMS)
|
|
2562
|
+
.map((item) => compactMaterializedTracePreview(item, depth + 1));
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
const out: Record<string, any> = {};
|
|
2566
|
+
const keys = Object.keys(value).slice(0, TRACE_VALUE_MAX_KEYS);
|
|
2567
|
+
for (const key of keys) {
|
|
2568
|
+
try {
|
|
2569
|
+
out[key] = compactMaterializedTracePreview((value as Record<string, any>)[key], depth + 1);
|
|
2570
|
+
} catch (err) {
|
|
2571
|
+
out[key] = `[Cannot serialize: ${(err as Error)?.message || 'unknown error'}]`;
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
return out;
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
function limitMaterializedTraceInlineValue(value: any): any {
|
|
2578
|
+
const inlineValue = sanitizeMaterializedTraceValue(value);
|
|
2579
|
+
if (!traceInlineValueExceedsLimit(inlineValue)) {
|
|
2580
|
+
return inlineValue;
|
|
2581
|
+
}
|
|
2582
|
+
const compactPreview = compactMaterializedTracePreview(inlineValue);
|
|
2583
|
+
if (!traceInlineValueExceedsLimit(compactPreview)) {
|
|
2584
|
+
return compactPreview;
|
|
2585
|
+
}
|
|
2586
|
+
return makeOmittedValue('inline-size-limit');
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2512
2589
|
function sanitizeTraceArgsForPrivacy(values: any): any {
|
|
2513
2590
|
if (!Array.isArray(values)) return [sanitizeTraceValueForPrivacy(values)];
|
|
2514
2591
|
return values.map(v => sanitizeTraceValueForPrivacy(v));
|
|
@@ -2523,6 +2600,18 @@ function hasMeaningfulRawTraceError(error: any): boolean {
|
|
|
2523
2600
|
return error !== undefined && error !== null;
|
|
2524
2601
|
}
|
|
2525
2602
|
|
|
2603
|
+
function attachTraceValueEntry(
|
|
2604
|
+
event: TraceEventRecord | PendingTraceEventRecord,
|
|
2605
|
+
entry: TraceValueBatchEntry,
|
|
2606
|
+
): void {
|
|
2607
|
+
const current = (event as any)[TRACE_FULL_VALUE_ENTRY_SYMBOL] as TraceValueBatchEntry[] | undefined;
|
|
2608
|
+
if (Array.isArray(current)) {
|
|
2609
|
+
current.push(entry);
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2612
|
+
(event as any)[TRACE_FULL_VALUE_ENTRY_SYMBOL] = [entry];
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2526
2615
|
async function materializePendingTraceEvents(
|
|
2527
2616
|
events: PendingTraceEventRecord[],
|
|
2528
2617
|
params: {
|
|
@@ -2540,9 +2629,24 @@ async function materializePendingTraceEvents(
|
|
|
2540
2629
|
const candidate = pending.candidate ?? traceEventCandidateFromRecord(evt);
|
|
2541
2630
|
const omitJsonBuiltinValues = shouldOmitJsonBuiltinTraceValues(candidate);
|
|
2542
2631
|
const activePrivacy = params.resolvePrivacy();
|
|
2632
|
+
const captureContext: FullValueCaptureContext = {
|
|
2633
|
+
runtimeConfig: params.cfg,
|
|
2634
|
+
captureHeaders: params.cfg.captureHeaders,
|
|
2635
|
+
maskReq: params.maskReq,
|
|
2636
|
+
trace: candidate,
|
|
2637
|
+
masking: params.masking,
|
|
2638
|
+
privacy: activePrivacy,
|
|
2639
|
+
};
|
|
2640
|
+
const traceValueMetadata = {
|
|
2641
|
+
fn: evt.fn,
|
|
2642
|
+
file: evt.file,
|
|
2643
|
+
line: evt.line ?? null,
|
|
2644
|
+
spanId: evt.spanId ?? null,
|
|
2645
|
+
parentSpanId: evt.parentSpanId ?? null,
|
|
2646
|
+
};
|
|
2543
2647
|
|
|
2544
2648
|
if (!omitJsonBuiltinValues && pending.argsRaw !== undefined) {
|
|
2545
|
-
|
|
2649
|
+
const argsMaterialized = await materializeTracePrivacyValueAsync(
|
|
2546
2650
|
'trace.args',
|
|
2547
2651
|
sanitizeTraceArgsForPrivacy(pending.argsRaw),
|
|
2548
2652
|
params.cfg,
|
|
@@ -2550,10 +2654,24 @@ async function materializePendingTraceEvents(
|
|
|
2550
2654
|
candidate,
|
|
2551
2655
|
params.masking,
|
|
2552
2656
|
activePrivacy,
|
|
2553
|
-
)
|
|
2657
|
+
);
|
|
2658
|
+
evt.args = limitMaterializedTraceInlineValue(argsMaterialized);
|
|
2659
|
+
const argsValueCapture = await maybeCaptureTraceValueAsync({
|
|
2660
|
+
target: 'trace.args',
|
|
2661
|
+
rawValue: pending.argsRaw,
|
|
2662
|
+
previewValue: evt.args,
|
|
2663
|
+
capture: captureContext,
|
|
2664
|
+
metadata: traceValueMetadata,
|
|
2665
|
+
});
|
|
2666
|
+
if (argsValueCapture?.entry) {
|
|
2667
|
+
attachTraceValueEntry(evt, argsValueCapture.entry);
|
|
2668
|
+
}
|
|
2669
|
+
if (argsValueCapture?.captureRef) {
|
|
2670
|
+
evt.argsValueCapture = argsValueCapture.captureRef;
|
|
2671
|
+
}
|
|
2554
2672
|
}
|
|
2555
2673
|
if (!omitJsonBuiltinValues && pending.returnValueRaw !== undefined) {
|
|
2556
|
-
|
|
2674
|
+
const returnValueMaterialized = await materializeTracePrivacyValueAsync(
|
|
2557
2675
|
'trace.returnValue',
|
|
2558
2676
|
sanitizeTraceValueForPrivacy(pending.returnValueRaw),
|
|
2559
2677
|
params.cfg,
|
|
@@ -2561,10 +2679,24 @@ async function materializePendingTraceEvents(
|
|
|
2561
2679
|
candidate,
|
|
2562
2680
|
params.masking,
|
|
2563
2681
|
activePrivacy,
|
|
2564
|
-
)
|
|
2682
|
+
);
|
|
2683
|
+
evt.returnValue = limitMaterializedTraceInlineValue(returnValueMaterialized);
|
|
2684
|
+
const returnValueCapture = await maybeCaptureTraceValueAsync({
|
|
2685
|
+
target: 'trace.returnValue',
|
|
2686
|
+
rawValue: pending.returnValueRaw,
|
|
2687
|
+
previewValue: evt.returnValue,
|
|
2688
|
+
capture: captureContext,
|
|
2689
|
+
metadata: traceValueMetadata,
|
|
2690
|
+
});
|
|
2691
|
+
if (returnValueCapture?.entry) {
|
|
2692
|
+
attachTraceValueEntry(evt, returnValueCapture.entry);
|
|
2693
|
+
}
|
|
2694
|
+
if (returnValueCapture?.captureRef) {
|
|
2695
|
+
evt.returnValueCapture = returnValueCapture.captureRef;
|
|
2696
|
+
}
|
|
2565
2697
|
}
|
|
2566
2698
|
if (pending.errorRaw !== undefined) {
|
|
2567
|
-
|
|
2699
|
+
const errorMaterialized = await materializeTracePrivacyValueAsync(
|
|
2568
2700
|
'trace.error',
|
|
2569
2701
|
sanitizeTraceValueForPrivacy(pending.errorRaw),
|
|
2570
2702
|
params.cfg,
|
|
@@ -2572,7 +2704,21 @@ async function materializePendingTraceEvents(
|
|
|
2572
2704
|
candidate,
|
|
2573
2705
|
params.masking,
|
|
2574
2706
|
activePrivacy,
|
|
2575
|
-
)
|
|
2707
|
+
);
|
|
2708
|
+
evt.error = limitMaterializedTraceInlineValue(errorMaterialized);
|
|
2709
|
+
const errorValueCapture = await maybeCaptureTraceValueAsync({
|
|
2710
|
+
target: 'trace.error',
|
|
2711
|
+
rawValue: pending.errorRaw,
|
|
2712
|
+
previewValue: evt.error,
|
|
2713
|
+
capture: captureContext,
|
|
2714
|
+
metadata: traceValueMetadata,
|
|
2715
|
+
});
|
|
2716
|
+
if (errorValueCapture?.entry) {
|
|
2717
|
+
attachTraceValueEntry(evt, errorValueCapture.entry);
|
|
2718
|
+
}
|
|
2719
|
+
if (errorValueCapture?.captureRef) {
|
|
2720
|
+
evt.errorValueCapture = errorValueCapture.captureRef;
|
|
2721
|
+
}
|
|
2576
2722
|
}
|
|
2577
2723
|
}
|
|
2578
2724
|
return events;
|
|
@@ -4301,7 +4447,10 @@ function createCapturedValueEntry(
|
|
|
4301
4447
|
| 'request.body'
|
|
4302
4448
|
| 'request.params'
|
|
4303
4449
|
| 'request.query'
|
|
4304
|
-
| 'response.body'
|
|
4450
|
+
| 'response.body'
|
|
4451
|
+
| 'trace.args'
|
|
4452
|
+
| 'trace.returnValue'
|
|
4453
|
+
| 'trace.error';
|
|
4305
4454
|
rawValue: any;
|
|
4306
4455
|
previewValue: any;
|
|
4307
4456
|
capture: FullValueCaptureContext;
|
|
@@ -4380,7 +4529,10 @@ async function createCapturedValueEntryAsync(
|
|
|
4380
4529
|
| 'request.body'
|
|
4381
4530
|
| 'request.params'
|
|
4382
4531
|
| 'request.query'
|
|
4383
|
-
| 'response.body'
|
|
4532
|
+
| 'response.body'
|
|
4533
|
+
| 'trace.args'
|
|
4534
|
+
| 'trace.returnValue'
|
|
4535
|
+
| 'trace.error';
|
|
4384
4536
|
rawValue: any;
|
|
4385
4537
|
previewValue: any;
|
|
4386
4538
|
capture: FullValueCaptureContext;
|
|
@@ -4480,6 +4632,30 @@ async function maybeCaptureRequestValueAsync(
|
|
|
4480
4632
|
return result.captureRef;
|
|
4481
4633
|
}
|
|
4482
4634
|
|
|
4635
|
+
function maybeCaptureTraceValue(
|
|
4636
|
+
params: {
|
|
4637
|
+
target: 'trace.args' | 'trace.returnValue' | 'trace.error';
|
|
4638
|
+
rawValue: any;
|
|
4639
|
+
previewValue: any;
|
|
4640
|
+
capture: FullValueCaptureContext;
|
|
4641
|
+
metadata?: Partial<TraceValueBatchEntry>;
|
|
4642
|
+
},
|
|
4643
|
+
): { captureRef: TraceValueCaptureRef; entry?: TraceValueBatchEntry } | undefined {
|
|
4644
|
+
return createCapturedValueEntry(params);
|
|
4645
|
+
}
|
|
4646
|
+
|
|
4647
|
+
async function maybeCaptureTraceValueAsync(
|
|
4648
|
+
params: {
|
|
4649
|
+
target: 'trace.args' | 'trace.returnValue' | 'trace.error';
|
|
4650
|
+
rawValue: any;
|
|
4651
|
+
previewValue: any;
|
|
4652
|
+
capture: FullValueCaptureContext;
|
|
4653
|
+
metadata?: Partial<TraceValueBatchEntry>;
|
|
4654
|
+
},
|
|
4655
|
+
): Promise<{ captureRef: TraceValueCaptureRef; entry?: TraceValueBatchEntry } | undefined> {
|
|
4656
|
+
return await createCapturedValueEntryAsync(params);
|
|
4657
|
+
}
|
|
4658
|
+
|
|
4483
4659
|
function maybeCaptureDbValue(
|
|
4484
4660
|
params: {
|
|
4485
4661
|
target: 'db.pk' | 'db.before' | 'db.after' | 'db.query' | 'db.resultMeta' | 'db.error';
|
|
@@ -4674,11 +4850,11 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4674
4850
|
return;
|
|
4675
4851
|
}
|
|
4676
4852
|
}
|
|
4677
|
-
|
|
4678
|
-
|
|
4853
|
+
flushed = true;
|
|
4854
|
+
clearTimers();
|
|
4679
4855
|
try { unsubscribe && unsubscribe(); } catch {}
|
|
4680
4856
|
if (flushPayload) {
|
|
4681
|
-
const scheduleFlushPayload = () => scheduleSdkBackgroundWork(flushPayload
|
|
4857
|
+
const scheduleFlushPayload = () => scheduleSdkBackgroundWork(flushPayload!, { priority: 'high' });
|
|
4682
4858
|
if (sessionDrainWait) {
|
|
4683
4859
|
sessionDrainWait.then(scheduleFlushPayload).catch(scheduleFlushPayload);
|
|
4684
4860
|
} else {
|
|
@@ -6083,7 +6259,7 @@ async function materializeKafkaRequestBody(
|
|
|
6083
6259
|
): Promise<InlinePrivacyMaterializationResult> {
|
|
6084
6260
|
return materializeInlinePrivacyValueAsync(
|
|
6085
6261
|
'request.body',
|
|
6086
|
-
|
|
6262
|
+
sanitizeInlinePrivacyValue(body),
|
|
6087
6263
|
cfg,
|
|
6088
6264
|
maskReq,
|
|
6089
6265
|
null,
|
|
@@ -6198,8 +6374,23 @@ function recordKafkaTraceEvent(
|
|
|
6198
6374
|
|
|
6199
6375
|
const privacy = getRuntimePrivacyState(cfg).policy ?? null;
|
|
6200
6376
|
const masking = normalizeMaskingConfig(cfg.masking);
|
|
6377
|
+
const captureContext: FullValueCaptureContext = {
|
|
6378
|
+
runtimeConfig: cfg,
|
|
6379
|
+
captureHeaders: cfg.captureHeaders,
|
|
6380
|
+
maskReq,
|
|
6381
|
+
trace: candidate,
|
|
6382
|
+
masking,
|
|
6383
|
+
privacy,
|
|
6384
|
+
};
|
|
6385
|
+
const traceValueMetadata = {
|
|
6386
|
+
fn: evt.fn,
|
|
6387
|
+
file: evt.file,
|
|
6388
|
+
line: evt.line ?? null,
|
|
6389
|
+
spanId: evt.spanId ?? null,
|
|
6390
|
+
parentSpanId: evt.parentSpanId ?? null,
|
|
6391
|
+
};
|
|
6201
6392
|
if (!omitJsonBuiltinValues && raw.args !== undefined) {
|
|
6202
|
-
|
|
6393
|
+
const argsMaterialized = materializeTracePrivacyValue(
|
|
6203
6394
|
'trace.args',
|
|
6204
6395
|
sanitizeTraceArgsForPrivacy(raw.args),
|
|
6205
6396
|
cfg,
|
|
@@ -6207,10 +6398,24 @@ function recordKafkaTraceEvent(
|
|
|
6207
6398
|
candidate,
|
|
6208
6399
|
masking,
|
|
6209
6400
|
privacy,
|
|
6210
|
-
)
|
|
6401
|
+
);
|
|
6402
|
+
evt.args = limitMaterializedTraceInlineValue(argsMaterialized);
|
|
6403
|
+
const argsValueCapture = maybeCaptureTraceValue({
|
|
6404
|
+
target: 'trace.args',
|
|
6405
|
+
rawValue: normalizeRawTraceArgs(raw.args),
|
|
6406
|
+
previewValue: evt.args,
|
|
6407
|
+
capture: captureContext,
|
|
6408
|
+
metadata: traceValueMetadata,
|
|
6409
|
+
});
|
|
6410
|
+
if (argsValueCapture?.entry) {
|
|
6411
|
+
attachTraceValueEntry(evt, argsValueCapture.entry);
|
|
6412
|
+
}
|
|
6413
|
+
if (argsValueCapture?.captureRef) {
|
|
6414
|
+
evt.argsValueCapture = argsValueCapture.captureRef;
|
|
6415
|
+
}
|
|
6211
6416
|
}
|
|
6212
6417
|
if (!omitJsonBuiltinValues && raw.returnValue !== undefined) {
|
|
6213
|
-
|
|
6418
|
+
const returnValueMaterialized = materializeTracePrivacyValue(
|
|
6214
6419
|
'trace.returnValue',
|
|
6215
6420
|
sanitizeTraceValueForPrivacy(raw.returnValue),
|
|
6216
6421
|
cfg,
|
|
@@ -6218,10 +6423,24 @@ function recordKafkaTraceEvent(
|
|
|
6218
6423
|
candidate,
|
|
6219
6424
|
masking,
|
|
6220
6425
|
privacy,
|
|
6221
|
-
)
|
|
6426
|
+
);
|
|
6427
|
+
evt.returnValue = limitMaterializedTraceInlineValue(returnValueMaterialized);
|
|
6428
|
+
const returnValueCapture = maybeCaptureTraceValue({
|
|
6429
|
+
target: 'trace.returnValue',
|
|
6430
|
+
rawValue: raw.returnValue,
|
|
6431
|
+
previewValue: evt.returnValue,
|
|
6432
|
+
capture: captureContext,
|
|
6433
|
+
metadata: traceValueMetadata,
|
|
6434
|
+
});
|
|
6435
|
+
if (returnValueCapture?.entry) {
|
|
6436
|
+
attachTraceValueEntry(evt, returnValueCapture.entry);
|
|
6437
|
+
}
|
|
6438
|
+
if (returnValueCapture?.captureRef) {
|
|
6439
|
+
evt.returnValueCapture = returnValueCapture.captureRef;
|
|
6440
|
+
}
|
|
6222
6441
|
}
|
|
6223
6442
|
if (hasMeaningfulRawTraceError(raw.error)) {
|
|
6224
|
-
|
|
6443
|
+
const errorMaterialized = materializeTracePrivacyValue(
|
|
6225
6444
|
'trace.error',
|
|
6226
6445
|
sanitizeTraceValueForPrivacy(raw.error),
|
|
6227
6446
|
cfg,
|
|
@@ -6229,7 +6448,21 @@ function recordKafkaTraceEvent(
|
|
|
6229
6448
|
candidate,
|
|
6230
6449
|
masking,
|
|
6231
6450
|
privacy,
|
|
6232
|
-
)
|
|
6451
|
+
);
|
|
6452
|
+
evt.error = limitMaterializedTraceInlineValue(errorMaterialized);
|
|
6453
|
+
const errorValueCapture = maybeCaptureTraceValue({
|
|
6454
|
+
target: 'trace.error',
|
|
6455
|
+
rawValue: raw.error,
|
|
6456
|
+
previewValue: evt.error,
|
|
6457
|
+
capture: captureContext,
|
|
6458
|
+
metadata: traceValueMetadata,
|
|
6459
|
+
});
|
|
6460
|
+
if (errorValueCapture?.entry) {
|
|
6461
|
+
attachTraceValueEntry(evt, errorValueCapture.entry);
|
|
6462
|
+
}
|
|
6463
|
+
if (errorValueCapture?.captureRef) {
|
|
6464
|
+
evt.errorValueCapture = errorValueCapture.captureRef;
|
|
6465
|
+
}
|
|
6233
6466
|
}
|
|
6234
6467
|
if (raw.threw !== undefined) evt.threw = raw.threw === true;
|
|
6235
6468
|
if (raw.unawaited !== undefined) evt.unawaited = raw.unawaited === true;
|
|
@@ -6338,6 +6571,7 @@ function buildKafkaTraceEntries(
|
|
|
6338
6571
|
return batches.map((batch, index) => ({
|
|
6339
6572
|
actionId: actionId ?? null,
|
|
6340
6573
|
trace: batch,
|
|
6574
|
+
traceValues: collectBatchTraceValueEntries(batch, index),
|
|
6341
6575
|
traceBatch: {
|
|
6342
6576
|
rid: requestRid,
|
|
6343
6577
|
index,
|
|
@@ -6381,7 +6615,7 @@ function scheduleKafkaTraceFlush(
|
|
|
6381
6615
|
post(cfg, sid, { entries });
|
|
6382
6616
|
}
|
|
6383
6617
|
});
|
|
6384
|
-
});
|
|
6618
|
+
}, { priority: 'high' });
|
|
6385
6619
|
}
|
|
6386
6620
|
|
|
6387
6621
|
function patchKafkaProducerInstance(producer: any, cfg: KafkaJsPatchConfig) {
|
|
@@ -7686,6 +7920,31 @@ export async function initRepro(cfg: ReproInitConfig): Promise<void> {
|
|
|
7686
7920
|
/** @internal Test hooks for runtime privacy policy wiring. */
|
|
7687
7921
|
export const __reproTestHooks = {
|
|
7688
7922
|
shareRuntimePrivacyStateForTest: shareRuntimePrivacyState,
|
|
7923
|
+
setFullValueCaptureEnabledForTest(enabled: boolean): void {
|
|
7924
|
+
__FULL_VALUE_CAPTURE_ENABLED = enabled === true;
|
|
7925
|
+
},
|
|
7926
|
+
scheduleSdkBackgroundWorkForTest(
|
|
7927
|
+
work: () => Promise<void> | void,
|
|
7928
|
+
options?: { priority?: 'high' | 'normal' },
|
|
7929
|
+
): void {
|
|
7930
|
+
scheduleSdkBackgroundWork(work, options);
|
|
7931
|
+
},
|
|
7932
|
+
async drainSdkBackgroundQueueForTest(): Promise<void> {
|
|
7933
|
+
await drainSdkBackgroundQueue();
|
|
7934
|
+
},
|
|
7935
|
+
resetSdkBackgroundQueuesForTest(): void {
|
|
7936
|
+
sdkBackgroundHighQueue.length = 0;
|
|
7937
|
+
sdkBackgroundQueue.length = 0;
|
|
7938
|
+
if (sdkBackgroundTimer) {
|
|
7939
|
+
try { clearTimeout(sdkBackgroundTimer); } catch {}
|
|
7940
|
+
sdkBackgroundTimer = null;
|
|
7941
|
+
}
|
|
7942
|
+
sdkBackgroundDraining = false;
|
|
7943
|
+
activeClientRequestCount = 0;
|
|
7944
|
+
oldestActiveClientRequestAt = null;
|
|
7945
|
+
lastClientActivityAt = 0;
|
|
7946
|
+
sdkBackgroundQuietUntil = 0;
|
|
7947
|
+
},
|
|
7689
7948
|
getRuntimePrivacyPolicyForTest(cfg: ReproMiddlewareConfig): NormalizedRuntimePrivacyPolicy | null {
|
|
7690
7949
|
return getRuntimePrivacyState(cfg).policy;
|
|
7691
7950
|
},
|
|
@@ -7701,4 +7960,7 @@ export const __reproTestHooks = {
|
|
|
7701
7960
|
wrapKafkaEachMessageHandlerForTest: wrapKafkaEachMessageHandler,
|
|
7702
7961
|
chunkTraceEventsForTransportForTest: chunkTraceEventsForTransport,
|
|
7703
7962
|
estimateTraceBatchSerializedBytesForTest: estimateTraceBatchSerializedBytes,
|
|
7963
|
+
sanitizeTraceValueForPrivacyForTest: sanitizeTraceValueForPrivacy,
|
|
7964
|
+
sanitizeMaterializedTraceValueForTest: sanitizeMaterializedTraceValue,
|
|
7965
|
+
getEventTraceValueEntriesForTest: getEventTraceValueEntries,
|
|
7704
7966
|
};
|
|
@@ -419,7 +419,19 @@ async function testKafkaProducerCapturesFullPublishedMessageValue() {
|
|
|
419
419
|
}
|
|
420
420
|
|
|
421
421
|
function assertNoLargeJsonPreviewArtifacts(value) {
|
|
422
|
-
const text = JSON.stringify(value)
|
|
422
|
+
const text = JSON.stringify(value, (key, currentValue) => {
|
|
423
|
+
if (
|
|
424
|
+
key === 'argsValueCapture' ||
|
|
425
|
+
key === 'returnValueCapture' ||
|
|
426
|
+
key === 'errorValueCapture' ||
|
|
427
|
+
key === 'argsMaterialization' ||
|
|
428
|
+
key === 'returnValueMaterialization' ||
|
|
429
|
+
key === 'errorMaterialization'
|
|
430
|
+
) {
|
|
431
|
+
return undefined;
|
|
432
|
+
}
|
|
433
|
+
return currentValue;
|
|
434
|
+
});
|
|
423
435
|
assert(!text.includes('__type":"json-string'), text);
|
|
424
436
|
assert(!text.includes('__truncatedKeys'), text);
|
|
425
437
|
assert(!text.includes('__truncatedItems'), text);
|
|
@@ -540,7 +552,7 @@ async function testJsonBuiltinTracePayloadsAreOmitted() {
|
|
|
540
552
|
assertNoLargeJsonPreviewArtifacts(consumedSink[0]);
|
|
541
553
|
}
|
|
542
554
|
|
|
543
|
-
async function
|
|
555
|
+
async function testLargeStringTraceValuesStayIngestibleAndCaptureFullValueOutOfBand() {
|
|
544
556
|
const hugeMarker = 'HUGE_INLINE_VALUE_MARKER';
|
|
545
557
|
const hugeJson = JSON.stringify({
|
|
546
558
|
eventId: 'evt-huge',
|
|
@@ -567,6 +579,7 @@ async function testLargeStringTraceValuesAreCapturedInlineWithoutTruncation() {
|
|
|
567
579
|
};
|
|
568
580
|
|
|
569
581
|
const sink = [];
|
|
582
|
+
__reproTestHooks.setFullValueCaptureEnabledForTest(true);
|
|
570
583
|
await __reproTestHooks.recordKafkaTraceEventAsyncForTest(
|
|
571
584
|
{
|
|
572
585
|
type: 'enter',
|
|
@@ -579,18 +592,24 @@ async function testLargeStringTraceValuesAreCapturedInlineWithoutTruncation() {
|
|
|
579
592
|
cfg,
|
|
580
593
|
maskReq,
|
|
581
594
|
);
|
|
595
|
+
__reproTestHooks.setFullValueCaptureEnabledForTest(false);
|
|
582
596
|
|
|
583
597
|
assert.strictEqual(sink.length, 1);
|
|
584
598
|
assert.strictEqual(sink[0].fn, 'parseGatewayPayload');
|
|
585
599
|
assert.strictEqual(typeof sink[0].args[0], 'string');
|
|
586
600
|
assert(sink[0].args[0].includes(hugeMarker), JSON.stringify(sink[0]));
|
|
587
|
-
assert(sink[0].args[0].
|
|
588
|
-
assert.strictEqual(sink[0].
|
|
589
|
-
|
|
590
|
-
|
|
601
|
+
assert(sink[0].args[0].length < 4096, JSON.stringify(sink[0]));
|
|
602
|
+
assert.strictEqual(sink[0].argsValueCapture?.status, 'stored', JSON.stringify(sink[0]));
|
|
603
|
+
const entries = __reproTestHooks.getEventTraceValueEntriesForTest(sink[0]);
|
|
604
|
+
assert(Array.isArray(entries) && entries.length === 1, JSON.stringify(entries));
|
|
605
|
+
assert.strictEqual(entries[0].target, 'trace.args');
|
|
606
|
+
assert.strictEqual(typeof entries[0].value?.[0], 'string');
|
|
607
|
+
assert(entries[0].value[0].includes(hugeMarker), JSON.stringify(entries[0]));
|
|
608
|
+
assert(entries[0].value[0].includes('x'.repeat(140 * 1024)), JSON.stringify(entries[0]));
|
|
609
|
+
assertNoLargeJsonPreviewArtifacts(sink[0].args);
|
|
591
610
|
}
|
|
592
611
|
|
|
593
|
-
async function
|
|
612
|
+
async function testLargeStructuredTraceValuesStayIngestibleAndCaptureFullValueOutOfBand() {
|
|
594
613
|
const hugeMarker = 'HUGE_STRUCTURED_TRACE_VALUE_MARKER';
|
|
595
614
|
const hugeEnvelope = {
|
|
596
615
|
eventId: 'evt-huge-structured',
|
|
@@ -622,6 +641,7 @@ async function testLargeStructuredTraceValuesAreCapturedInlineWithoutTruncation(
|
|
|
622
641
|
};
|
|
623
642
|
|
|
624
643
|
const sink = [];
|
|
644
|
+
__reproTestHooks.setFullValueCaptureEnabledForTest(true);
|
|
625
645
|
await __reproTestHooks.recordKafkaTraceEventAsyncForTest(
|
|
626
646
|
{
|
|
627
647
|
type: 'enter',
|
|
@@ -634,22 +654,33 @@ async function testLargeStructuredTraceValuesAreCapturedInlineWithoutTruncation(
|
|
|
634
654
|
cfg,
|
|
635
655
|
maskReq,
|
|
636
656
|
);
|
|
657
|
+
__reproTestHooks.setFullValueCaptureEnabledForTest(false);
|
|
637
658
|
|
|
638
659
|
assert.strictEqual(sink.length, 1);
|
|
639
660
|
assert.strictEqual(sink[0].fn, 'recordConsumedEvent');
|
|
640
|
-
assert(sink[0].args, 'expected args to be stored inline');
|
|
641
|
-
assert.
|
|
661
|
+
assert(sink[0].args, 'expected args preview to be stored inline');
|
|
662
|
+
assert(JSON.stringify(sink[0].args).length < 9000, JSON.stringify(sink[0]).slice(0, 12000));
|
|
663
|
+
assert.strictEqual(
|
|
664
|
+
sink[0].args[0]?.providerResponse?.rawNarrative?.__kind,
|
|
665
|
+
'omitted',
|
|
666
|
+
JSON.stringify(sink[0]),
|
|
667
|
+
);
|
|
642
668
|
assert.strictEqual(typeof sink[0].args[2], 'string');
|
|
643
|
-
|
|
669
|
+
assert(sink[0].args[2].includes('providerResponse'), JSON.stringify(sink[0]));
|
|
670
|
+
assert.strictEqual(sink[0].argsValueCapture?.status, 'stored', JSON.stringify(sink[0]));
|
|
671
|
+
const entries = __reproTestHooks.getEventTraceValueEntriesForTest(sink[0]);
|
|
672
|
+
assert(Array.isArray(entries) && entries.length === 1, JSON.stringify(entries));
|
|
673
|
+
assert.strictEqual(entries[0].target, 'trace.args');
|
|
674
|
+
assert.strictEqual(entries[0].value?.[0]?.providerResponse?.rawNarrative, `${hugeMarker}:${'x'.repeat(40 * 1024)}`);
|
|
675
|
+
assert.strictEqual(typeof entries[0].value?.[2], 'string');
|
|
676
|
+
const parsedRawArg = JSON.parse(entries[0].value[2]);
|
|
644
677
|
assert.strictEqual(parsedRawArg.providerResponse.rawNarrative, `${hugeMarker}:${'x'.repeat(40 * 1024)}`);
|
|
645
678
|
assert.strictEqual(parsedRawArg.providerResponse.credentials.apiKey, '[dropped]');
|
|
646
679
|
assert.strictEqual(parsedRawArg.providerResponse.credentials.webhookSecret, '[dropped]');
|
|
647
|
-
|
|
648
|
-
assert.strictEqual(sink[0].argsValueCapture, undefined, JSON.stringify(sink[0]));
|
|
649
|
-
assertNoLargeJsonPreviewArtifacts(sink[0]);
|
|
680
|
+
assertNoLargeJsonPreviewArtifacts(sink[0].args);
|
|
650
681
|
}
|
|
651
682
|
|
|
652
|
-
async function
|
|
683
|
+
async function testLargeTraceReturnValueStaysIngestibleAndCapturesFullValueOutOfBand() {
|
|
653
684
|
const hugeMarker = 'HUGE_STRUCTURED_TRACE_RETURN_MARKER';
|
|
654
685
|
const hugeResponse = {
|
|
655
686
|
id: 'drill-1',
|
|
@@ -677,6 +708,7 @@ async function testLargeTraceReturnValueIsCapturedInlineWithoutTruncation() {
|
|
|
677
708
|
};
|
|
678
709
|
|
|
679
710
|
const sink = [];
|
|
711
|
+
__reproTestHooks.setFullValueCaptureEnabledForTest(true);
|
|
680
712
|
await __reproTestHooks.recordKafkaTraceEventAsyncForTest(
|
|
681
713
|
{
|
|
682
714
|
type: 'exit',
|
|
@@ -689,14 +721,21 @@ async function testLargeTraceReturnValueIsCapturedInlineWithoutTruncation() {
|
|
|
689
721
|
cfg,
|
|
690
722
|
maskReq,
|
|
691
723
|
);
|
|
724
|
+
__reproTestHooks.setFullValueCaptureEnabledForTest(false);
|
|
692
725
|
|
|
693
726
|
assert.strictEqual(sink.length, 1);
|
|
694
727
|
assert.strictEqual(sink[0].fn, 'toDrillResponse');
|
|
695
|
-
assert(sink[0].returnValue, 'expected return value to be stored inline');
|
|
696
|
-
assert.
|
|
697
|
-
assert.strictEqual(sink[0].
|
|
698
|
-
assert
|
|
699
|
-
|
|
728
|
+
assert(sink[0].returnValue, 'expected return value preview to be stored inline');
|
|
729
|
+
assert(JSON.stringify(sink[0].returnValue).length < 9000, JSON.stringify(sink[0]).slice(0, 12000));
|
|
730
|
+
assert.strictEqual(typeof sink[0].returnValue?.structuredSnapshot?.payload, 'string');
|
|
731
|
+
assert(sink[0].returnValue.structuredSnapshot.payload.includes(hugeMarker), JSON.stringify(sink[0]));
|
|
732
|
+
assert(sink[0].returnValue.structuredSnapshot.payload.length < 4096, JSON.stringify(sink[0]));
|
|
733
|
+
assert.strictEqual(sink[0].returnValueCapture?.status, 'stored', JSON.stringify(sink[0]));
|
|
734
|
+
const entries = __reproTestHooks.getEventTraceValueEntriesForTest(sink[0]);
|
|
735
|
+
assert(Array.isArray(entries) && entries.length === 1, JSON.stringify(entries));
|
|
736
|
+
assert.strictEqual(entries[0].target, 'trace.returnValue');
|
|
737
|
+
assert.strictEqual(entries[0].value?.structuredSnapshot?.payload, `${hugeMarker}:${'x'.repeat(40 * 1024)}`);
|
|
738
|
+
assertNoLargeJsonPreviewArtifacts(sink[0].returnValue);
|
|
700
739
|
}
|
|
701
740
|
|
|
702
741
|
async function main() {
|
|
@@ -706,9 +745,9 @@ async function main() {
|
|
|
706
745
|
await testKafkaTraceFlushIsNotBlockedForeverByActiveHttpRequest();
|
|
707
746
|
await testLargeHttpResponseBodyIsCapturedWithoutDroppingRequest();
|
|
708
747
|
await testJsonBuiltinTracePayloadsAreOmitted();
|
|
709
|
-
await
|
|
710
|
-
await
|
|
711
|
-
await
|
|
748
|
+
await testLargeStringTraceValuesStayIngestibleAndCaptureFullValueOutOfBand();
|
|
749
|
+
await testLargeStructuredTraceValuesStayIngestibleAndCaptureFullValueOutOfBand();
|
|
750
|
+
await testLargeTraceReturnValueStaysIngestibleAndCapturesFullValueOutOfBand();
|
|
712
751
|
console.log('kafka runtime privacy policy wiring OK');
|
|
713
752
|
}
|
|
714
753
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const assert = require('node:assert/strict');
|
|
2
|
+
|
|
3
|
+
const sdk = require('../dist/index.js');
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
const hooks = sdk.__reproTestHooks;
|
|
7
|
+
hooks.resetSdkBackgroundQueuesForTest();
|
|
8
|
+
|
|
9
|
+
const order = [];
|
|
10
|
+
|
|
11
|
+
hooks.scheduleSdkBackgroundWorkForTest(() => {
|
|
12
|
+
order.push('normal-1');
|
|
13
|
+
});
|
|
14
|
+
hooks.scheduleSdkBackgroundWorkForTest(() => {
|
|
15
|
+
order.push('normal-2');
|
|
16
|
+
});
|
|
17
|
+
hooks.scheduleSdkBackgroundWorkForTest(() => {
|
|
18
|
+
order.push('high-1');
|
|
19
|
+
}, { priority: 'high' });
|
|
20
|
+
hooks.scheduleSdkBackgroundWorkForTest(() => {
|
|
21
|
+
order.push('high-2');
|
|
22
|
+
}, { priority: 'high' });
|
|
23
|
+
|
|
24
|
+
await hooks.drainSdkBackgroundQueueForTest();
|
|
25
|
+
|
|
26
|
+
assert.deepStrictEqual(order, ['high-1', 'high-2', 'normal-1', 'normal-2']);
|
|
27
|
+
|
|
28
|
+
hooks.resetSdkBackgroundQueuesForTest();
|
|
29
|
+
console.log('sdk background priority OK');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
main().catch((error) => {
|
|
33
|
+
console.error(error);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
});
|