@reproapp/node-sdk 0.0.49 → 0.0.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -1
- package/dist/index.d.ts +16 -0
- package/dist/index.js +62 -6
- package/dist/ingest/client.js +27 -2
- package/dist/live-capture-limits.d.ts +19 -0
- package/dist/live-capture-limits.js +207 -0
- package/dist/semantic-serialization.d.ts +1 -0
- package/dist/semantic-serialization.js +33 -1
- package/package.json +6 -1
- package/src/index.ts +75 -6
- package/src/ingest/client.ts +27 -3
- package/src/live-capture-limits.ts +177 -0
- package/src/semantic-serialization.ts +30 -0
- package/test/fixtures/live-capture-limits-server.js +21 -0
- package/test/ingest-throttle-retention.test.js +41 -0
- package/test/live-broker-limits.testcontainers.test.js +86 -0
- package/test/live-capture-limits.test.js +99 -0
- package/test/live-capture-limits.testcontainers.test.js +78 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.limitLiveArguments = exports.collectCaptureOmissions = exports.limitLiveCapture = exports.LIVE_CAPTURE_LIMITS = void 0;
|
|
4
|
+
const http_1 = require("http");
|
|
5
|
+
const semantic_serialization_1 = require("./semantic-serialization");
|
|
6
|
+
exports.LIVE_CAPTURE_LIMITS = Object.freeze({ maxDepth: 10, maxKeys: 100, maxEntries: 100 });
|
|
7
|
+
const marker = (omission) => ({ __kind: 'repro-omitted', complete: false, ...omission });
|
|
8
|
+
const childPath = (path, key) => `${path}[${JSON.stringify(key)}]`;
|
|
9
|
+
function parseStructuredString(value) {
|
|
10
|
+
if (typeof value !== 'string' || !/^[\s]*[\[{]/.test(value))
|
|
11
|
+
return undefined;
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(value);
|
|
14
|
+
return parsed && typeof parsed === 'object' ? parsed : undefined;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Snapshot only data descriptors: capture must never execute application getters. */
|
|
21
|
+
function limitLiveCapture(value, rootPath = '$') {
|
|
22
|
+
const omissions = [];
|
|
23
|
+
const seen = new WeakMap();
|
|
24
|
+
const omit = (detail) => { omissions.push(detail); return marker(detail); };
|
|
25
|
+
const walk = (input, depth, path) => {
|
|
26
|
+
if (input === null || input === undefined)
|
|
27
|
+
return input;
|
|
28
|
+
if (typeof input === 'string') {
|
|
29
|
+
const parsed = parseStructuredString(input);
|
|
30
|
+
if (parsed === undefined)
|
|
31
|
+
return input;
|
|
32
|
+
const before = omissions.length;
|
|
33
|
+
const bounded = walk(parsed, depth, path);
|
|
34
|
+
// Preserve the original string exactly unless its structured data exceeded a limit.
|
|
35
|
+
return omissions.length === before ? input : JSON.stringify(bounded);
|
|
36
|
+
}
|
|
37
|
+
if (typeof input === 'bigint' || typeof input === 'symbol')
|
|
38
|
+
return String(input);
|
|
39
|
+
if (typeof input === 'function')
|
|
40
|
+
return '[Function]';
|
|
41
|
+
if (typeof input !== 'object')
|
|
42
|
+
return input;
|
|
43
|
+
const previous = seen.get(input);
|
|
44
|
+
if (previous && previous.depth <= depth)
|
|
45
|
+
return { __reproCircularRef: previous.path };
|
|
46
|
+
seen.set(input, { path, depth });
|
|
47
|
+
const descriptor = (key) => Object.getOwnPropertyDescriptor(input, key);
|
|
48
|
+
const data = (key) => descriptor(key)?.value;
|
|
49
|
+
if (data('__kind') === 'repro-omitted') {
|
|
50
|
+
const detail = { reason: data('reason'), path: data('path'), limit: data('limit'), originalCount: data('originalCount'), capturedCount: data('capturedCount') };
|
|
51
|
+
omissions.push(detail);
|
|
52
|
+
return marker(detail);
|
|
53
|
+
}
|
|
54
|
+
if (input instanceof Date)
|
|
55
|
+
return Number.isNaN(Date.prototype.getTime.call(input)) ? 'Invalid Date' : Date.prototype.toISOString.call(input);
|
|
56
|
+
if (input instanceof RegExp)
|
|
57
|
+
return RegExp.prototype.toString.call(input);
|
|
58
|
+
if (input instanceof http_1.IncomingMessage) {
|
|
59
|
+
// The business body is its own root, not depth one in a socket graph.
|
|
60
|
+
return { __kind: 'http-request', method: data('method'), url: data('originalUrl') ?? data('url'),
|
|
61
|
+
headers: walk(data('headers') ?? Object.getOwnPropertyDescriptor(http_1.IncomingMessage.prototype, 'headers')?.get?.call(input), 0, childPath(path, 'headers')),
|
|
62
|
+
query: walk(data('query'), 0, childPath(path, 'query')),
|
|
63
|
+
body: walk(data('body'), 0, childPath(path, 'body')),
|
|
64
|
+
params: walk(data('params'), 0, childPath(path, 'params')) };
|
|
65
|
+
}
|
|
66
|
+
if (input instanceof http_1.ServerResponse)
|
|
67
|
+
return { __kind: 'http-response', statusCode: data('statusCode') };
|
|
68
|
+
const projected = (0, semantic_serialization_1.projectSemanticInfrastructureValue)(input);
|
|
69
|
+
if (projected)
|
|
70
|
+
return projected.value;
|
|
71
|
+
// Mongoose stores business fields in _doc; never recurse through $__ state.
|
|
72
|
+
const documentData = (0, semantic_serialization_1.mongooseDocumentData)(input);
|
|
73
|
+
if (documentData)
|
|
74
|
+
return walk(documentData, depth, path);
|
|
75
|
+
if (depth > exports.LIVE_CAPTURE_LIMITS.maxDepth)
|
|
76
|
+
return omit({ reason: 'max-depth', path, limit: exports.LIVE_CAPTURE_LIMITS.maxDepth, capturedCount: 0 });
|
|
77
|
+
if (input instanceof Error) {
|
|
78
|
+
const details = Object.create(null);
|
|
79
|
+
for (const key of ['message', 'stack', 'cause', 'errors', ...Object.keys(input)]) {
|
|
80
|
+
const item = descriptor(key);
|
|
81
|
+
if (item)
|
|
82
|
+
Object.defineProperty(details, key, { ...item, enumerable: true, configurable: true });
|
|
83
|
+
}
|
|
84
|
+
return walk(details, depth, path);
|
|
85
|
+
}
|
|
86
|
+
if (input instanceof ArrayBuffer || (typeof SharedArrayBuffer !== 'undefined' && input instanceof SharedArrayBuffer) || input instanceof DataView) {
|
|
87
|
+
let bytes;
|
|
88
|
+
if (input instanceof DataView) {
|
|
89
|
+
const read = (key) => Object.getOwnPropertyDescriptor(DataView.prototype, key).get.call(input);
|
|
90
|
+
bytes = new Uint8Array(read('buffer'), read('byteOffset'), read('byteLength'));
|
|
91
|
+
}
|
|
92
|
+
else
|
|
93
|
+
bytes = new Uint8Array(input);
|
|
94
|
+
return { __type: input instanceof DataView ? 'DataView' : 'ArrayBuffer', values: walk(bytes, depth, path).values };
|
|
95
|
+
}
|
|
96
|
+
if (input instanceof Map) {
|
|
97
|
+
const entries = [];
|
|
98
|
+
const size = Object.getOwnPropertyDescriptor(Map.prototype, 'size').get.call(input);
|
|
99
|
+
for (const [key, item] of Map.prototype.entries.call(input)) {
|
|
100
|
+
if (entries.length >= exports.LIVE_CAPTURE_LIMITS.maxEntries)
|
|
101
|
+
break;
|
|
102
|
+
const index = entries.length;
|
|
103
|
+
entries.push([walk(key, depth + 1, childPath(path, `key:${index}`)), walk(item, depth + 1, childPath(path, index))]);
|
|
104
|
+
}
|
|
105
|
+
if (size > entries.length)
|
|
106
|
+
entries.push(omit({ reason: 'max-entries', path, limit: 100, originalCount: size, capturedCount: entries.length }));
|
|
107
|
+
return { __type: 'Map', entries };
|
|
108
|
+
}
|
|
109
|
+
if (input instanceof Set) {
|
|
110
|
+
const values = [];
|
|
111
|
+
const size = Object.getOwnPropertyDescriptor(Set.prototype, 'size').get.call(input);
|
|
112
|
+
for (const item of Set.prototype.values.call(input)) {
|
|
113
|
+
if (values.length >= 100)
|
|
114
|
+
break;
|
|
115
|
+
values.push(walk(item, depth + 1, childPath(path, values.length)));
|
|
116
|
+
}
|
|
117
|
+
if (size > values.length)
|
|
118
|
+
values.push(omit({ reason: 'max-entries', path, limit: 100, originalCount: size, capturedCount: values.length }));
|
|
119
|
+
return { __type: 'Set', values };
|
|
120
|
+
}
|
|
121
|
+
if (Array.isArray(input) || (ArrayBuffer.isView(input) && !(input instanceof DataView))) {
|
|
122
|
+
const length = input.length;
|
|
123
|
+
const previousMarker = Array.isArray(input) && length === 101 ? descriptor('100')?.value : null;
|
|
124
|
+
const prior = previousMarker && Object.getOwnPropertyDescriptors(previousMarker);
|
|
125
|
+
const hasPriorLimit = prior?.__kind?.value === 'repro-omitted' && prior?.reason?.value === 'max-entries';
|
|
126
|
+
const values = [];
|
|
127
|
+
for (let i = 0; i < Math.min(length, 100); i++) {
|
|
128
|
+
const itemDescriptor = descriptor(String(i));
|
|
129
|
+
values.push(itemDescriptor && !('value' in itemDescriptor)
|
|
130
|
+
? omit({ reason: 'accessor', path: childPath(path, i), capturedCount: 0 })
|
|
131
|
+
: walk(itemDescriptor?.value, depth + 1, childPath(path, i)));
|
|
132
|
+
}
|
|
133
|
+
if (length > 100)
|
|
134
|
+
values.push(hasPriorLimit
|
|
135
|
+
? walk(previousMarker, depth + 1, path)
|
|
136
|
+
: omit({ reason: 'max-entries', path, limit: 100, originalCount: length, capturedCount: 100 }));
|
|
137
|
+
return Array.isArray(input) ? values : { __type: 'TypedArray', values };
|
|
138
|
+
}
|
|
139
|
+
const keys = Object.keys(input);
|
|
140
|
+
const previousMetadataKey = keys.find(key => {
|
|
141
|
+
if (!/^__reproCapture\$*$/.test(key))
|
|
142
|
+
return false;
|
|
143
|
+
const candidate = descriptor(key)?.value;
|
|
144
|
+
return candidate && Object.getOwnPropertyDescriptor(candidate, '__kind')?.value === 'repro-omitted'
|
|
145
|
+
&& Object.getOwnPropertyDescriptor(candidate, 'reason')?.value === 'max-keys';
|
|
146
|
+
});
|
|
147
|
+
const businessKeys = keys.filter(key => key !== previousMetadataKey);
|
|
148
|
+
const out = Object.create(null);
|
|
149
|
+
for (const key of businessKeys.slice(0, 100)) {
|
|
150
|
+
const itemDescriptor = descriptor(key);
|
|
151
|
+
out[key] = 'value' in itemDescriptor
|
|
152
|
+
? walk(itemDescriptor.value, depth + 1, childPath(path, key))
|
|
153
|
+
: omit({ reason: 'accessor', path: childPath(path, key), capturedCount: 0 });
|
|
154
|
+
}
|
|
155
|
+
if (businessKeys.length > 100 || previousMetadataKey) {
|
|
156
|
+
const detail = previousMetadataKey
|
|
157
|
+
? walk(descriptor(previousMetadataKey)?.value, depth + 1, path)
|
|
158
|
+
: omit({ reason: 'max-keys', path, limit: 100, originalCount: businessKeys.length, capturedCount: 100 });
|
|
159
|
+
let metadataKey = '__reproCapture';
|
|
160
|
+
while (Object.prototype.hasOwnProperty.call(out, metadataKey))
|
|
161
|
+
metadataKey += '$';
|
|
162
|
+
out[metadataKey] = detail;
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
};
|
|
166
|
+
try {
|
|
167
|
+
return { value: walk(value, 0, rootPath), omissions };
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return { value: omit({ reason: 'uninspectable', path: rootPath, capturedCount: 0 }), omissions };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
exports.limitLiveCapture = limitLiveCapture;
|
|
174
|
+
function collectCaptureOmissions(value) {
|
|
175
|
+
const result = [];
|
|
176
|
+
const seen = new WeakSet();
|
|
177
|
+
const pending = [value];
|
|
178
|
+
while (pending.length) {
|
|
179
|
+
const item = pending.pop();
|
|
180
|
+
const node = typeof item === 'string' ? parseStructuredString(item) : item;
|
|
181
|
+
if (!node || typeof node !== 'object' || seen.has(node))
|
|
182
|
+
continue;
|
|
183
|
+
seen.add(node);
|
|
184
|
+
const descriptors = Object.getOwnPropertyDescriptors(node);
|
|
185
|
+
if (descriptors.__kind?.value === 'repro-omitted') {
|
|
186
|
+
result.push({ reason: descriptors.reason?.value, path: descriptors.path?.value, limit: descriptors.limit?.value,
|
|
187
|
+
originalCount: descriptors.originalCount?.value, capturedCount: descriptors.capturedCount?.value });
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
for (const item of Object.values(descriptors))
|
|
191
|
+
if ('value' in item)
|
|
192
|
+
pending.push(item.value);
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
exports.collectCaptureOmissions = collectCaptureOmissions;
|
|
197
|
+
function limitLiveArguments(args) {
|
|
198
|
+
const values = args.slice(0, 100).map((arg, index) => limitLiveCapture(arg, `$[${index}]`).value);
|
|
199
|
+
if (args.length > 100) {
|
|
200
|
+
const prior = Object.getOwnPropertyDescriptor(args, '100')?.value;
|
|
201
|
+
values.push(args.length === 101 && prior && Object.getOwnPropertyDescriptor(prior, '__kind')?.value === 'repro-omitted'
|
|
202
|
+
? limitLiveCapture(prior).value
|
|
203
|
+
: marker({ reason: 'max-entries', path: '$', limit: 100, originalCount: args.length, capturedCount: 100 }));
|
|
204
|
+
}
|
|
205
|
+
return values;
|
|
206
|
+
}
|
|
207
|
+
exports.limitLiveArguments = limitLiveArguments;
|
|
@@ -2,6 +2,7 @@ export type SemanticProjection = {
|
|
|
2
2
|
matched: true;
|
|
3
3
|
value: Record<string, unknown>;
|
|
4
4
|
};
|
|
5
|
+
export declare function mongooseDocumentData(value: object): object | null;
|
|
5
6
|
/**
|
|
6
7
|
* Projects known runtime machinery into stable, non-recursive descriptions.
|
|
7
8
|
* Unknown application values deliberately return null and remain fully captured.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.projectSemanticInfrastructureValue = void 0;
|
|
3
|
+
exports.projectSemanticInfrastructureValue = exports.mongooseDocumentData = void 0;
|
|
4
4
|
function constructorName(value) {
|
|
5
5
|
if (!value || typeof value !== 'object')
|
|
6
6
|
return '';
|
|
@@ -49,6 +49,13 @@ function method(value, key) {
|
|
|
49
49
|
return false;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
function mongooseDocumentData(value) {
|
|
53
|
+
const doc = Object.getOwnPropertyDescriptor(value, '_doc')?.value;
|
|
54
|
+
if (!doc || typeof doc !== 'object' || !Object.getOwnPropertyDescriptor(value, '$__')?.value)
|
|
55
|
+
return null;
|
|
56
|
+
return method(value, '$__init') && method(value, '$toObject') ? doc : null;
|
|
57
|
+
}
|
|
58
|
+
exports.mongooseDocumentData = mongooseDocumentData;
|
|
52
59
|
function primitiveStateValue(value) {
|
|
53
60
|
if (value === null)
|
|
54
61
|
return null;
|
|
@@ -82,6 +89,8 @@ function projectConfigurationService(value, className) {
|
|
|
82
89
|
function projectLogger(value, className) {
|
|
83
90
|
if (!/(Logger|LoggerService)$/i.test(className))
|
|
84
91
|
return null;
|
|
92
|
+
if (!method(value, 'log') && !method(value, 'info'))
|
|
93
|
+
return null;
|
|
85
94
|
const descriptors = ownDataDescriptors(value);
|
|
86
95
|
const context = primitiveStateValue(descriptors.context?.value);
|
|
87
96
|
return semanticValue('logger', className, context !== undefined ? { context } : {});
|
|
@@ -90,6 +99,15 @@ function projectNestInfrastructure(value, className) {
|
|
|
90
99
|
if (!/(ModuleRef|ModulesContainer|InstanceWrapper|NestContainer|Injector|DiscoveryService|ExecutionContextHost|ArgumentsHost|HttpAdapterHost)$/i.test(className)) {
|
|
91
100
|
return null;
|
|
92
101
|
}
|
|
102
|
+
const signatures = {
|
|
103
|
+
ModuleRef: ['get', 'resolve'], ModulesContainer: ['get', 'set'],
|
|
104
|
+
InstanceWrapper: ['getInstanceByContextId'], NestContainer: ['getModules'],
|
|
105
|
+
Injector: ['loadInstance'], DiscoveryService: ['getProviders'],
|
|
106
|
+
ExecutionContextHost: ['switchToHttp', 'getArgs'], ArgumentsHost: ['switchToHttp', 'getArgs'],
|
|
107
|
+
HttpAdapterHost: ['listen$'],
|
|
108
|
+
};
|
|
109
|
+
if (!(signatures[className] ?? []).some(key => method(value, key)))
|
|
110
|
+
return null;
|
|
93
111
|
return semanticValue('nestjs-infrastructure', className);
|
|
94
112
|
}
|
|
95
113
|
function projectRedis(value, className) {
|
|
@@ -149,6 +167,19 @@ function projectTypeOrm(value, className) {
|
|
|
149
167
|
}
|
|
150
168
|
return null;
|
|
151
169
|
}
|
|
170
|
+
function projectMongoInfrastructure(value, className) {
|
|
171
|
+
const signatures = {
|
|
172
|
+
MongoClient: ['connect', 'db', 'close'],
|
|
173
|
+
ClientSession: ['startTransaction', 'endSession'],
|
|
174
|
+
ConnectionPool: ['checkOut', 'checkIn'],
|
|
175
|
+
NativeConnection: ['openUri', 'model', 'close'],
|
|
176
|
+
Schema: ['add', 'path', 'plugin'],
|
|
177
|
+
};
|
|
178
|
+
const required = signatures[className];
|
|
179
|
+
return required?.every(key => method(value, key))
|
|
180
|
+
? semanticValue('mongodb-infrastructure', className)
|
|
181
|
+
: null;
|
|
182
|
+
}
|
|
152
183
|
/**
|
|
153
184
|
* Projects known runtime machinery into stable, non-recursive descriptions.
|
|
154
185
|
* Unknown application values deliberately return null and remain fully captured.
|
|
@@ -165,6 +196,7 @@ function projectSemanticInfrastructureValue(value) {
|
|
|
165
196
|
projectRedis(value, className) ||
|
|
166
197
|
projectKafka(value, className) ||
|
|
167
198
|
projectAmqp(value, className) ||
|
|
199
|
+
projectMongoInfrastructure(value, className) ||
|
|
168
200
|
projectPostgres(value, className) ||
|
|
169
201
|
projectTypeOrm(value, className));
|
|
170
202
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reproapp/node-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.50",
|
|
4
4
|
"description": "Repro Nest SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
"node": ">=18"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
+
"test:live-capture": "npm run build && node test/live-capture-limits.test.js && node test/ingest-throttle-retention.test.js && node test/live-capture-limits.testcontainers.test.js",
|
|
13
|
+
"test:live-brokers": "npm run build && node test/live-broker-limits.testcontainers.test.js",
|
|
12
14
|
"build": "tsc -p tsconfig.json",
|
|
13
15
|
"dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
|
|
14
16
|
"prepublishOnly": "npm run build",
|
|
@@ -21,8 +23,11 @@
|
|
|
21
23
|
},
|
|
22
24
|
"devDependencies": {
|
|
23
25
|
"@nestjs/typeorm": "^11.0.0",
|
|
26
|
+
"@testcontainers/kafka": "^11.8.1",
|
|
24
27
|
"@types/express": "^5.0.3",
|
|
25
28
|
"@types/pg": "^8.20.0",
|
|
29
|
+
"amqplib": "^0.10.9",
|
|
30
|
+
"kafkajs": "^2.2.4",
|
|
26
31
|
"mongodb": "^6.21.0",
|
|
27
32
|
"pg": "^8.22.0",
|
|
28
33
|
"postgres": "^3.4.9",
|
package/src/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type RuntimePrivacyTraceContext,
|
|
30
30
|
} from './privacy';
|
|
31
31
|
import { projectSemanticInfrastructureValue } from './semantic-serialization';
|
|
32
|
+
import { limitLiveCapture, limitLiveArguments, collectCaptureOmissions, type CaptureOmission } from './live-capture-limits';
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* ============================= IMPORTANT =============================
|
|
@@ -380,6 +381,8 @@ type PendingTraceEventRecord = TraceEventRecord & {
|
|
|
380
381
|
};
|
|
381
382
|
|
|
382
383
|
type TraceValueCaptureRef = {
|
|
384
|
+
complete?: boolean;
|
|
385
|
+
omissions?: CaptureOmission[];
|
|
383
386
|
status:
|
|
384
387
|
| 'stored'
|
|
385
388
|
| 'truncated-for-storage'
|
|
@@ -419,6 +422,7 @@ type TraceValueBatchEntry = {
|
|
|
419
422
|
};
|
|
420
423
|
|
|
421
424
|
type TraceMaterializationConfig = {
|
|
425
|
+
captureMode?: 'live' | 'staging';
|
|
422
426
|
tenantId: string;
|
|
423
427
|
appId: string;
|
|
424
428
|
appSecret?: string;
|
|
@@ -1213,6 +1217,7 @@ type NativeMongoSuppression = {
|
|
|
1213
1217
|
};
|
|
1214
1218
|
|
|
1215
1219
|
type RequestCaptureScopeState = {
|
|
1220
|
+
liveCapture?: boolean;
|
|
1216
1221
|
requestRid: string;
|
|
1217
1222
|
transport: 'http' | 'kafka' | 'amqp' | 'background';
|
|
1218
1223
|
backendRequestCount: number;
|
|
@@ -1246,6 +1251,7 @@ function createRequestCaptureScope(
|
|
|
1246
1251
|
return {
|
|
1247
1252
|
requestRid,
|
|
1248
1253
|
transport,
|
|
1254
|
+
liveCapture: cfg?.captureMode === 'live' || (!!cfg && cfg.captureMode !== 'staging' && !!normalizeLiveModeConfig(cfg)),
|
|
1249
1255
|
// Every scope represents one captured transport request. This is an
|
|
1250
1256
|
// observed expectation, not a count of successful enqueue operations.
|
|
1251
1257
|
backendRequestCount: 1,
|
|
@@ -2599,6 +2605,7 @@ async function resolveBackendLiveContext(
|
|
|
2599
2605
|
liveMode,
|
|
2600
2606
|
'/v1/live-sessions/claim',
|
|
2601
2607
|
{
|
|
2608
|
+
liveCapture: liveMode.liveCapture,
|
|
2602
2609
|
clientTime: requestStart,
|
|
2603
2610
|
captureMode: 'backend-only',
|
|
2604
2611
|
actorId,
|
|
@@ -2712,6 +2719,7 @@ function stripReproQueryFromUrl(rawUrl: string): string {
|
|
|
2712
2719
|
parsed.searchParams.delete(REPRO_QUERY_SESSION_PARAM);
|
|
2713
2720
|
parsed.searchParams.delete(REPRO_QUERY_ACTION_PARAM);
|
|
2714
2721
|
parsed.searchParams.delete(REPRO_QUERY_START_PARAM);
|
|
2722
|
+
parsed.searchParams.delete('__repro_mode');
|
|
2715
2723
|
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
2716
2724
|
} catch {
|
|
2717
2725
|
return rawUrl;
|
|
@@ -2725,6 +2733,7 @@ function stripReproQueryParams(req: Request): void {
|
|
|
2725
2733
|
delete query[REPRO_QUERY_SESSION_PARAM];
|
|
2726
2734
|
delete query[REPRO_QUERY_ACTION_PARAM];
|
|
2727
2735
|
delete query[REPRO_QUERY_START_PARAM];
|
|
2736
|
+
delete query.__repro_mode;
|
|
2728
2737
|
}
|
|
2729
2738
|
} catch {}
|
|
2730
2739
|
try {
|
|
@@ -3439,6 +3448,11 @@ function snapshotTraceValueAtObservation(
|
|
|
3439
3448
|
target = 'trace.value',
|
|
3440
3449
|
): any {
|
|
3441
3450
|
try {
|
|
3451
|
+
if (captureScope?.liveCapture) {
|
|
3452
|
+
value = target === 'trace.args' && Array.isArray(value)
|
|
3453
|
+
? limitLiveArguments(value)
|
|
3454
|
+
: limitLiveCapture(value, valuePath).value;
|
|
3455
|
+
}
|
|
3442
3456
|
const snapshot = sanitizeTraceValue(
|
|
3443
3457
|
value,
|
|
3444
3458
|
0,
|
|
@@ -3714,6 +3728,7 @@ async function materializePendingTraceEventsAsync(
|
|
|
3714
3728
|
appSecret: params.cfg.appSecret,
|
|
3715
3729
|
appName: params.cfg.appName,
|
|
3716
3730
|
privacy: params.cfg.privacy,
|
|
3731
|
+
captureMode: params.captureScope?.liveCapture ? 'live' : params.cfg.captureMode,
|
|
3717
3732
|
},
|
|
3718
3733
|
maskReq: params.maskReq,
|
|
3719
3734
|
masking: params.masking,
|
|
@@ -4223,6 +4238,18 @@ async function initializeRuntimePrivacyPolicyAtStartup(cfg: ReproMiddlewareConfi
|
|
|
4223
4238
|
);
|
|
4224
4239
|
}
|
|
4225
4240
|
|
|
4241
|
+
function isLiveCapture(cfg: ReproMiddlewareConfig, scope?: RequestCaptureScopeState | null): boolean {
|
|
4242
|
+
if (scope) return scope.liveCapture === true;
|
|
4243
|
+
return cfg.captureMode === 'live' || (cfg.captureMode !== 'staging' && getCtx().captureScope?.liveCapture === true);
|
|
4244
|
+
}
|
|
4245
|
+
|
|
4246
|
+
function constrainLiveCapture(value: any, cfg: ReproMiddlewareConfig, target: string, scope?: RequestCaptureScopeState | null): any {
|
|
4247
|
+
if (!isLiveCapture(cfg, scope)) return value;
|
|
4248
|
+
return target === 'trace.args' && Array.isArray(value)
|
|
4249
|
+
? limitLiveArguments(value)
|
|
4250
|
+
: limitLiveCapture(value).value;
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4226
4253
|
function applyPrivacyThenMask(
|
|
4227
4254
|
target: RuntimePrivacySurface,
|
|
4228
4255
|
value: any,
|
|
@@ -4233,6 +4260,7 @@ function applyPrivacyThenMask(
|
|
|
4233
4260
|
privacy: NormalizedRuntimePrivacyPolicy | null,
|
|
4234
4261
|
db?: RuntimePrivacyDbContext | null,
|
|
4235
4262
|
) {
|
|
4263
|
+
value = constrainLiveCapture(value, cfg, target);
|
|
4236
4264
|
if (shouldCapturePrivacyRaw(cfg)) {
|
|
4237
4265
|
return value;
|
|
4238
4266
|
}
|
|
@@ -4263,6 +4291,7 @@ async function applyPrivacyThenMaskAsync(
|
|
|
4263
4291
|
privacy: NormalizedRuntimePrivacyPolicy | null,
|
|
4264
4292
|
db?: RuntimePrivacyDbContext | null,
|
|
4265
4293
|
) {
|
|
4294
|
+
value = constrainLiveCapture(value, cfg, target);
|
|
4266
4295
|
if (shouldCapturePrivacyRaw(cfg)) {
|
|
4267
4296
|
return value;
|
|
4268
4297
|
}
|
|
@@ -4342,6 +4371,7 @@ function materializeInlinePrivacyValue(
|
|
|
4342
4371
|
db?: RuntimePrivacyDbContext | null,
|
|
4343
4372
|
captureScope?: RequestCaptureScopeState | null,
|
|
4344
4373
|
): InlinePrivacyMaterializationResult {
|
|
4374
|
+
value = constrainLiveCapture(value, cfg, target, captureScope);
|
|
4345
4375
|
if (value === undefined) {
|
|
4346
4376
|
return { value };
|
|
4347
4377
|
}
|
|
@@ -4381,6 +4411,7 @@ async function materializeInlinePrivacyValueAsync(
|
|
|
4381
4411
|
db?: RuntimePrivacyDbContext | null,
|
|
4382
4412
|
captureScope?: RequestCaptureScopeState | null,
|
|
4383
4413
|
): Promise<InlinePrivacyMaterializationResult> {
|
|
4414
|
+
value = constrainLiveCapture(value, cfg, target, captureScope);
|
|
4384
4415
|
if (value === undefined) {
|
|
4385
4416
|
return { value };
|
|
4386
4417
|
}
|
|
@@ -5549,6 +5580,9 @@ function createCapturedValueEntry(
|
|
|
5549
5580
|
): { captureRef: TraceValueCaptureRef; entry?: TraceValueBatchEntry } | undefined {
|
|
5550
5581
|
if (!__FULL_VALUE_CAPTURE_ENABLED) return undefined;
|
|
5551
5582
|
|
|
5583
|
+
const limitedRaw = constrainLiveCapture(params.rawValue, params.capture.runtimeConfig, params.target);
|
|
5584
|
+
const omissions = isLiveCapture(params.capture.runtimeConfig) ? collectCaptureOmissions(limitedRaw) : [];
|
|
5585
|
+
|
|
5552
5586
|
const preferredLimits: FullValueCloneLimits = {
|
|
5553
5587
|
maxDepth: TRACE_FULL_VALUE_MAX_DEPTH,
|
|
5554
5588
|
maxKeys: TRACE_FULL_VALUE_MAX_KEYS,
|
|
@@ -5558,7 +5592,7 @@ function createCapturedValueEntry(
|
|
|
5558
5592
|
const buildMaskedCandidate = (limits: FullValueCloneLimits) => {
|
|
5559
5593
|
const projected = params.target === 'request.headers'
|
|
5560
5594
|
? sanitizeHeaders(params.rawValue, params.capture.captureHeaders)
|
|
5561
|
-
: cloneFullValueCandidate(
|
|
5595
|
+
: cloneFullValueCandidate(limitedRaw, params.capture, limits);
|
|
5562
5596
|
if (projected === undefined) return undefined;
|
|
5563
5597
|
return applyPrivacyThenMask(
|
|
5564
5598
|
params.target,
|
|
@@ -5579,7 +5613,7 @@ function createCapturedValueEntry(
|
|
|
5579
5613
|
}
|
|
5580
5614
|
|
|
5581
5615
|
const candidateSerialized = stableSerializeTraceValue(candidate);
|
|
5582
|
-
if (!candidateSerialized || candidateSerialized === previewSerialized) {
|
|
5616
|
+
if (!candidateSerialized || (candidateSerialized === previewSerialized && !omissions.length)) {
|
|
5583
5617
|
return undefined;
|
|
5584
5618
|
}
|
|
5585
5619
|
|
|
@@ -5605,6 +5639,7 @@ function createCapturedValueEntry(
|
|
|
5605
5639
|
status: 'stored',
|
|
5606
5640
|
valueId,
|
|
5607
5641
|
projectedKind: entry.projectedKind,
|
|
5642
|
+
...(omissions.length ? { complete: false, omissions } : {}),
|
|
5608
5643
|
},
|
|
5609
5644
|
entry,
|
|
5610
5645
|
};
|
|
@@ -5635,6 +5670,9 @@ async function createCapturedValueEntryAsync(
|
|
|
5635
5670
|
): Promise<{ captureRef: TraceValueCaptureRef; entry?: TraceValueBatchEntry } | undefined> {
|
|
5636
5671
|
if (!__FULL_VALUE_CAPTURE_ENABLED) return undefined;
|
|
5637
5672
|
|
|
5673
|
+
const limitedRaw = constrainLiveCapture(params.rawValue, params.capture.runtimeConfig, params.target);
|
|
5674
|
+
const omissions = isLiveCapture(params.capture.runtimeConfig) ? collectCaptureOmissions(limitedRaw) : [];
|
|
5675
|
+
|
|
5638
5676
|
const preferredLimits: FullValueCloneLimits = {
|
|
5639
5677
|
maxDepth: TRACE_FULL_VALUE_MAX_DEPTH,
|
|
5640
5678
|
maxKeys: TRACE_FULL_VALUE_MAX_KEYS,
|
|
@@ -5644,7 +5682,7 @@ async function createCapturedValueEntryAsync(
|
|
|
5644
5682
|
const buildMaskedCandidate = async (limits: FullValueCloneLimits) => {
|
|
5645
5683
|
const projected = params.target === 'request.headers'
|
|
5646
5684
|
? sanitizeHeaders(params.rawValue, params.capture.captureHeaders)
|
|
5647
|
-
: await cloneFullValueCandidateAsync(
|
|
5685
|
+
: await cloneFullValueCandidateAsync(limitedRaw, params.capture, limits);
|
|
5648
5686
|
if (projected === undefined) return undefined;
|
|
5649
5687
|
return applyPrivacyThenMaskAsync(
|
|
5650
5688
|
params.target,
|
|
@@ -5665,7 +5703,7 @@ async function createCapturedValueEntryAsync(
|
|
|
5665
5703
|
}
|
|
5666
5704
|
|
|
5667
5705
|
const candidateSerialized = stableSerializeTraceValue(candidate);
|
|
5668
|
-
if (!candidateSerialized || candidateSerialized === previewSerialized) {
|
|
5706
|
+
if (!candidateSerialized || (candidateSerialized === previewSerialized && !omissions.length)) {
|
|
5669
5707
|
return undefined;
|
|
5670
5708
|
}
|
|
5671
5709
|
|
|
@@ -5691,6 +5729,7 @@ async function createCapturedValueEntryAsync(
|
|
|
5691
5729
|
status: 'stored',
|
|
5692
5730
|
valueId,
|
|
5693
5731
|
projectedKind: entry.projectedKind,
|
|
5732
|
+
...(omissions.length ? { complete: false, omissions } : {}),
|
|
5694
5733
|
},
|
|
5695
5734
|
entry,
|
|
5696
5735
|
};
|
|
@@ -5892,6 +5931,8 @@ async function maybeCaptureDbValueFromCandidatesAsync(
|
|
|
5892
5931
|
// reproMiddleware — unchanged behavior + passive per-request trace
|
|
5893
5932
|
// ===================================================================
|
|
5894
5933
|
export type ReproMiddlewareConfig = IngestClientConfig & {
|
|
5934
|
+
/** Propagated recording mode. A tagged staging request never inherits live limits. */
|
|
5935
|
+
captureMode?: 'live' | 'staging';
|
|
5895
5936
|
/** API base used for backend-owned live session documents. Defaults to REPRO_API_BASE_URL/REPRO_API_BASE/privacy.apiBase. */
|
|
5896
5937
|
apiBase?: string;
|
|
5897
5938
|
/** Configure header capture/masking. Defaults to capturing with sensitive headers masked. */
|
|
@@ -5923,6 +5964,11 @@ export type ReproLiveActorDecision = ReproLiveActorArgs & {
|
|
|
5923
5964
|
};
|
|
5924
5965
|
|
|
5925
5966
|
export type ReproLiveModeConfig = {
|
|
5967
|
+
/** Stable chain sampling. Runtime updates apply to new chains; targets are evaluated on each claim. */
|
|
5968
|
+
liveCapture?: {
|
|
5969
|
+
sampleRate?: number;
|
|
5970
|
+
targets?: { actorIds?: string[]; labels?: Record<string, string>; expiresAt: number }[];
|
|
5971
|
+
};
|
|
5926
5972
|
enabled?: boolean;
|
|
5927
5973
|
backendOnly?: boolean;
|
|
5928
5974
|
/**
|
|
@@ -6112,6 +6158,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
6112
6158
|
const querySid = readRequestQueryParam(req, REPRO_QUERY_SESSION_PARAM);
|
|
6113
6159
|
const queryAid = readRequestQueryParam(req, REPRO_QUERY_ACTION_PARAM);
|
|
6114
6160
|
const queryTs = readRequestQueryNumber(req, REPRO_QUERY_START_PARAM);
|
|
6161
|
+
const queryCaptureMode = readRequestQueryParam(req, '__repro_mode');
|
|
6115
6162
|
if (querySid || queryAid || queryTs !== null) {
|
|
6116
6163
|
stripReproQueryParams(req);
|
|
6117
6164
|
}
|
|
@@ -6181,6 +6228,11 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
6181
6228
|
}],
|
|
6182
6229
|
}, { priority: 'high' });
|
|
6183
6230
|
}
|
|
6231
|
+
const captureMode = backendLiveContext || req.headers['x-repro-capture-mode'] === 'live' || queryCaptureMode === 'live'
|
|
6232
|
+
? 'live' : 'staging';
|
|
6233
|
+
const modeConfig = { ...captureCfg, captureMode } as ReproMiddlewareConfig;
|
|
6234
|
+
shareRuntimePrivacyState(captureCfg, modeConfig);
|
|
6235
|
+
captureCfg = modeConfig;
|
|
6184
6236
|
const resolvePrivacy = (): NormalizedRuntimePrivacyPolicy | null =>
|
|
6185
6237
|
getRuntimePrivacyState(captureCfg).policy ?? null;
|
|
6186
6238
|
|
|
@@ -7745,6 +7797,7 @@ function patchNodeFetchForPropagation() {
|
|
|
7745
7797
|
if (runtime.aid && !hasHttpHeader(headers, KAFKA_HEADER_ACTION_ID)) {
|
|
7746
7798
|
setHttpHeader(headers, KAFKA_HEADER_ACTION_ID, runtime.aid);
|
|
7747
7799
|
}
|
|
7800
|
+
if (runtime.sid) setHttpHeader(headers, 'x-repro-capture-mode', getCtx().captureScope?.liveCapture ? 'live' : 'staging');
|
|
7748
7801
|
if (runtime.traceId && !hasHttpHeader(headers, KAFKA_HEADER_TRACE_ID)) {
|
|
7749
7802
|
setHttpHeader(headers, KAFKA_HEADER_TRACE_ID, runtime.traceId);
|
|
7750
7803
|
}
|
|
@@ -7799,6 +7852,7 @@ function patchNodeHttpModuleRequest(mod: any) {
|
|
|
7799
7852
|
maybeSet(REQUEST_START_HEADER, String(alignedNow()));
|
|
7800
7853
|
maybeSet(KAFKA_HEADER_SESSION_ID, runtime.sid);
|
|
7801
7854
|
maybeSet(KAFKA_HEADER_ACTION_ID, runtime.aid);
|
|
7855
|
+
if (runtime.sid) maybeSet('x-repro-capture-mode', getCtx().captureScope?.liveCapture ? 'live' : 'staging');
|
|
7802
7856
|
maybeSet(KAFKA_HEADER_TRACE_ID, runtime.traceId);
|
|
7803
7857
|
maybeSet(KAFKA_HEADER_PARENT_SPAN_ID, runtime.parentSpanId);
|
|
7804
7858
|
maybeSet(KAFKA_HEADER_REQUEST_ID, runtime.requestRid);
|
|
@@ -7826,6 +7880,7 @@ type KafkaHeaderValue = string | Buffer | null | undefined;
|
|
|
7826
7880
|
type KafkaHeaderMap = Record<string, KafkaHeaderValue>;
|
|
7827
7881
|
type KafkaContextResolver = () => { sid?: string; aid?: string } | undefined;
|
|
7828
7882
|
type KafkaPropagationContext = {
|
|
7883
|
+
captureMode?: 'live' | 'staging';
|
|
7829
7884
|
sid: string | null;
|
|
7830
7885
|
aid: string | null;
|
|
7831
7886
|
traceId: string | null;
|
|
@@ -7990,6 +8045,11 @@ async function materializeKafkaRequestBody(
|
|
|
7990
8045
|
body: Record<string, any>,
|
|
7991
8046
|
captureScope?: RequestCaptureScopeState | null,
|
|
7992
8047
|
): Promise<InlinePrivacyMaterializationResult> {
|
|
8048
|
+
// Broker bodies have no separate full-value reference. Returning the trace
|
|
8049
|
+
// inline preview here would permanently replace message arrays with 20 items.
|
|
8050
|
+
if (shouldCapturePrivacyRaw(cfg)) {
|
|
8051
|
+
return { value: constrainLiveCapture(sanitizeInlinePrivacyValue(body), cfg, 'request.body', captureScope) };
|
|
8052
|
+
}
|
|
7993
8053
|
return materializeInlinePrivacyValueAsync(
|
|
7994
8054
|
'request.body',
|
|
7995
8055
|
sanitizeInlinePrivacyValue(body),
|
|
@@ -8053,7 +8113,8 @@ function resolveKafkaContext(cfg: KafkaJsPatchConfig, headers: any): KafkaPropag
|
|
|
8053
8113
|
null;
|
|
8054
8114
|
const requestRid = readKafkaHeader(headers, KAFKA_HEADER_REQUEST_ID);
|
|
8055
8115
|
|
|
8056
|
-
return { sid, aid, traceId, parentSpanId, requestRid
|
|
8116
|
+
return { sid, aid, traceId, parentSpanId, requestRid,
|
|
8117
|
+
captureMode: readKafkaHeader(headers, 'x-repro-capture-mode') === 'live' || ctx.captureScope?.liveCapture ? 'live' : 'staging' };
|
|
8057
8118
|
}
|
|
8058
8119
|
|
|
8059
8120
|
function withOptionalTraceContext<T>(traceId: string | null, fn: () => Promise<T>): Promise<T> {
|
|
@@ -8420,6 +8481,7 @@ function patchKafkaProducerInstance(producer: any, cfg: KafkaJsPatchConfig) {
|
|
|
8420
8481
|
[KAFKA_HEADER_REQUEST_ID]: publishRid,
|
|
8421
8482
|
[KAFKA_HEADER_SESSION_ID]: sid,
|
|
8422
8483
|
[KAFKA_HEADER_ACTION_ID]: aid,
|
|
8484
|
+
'x-repro-capture-mode': ctx.captureScope?.liveCapture ? 'live' : 'staging',
|
|
8423
8485
|
}),
|
|
8424
8486
|
};
|
|
8425
8487
|
});
|
|
@@ -8552,6 +8614,7 @@ function patchKafkaProducerInstance(producer: any, cfg: KafkaJsPatchConfig) {
|
|
|
8552
8614
|
[KAFKA_HEADER_REQUEST_ID]: publishRid,
|
|
8553
8615
|
[KAFKA_HEADER_SESSION_ID]: sid,
|
|
8554
8616
|
[KAFKA_HEADER_ACTION_ID]: aid,
|
|
8617
|
+
'x-repro-capture-mode': ctx.captureScope?.liveCapture ? 'live' : 'staging',
|
|
8555
8618
|
}),
|
|
8556
8619
|
};
|
|
8557
8620
|
}),
|
|
@@ -8668,6 +8731,7 @@ function wrapKafkaEachMessageHandler(eachMessage: any, cfg: KafkaJsPatchConfig,
|
|
|
8668
8731
|
const consumeRid = nextRequestId(`${topic}-${message?.offset ?? alignedNow()}`);
|
|
8669
8732
|
const traceId = consumeRid;
|
|
8670
8733
|
const captureScope = createRequestCaptureScope(consumeRid, 'kafka', cfg);
|
|
8734
|
+
captureScope.liveCapture = runtime.captureMode === 'live';
|
|
8671
8735
|
emitRequestCaptureStart(cfg, sid, aid, captureScope);
|
|
8672
8736
|
const rawTraceEvents: any[] = [];
|
|
8673
8737
|
const maskReq: MaskRequestContext = {
|
|
@@ -8778,6 +8842,7 @@ function wrapKafkaEachBatchHandler(eachBatch: any, cfg: KafkaJsPatchConfig, cons
|
|
|
8778
8842
|
const consumeRid = nextRequestId(`${topic}-batch-${alignedNow()}`);
|
|
8779
8843
|
const traceId = consumeRid;
|
|
8780
8844
|
const captureScope = createRequestCaptureScope(consumeRid, 'kafka', cfg);
|
|
8845
|
+
captureScope.liveCapture = runtime.captureMode === 'live';
|
|
8781
8846
|
emitRequestCaptureStart(cfg, sid, aid, captureScope);
|
|
8782
8847
|
const rawTraceEvents: any[] = [];
|
|
8783
8848
|
const maskReq: MaskRequestContext = {
|
|
@@ -8972,6 +9037,7 @@ export function patchKafkaJs(cfg: KafkaJsPatchConfig) {
|
|
|
8972
9037
|
type AmqpHeaderMap = Record<string, any>;
|
|
8973
9038
|
type AmqpContextResolver = () => { sid?: string; aid?: string } | undefined;
|
|
8974
9039
|
type AmqpPropagationContext = {
|
|
9040
|
+
captureMode?: 'live' | 'staging';
|
|
8975
9041
|
sid: string | null;
|
|
8976
9042
|
aid: string | null;
|
|
8977
9043
|
traceId: string | null;
|
|
@@ -9240,7 +9306,8 @@ function resolveAmqpContext(cfg: AmqplibPatchConfig, headers: any): AmqpPropagat
|
|
|
9240
9306
|
null;
|
|
9241
9307
|
const requestRid = readAmqpHeader(headers, KAFKA_HEADER_REQUEST_ID);
|
|
9242
9308
|
|
|
9243
|
-
return { sid, aid, traceId, parentSpanId, requestRid
|
|
9309
|
+
return { sid, aid, traceId, parentSpanId, requestRid,
|
|
9310
|
+
captureMode: readAmqpHeader(headers, 'x-repro-capture-mode') === 'live' || ctx.captureScope?.liveCapture ? 'live' : 'staging' };
|
|
9244
9311
|
}
|
|
9245
9312
|
|
|
9246
9313
|
function withOptionalTraceContextSync<T>(traceId: string | null, fn: () => T): T {
|
|
@@ -9478,6 +9545,7 @@ function patchAmqplibPublishMethod(
|
|
|
9478
9545
|
[KAFKA_HEADER_REQUEST_ID]: publishRid,
|
|
9479
9546
|
[KAFKA_HEADER_SESSION_ID]: sid,
|
|
9480
9547
|
[KAFKA_HEADER_ACTION_ID]: aid,
|
|
9548
|
+
'x-repro-capture-mode': ctx.captureScope?.liveCapture ? 'live' : 'staging',
|
|
9481
9549
|
};
|
|
9482
9550
|
const shouldPropagate = hasAmqpPropagationValues(propagationValues);
|
|
9483
9551
|
const callbackIndex = args.length > 0 && typeof args[args.length - 1] === 'function'
|
|
@@ -9586,6 +9654,7 @@ function wrapAmqplibConsumeHandler(onMessage: any, cfg: AmqplibPatchConfig, queu
|
|
|
9586
9654
|
const consumeRid = nextRequestId(`${queueName}-${deliveryTag}`);
|
|
9587
9655
|
const traceId = consumeRid;
|
|
9588
9656
|
const captureScope = createRequestCaptureScope(consumeRid, 'amqp', cfg);
|
|
9657
|
+
captureScope.liveCapture = runtime.captureMode === 'live';
|
|
9589
9658
|
emitRequestCaptureStart(cfg, sid, aid, captureScope);
|
|
9590
9659
|
const rawTraceEvents: any[] = [];
|
|
9591
9660
|
const maskReq: MaskRequestContext = {
|