@reproapp/node-sdk 0.0.23 → 0.0.25

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/src/index.ts CHANGED
@@ -1158,7 +1158,7 @@ type Ctx = {
1158
1158
 
1159
1159
  type RequestCaptureScopeState = {
1160
1160
  requestRid: string;
1161
- transport: 'http' | 'kafka' | 'background';
1161
+ transport: 'http' | 'kafka' | 'amqp' | 'background';
1162
1162
  backendRequestCount: number;
1163
1163
  traceBatchCount: number;
1164
1164
  dbChangeCount: number;
@@ -1921,11 +1921,12 @@ function post(
1921
1921
  sessionId: string,
1922
1922
  body: any,
1923
1923
  options?: EnqueueIngestOptions,
1924
- ) {
1924
+ ): boolean {
1925
1925
  try {
1926
1926
  const entries = Array.isArray(body?.entries) ? (body.entries as LegacyEntry[]) : [];
1927
- enqueueIngestEntries(cfg, sessionId, entries, options);
1927
+ return enqueueIngestEntries(cfg, sessionId, entries, options);
1928
1928
  } catch { /* swallow in SDK */ }
1929
+ return false;
1929
1930
  }
1930
1931
 
1931
1932
  function toIngestConfig(cfg: IngestClientConfig): IngestClientConfig {
@@ -5858,9 +5859,6 @@ function emitDbQuery(cfg: any, sid?: string, aid?: string, payload?: any) {
5858
5859
  const ctx = getCtx() as Ctx;
5859
5860
  const spanContext = payload?.spanContext ?? captureSpanContextFromTracer();
5860
5861
  if (!shouldCaptureDbSpan(spanContext)) return;
5861
- if (ctx.captureScope) {
5862
- ctx.captureScope.dbChangeCount += 1;
5863
- }
5864
5862
  const runCtx: Ctx = {
5865
5863
  sid: ctx.sid,
5866
5864
  aid: ctx.aid,
@@ -5978,7 +5976,7 @@ async function emitDbQueryAsync(cfg: any, sid?: string, aid?: string, payload?:
5978
5976
  resultMetaMaterialization: resultMetaMaterialization.skipped,
5979
5977
  errorMaterialization: errorMaterialization.skipped,
5980
5978
  }, spanContext);
5981
- post(cfg, sid, {
5979
+ const enqueued = post(cfg, sid, {
5982
5980
  entries: [{
5983
5981
  actionId: aid ?? null,
5984
5982
  requestRid: ctx.rid ?? null,
@@ -5987,6 +5985,9 @@ async function emitDbQueryAsync(cfg: any, sid?: string, aid?: string, payload?:
5987
5985
  t: payload.t,
5988
5986
  }]
5989
5987
  });
5988
+ if (enqueued && ctx.captureScope) {
5989
+ ctx.captureScope.dbChangeCount += 1;
5990
+ }
5990
5991
  }
5991
5992
 
5992
5993
  function buildMinimalUpdate(before: any, after: any) {
@@ -6418,7 +6419,7 @@ function cloneKafkaTelemetryValue(value: any): any {
6418
6419
  }
6419
6420
 
6420
6421
  async function materializeKafkaRequestBody(
6421
- cfg: KafkaJsPatchConfig,
6422
+ cfg: ReproMiddlewareConfig,
6422
6423
  maskReq: MaskRequestContext,
6423
6424
  body: Record<string, any>,
6424
6425
  ): Promise<InlinePrivacyMaterializationResult> {
@@ -7379,6 +7380,871 @@ export function patchKafkaJs(cfg: KafkaJsPatchConfig) {
7379
7380
  }
7380
7381
  }
7381
7382
 
7383
+ type AmqpHeaderMap = Record<string, any>;
7384
+ type AmqpContextResolver = () => { sid?: string; aid?: string } | undefined;
7385
+ type AmqpPropagationContext = {
7386
+ sid: string | null;
7387
+ aid: string | null;
7388
+ traceId: string | null;
7389
+ parentSpanId: string | null;
7390
+ requestRid: string | null;
7391
+ };
7392
+
7393
+ export type AmqplibPatchConfig = ReproMiddlewareConfig & {
7394
+ resolveContext?: AmqpContextResolver;
7395
+ };
7396
+
7397
+ function decodeAmqpHeaderValue(value: any): string | null {
7398
+ if (value === null || value === undefined) return null;
7399
+ if (typeof value === 'string') {
7400
+ const trimmed = value.trim();
7401
+ return trimmed.length ? trimmed : null;
7402
+ }
7403
+ if (Buffer.isBuffer(value)) {
7404
+ const decoded = value.toString('utf8').trim();
7405
+ return decoded.length ? decoded : null;
7406
+ }
7407
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
7408
+ return String(value);
7409
+ }
7410
+ return null;
7411
+ }
7412
+
7413
+ function readAmqpHeader(headers: any, key: string): string | null {
7414
+ if (!headers || typeof headers !== 'object') return null;
7415
+ const direct = decodeAmqpHeaderValue((headers as Record<string, any>)[key]);
7416
+ if (direct) return direct;
7417
+ const normalized = key.toLowerCase();
7418
+ for (const [entryKey, entryValue] of Object.entries(headers as Record<string, any>)) {
7419
+ if (entryKey.toLowerCase() !== normalized) continue;
7420
+ return decodeAmqpHeaderValue(entryValue);
7421
+ }
7422
+ return null;
7423
+ }
7424
+
7425
+ function cloneAmqpHeaders(headers: any): AmqpHeaderMap {
7426
+ if (!headers || typeof headers !== 'object' || Buffer.isBuffer(headers)) return {};
7427
+ const out: AmqpHeaderMap = {};
7428
+ for (const [key, value] of Object.entries(headers as Record<string, any>)) {
7429
+ out[key] = value;
7430
+ }
7431
+ return out;
7432
+ }
7433
+
7434
+ function withAmqpPropagationHeaders(
7435
+ headers: any,
7436
+ values: Record<string, string | null | undefined>,
7437
+ ): AmqpHeaderMap {
7438
+ const out = cloneAmqpHeaders(headers);
7439
+ for (const [key, value] of Object.entries(values)) {
7440
+ if (value === null || value === undefined) continue;
7441
+ const str = String(value).trim();
7442
+ if (!str.length) continue;
7443
+ out[key] = str;
7444
+ }
7445
+ return out;
7446
+ }
7447
+
7448
+ function normalizeAmqpHeadersForPayload(headers: any): Record<string, any> {
7449
+ const out: Record<string, any> = {};
7450
+ if (!headers || typeof headers !== 'object' || Buffer.isBuffer(headers)) return out;
7451
+ for (const [key, value] of Object.entries(headers as Record<string, any>)) {
7452
+ const decoded = decodeAmqpHeaderValue(value);
7453
+ if (decoded !== null) {
7454
+ out[key] = decoded;
7455
+ } else {
7456
+ const sanitized = sanitizeResultForMeta(value);
7457
+ if (sanitized !== undefined) out[key] = sanitized;
7458
+ }
7459
+ }
7460
+ return out;
7461
+ }
7462
+
7463
+ function hasAmqpPropagationValues(values: Record<string, string | null | undefined>): boolean {
7464
+ return Object.values(values).some((value) => {
7465
+ if (value === null || value === undefined) return false;
7466
+ return String(value).trim().length > 0;
7467
+ });
7468
+ }
7469
+
7470
+ function isAmqpOptionsObject(value: any): boolean {
7471
+ return Boolean(value && typeof value === 'object' && !Buffer.isBuffer(value) && typeof value !== 'function');
7472
+ }
7473
+
7474
+ function withAmqpPropagationOptions(
7475
+ options: any,
7476
+ values: Record<string, string | null | undefined>,
7477
+ ): Record<string, any> {
7478
+ const base = isAmqpOptionsObject(options) ? { ...options } : {};
7479
+ return {
7480
+ ...base,
7481
+ headers: withAmqpPropagationHeaders(base.headers, values),
7482
+ };
7483
+ }
7484
+
7485
+ function amqpContentSize(content: any): number | null {
7486
+ if (content === null || content === undefined) return null;
7487
+ if (typeof content === 'string') return Buffer.byteLength(content, 'utf8');
7488
+ if (Buffer.isBuffer(content)) return content.length;
7489
+ if (content instanceof Uint8Array) return content.byteLength;
7490
+ return null;
7491
+ }
7492
+
7493
+ function decodeAmqpContent(content: any): any {
7494
+ if (content === null || content === undefined) return null;
7495
+ if (typeof content === 'string') return content;
7496
+ if (Buffer.isBuffer(content)) return content.toString('utf8');
7497
+ if (content instanceof Uint8Array) return Buffer.from(content).toString('utf8');
7498
+ if (typeof content === 'number' || typeof content === 'boolean') return content;
7499
+ return sanitizeResultForMeta(content);
7500
+ }
7501
+
7502
+ function contentTypeLooksJson(contentType: any): boolean {
7503
+ return typeof contentType === 'string' && /\bjson\b/i.test(contentType);
7504
+ }
7505
+
7506
+ function parseAmqpJsonString(
7507
+ value: string,
7508
+ contentType?: any,
7509
+ ): { parsed: true; value: any } | { parsed: false; value: string } {
7510
+ const trimmed = value.trim();
7511
+ if (!trimmed) return { parsed: false, value };
7512
+ if (!contentTypeLooksJson(contentType) && !'{['.includes(trimmed[0]!)) {
7513
+ return { parsed: false, value };
7514
+ }
7515
+ try {
7516
+ return { parsed: true, value: JSON.parse(value) };
7517
+ } catch {
7518
+ return { parsed: false, value };
7519
+ }
7520
+ }
7521
+
7522
+ function decodeAmqpMessageContent(content: any, propertiesOrOptions?: any): {
7523
+ value: any;
7524
+ rawValue?: string;
7525
+ valueEncoding: string | null;
7526
+ } {
7527
+ const decoded = decodeAmqpContent(content);
7528
+ if (typeof decoded !== 'string') {
7529
+ return {
7530
+ value: decoded,
7531
+ valueEncoding: decoded === null ? null : typeof decoded,
7532
+ };
7533
+ }
7534
+ const parsed = parseAmqpJsonString(decoded, propertiesOrOptions?.contentType);
7535
+ if (parsed.parsed) {
7536
+ return {
7537
+ value: parsed.value,
7538
+ rawValue: decoded,
7539
+ valueEncoding: 'json',
7540
+ };
7541
+ }
7542
+ return {
7543
+ value: decoded,
7544
+ valueEncoding: Buffer.isBuffer(content) || content instanceof Uint8Array ? 'utf8' : 'string',
7545
+ };
7546
+ }
7547
+
7548
+ function buildAmqpMessagePayload(content: any, propertiesOrOptions?: any): Record<string, any> {
7549
+ const decoded = decodeAmqpMessageContent(content, propertiesOrOptions);
7550
+ const payload: Record<string, any> = {
7551
+ value: decoded.value,
7552
+ valueEncoding: decoded.valueEncoding,
7553
+ valueBytes: amqpContentSize(content),
7554
+ headers: normalizeAmqpHeadersForPayload(propertiesOrOptions?.headers),
7555
+ };
7556
+ if (decoded.rawValue !== undefined) {
7557
+ payload.rawValue = decoded.rawValue;
7558
+ }
7559
+ if (propertiesOrOptions?.contentType !== undefined) {
7560
+ payload.contentType = propertiesOrOptions.contentType;
7561
+ }
7562
+ if (propertiesOrOptions?.contentEncoding !== undefined) {
7563
+ payload.contentEncoding = propertiesOrOptions.contentEncoding;
7564
+ }
7565
+ if (propertiesOrOptions?.messageId !== undefined) {
7566
+ payload.messageId = propertiesOrOptions.messageId;
7567
+ }
7568
+ if (propertiesOrOptions?.correlationId !== undefined) {
7569
+ payload.correlationId = propertiesOrOptions.correlationId;
7570
+ }
7571
+ if (propertiesOrOptions?.replyTo !== undefined) {
7572
+ payload.replyTo = propertiesOrOptions.replyTo;
7573
+ }
7574
+ if (propertiesOrOptions?.type !== undefined) {
7575
+ payload.type = propertiesOrOptions.type;
7576
+ }
7577
+ return payload;
7578
+ }
7579
+
7580
+ function buildAmqpMessagePropertiesPayload(properties: any): Record<string, any> {
7581
+ if (!properties || typeof properties !== 'object') return {};
7582
+ const payload: Record<string, any> = {};
7583
+ const copyKeys = [
7584
+ 'contentType',
7585
+ 'contentEncoding',
7586
+ 'deliveryMode',
7587
+ 'priority',
7588
+ 'correlationId',
7589
+ 'replyTo',
7590
+ 'expiration',
7591
+ 'messageId',
7592
+ 'timestamp',
7593
+ 'type',
7594
+ 'userId',
7595
+ 'appId',
7596
+ 'clusterId',
7597
+ ];
7598
+ for (const key of copyKeys) {
7599
+ if (properties[key] !== undefined) payload[key] = sanitizeResultForMeta(properties[key]);
7600
+ }
7601
+ payload.headers = normalizeAmqpHeadersForPayload(properties.headers);
7602
+ return payload;
7603
+ }
7604
+
7605
+ function sanitizeAmqpQueue(queue: any): string {
7606
+ const value = typeof queue === 'string' ? queue.trim() : '';
7607
+ return value.length ? value : 'unknown-queue';
7608
+ }
7609
+
7610
+ function sanitizeAmqpExchange(exchange: any): string {
7611
+ if (exchange === undefined || exchange === null) return '';
7612
+ const value = typeof exchange === 'string' ? exchange.trim() : String(exchange).trim();
7613
+ return value;
7614
+ }
7615
+
7616
+ function sanitizeAmqpRoutingKey(routingKey: any): string {
7617
+ const value = typeof routingKey === 'string' ? routingKey.trim() : '';
7618
+ return value.length ? value : 'unknown-routing-key';
7619
+ }
7620
+
7621
+ function summarizeAmqpError(error: any): Record<string, unknown> {
7622
+ const message =
7623
+ typeof error?.message === 'string'
7624
+ ? error.message
7625
+ : error === null || error === undefined
7626
+ ? 'Unknown AMQP error'
7627
+ : String(error);
7628
+ return {
7629
+ name: typeof error?.name === 'string' ? error.name : undefined,
7630
+ message,
7631
+ code: error?.code ?? undefined,
7632
+ };
7633
+ }
7634
+
7635
+ function resolveAmqpContext(cfg: AmqplibPatchConfig, headers: any): AmqpPropagationContext {
7636
+ const ctx = getCtx();
7637
+ const fallback = cfg.resolveContext?.() ?? {};
7638
+ const tracerSpan = captureSpanContextFromTracer();
7639
+
7640
+ const sid = readAmqpHeader(headers, KAFKA_HEADER_SESSION_ID) ?? ctx.sid ?? fallback.sid ?? null;
7641
+ const aid = readAmqpHeader(headers, KAFKA_HEADER_ACTION_ID) ?? ctx.aid ?? fallback.aid ?? null;
7642
+ const traceId =
7643
+ readAmqpHeader(headers, KAFKA_HEADER_TRACE_ID) ??
7644
+ tracerSpan?.traceId ??
7645
+ __TRACER__?.getCurrentTraceId?.() ??
7646
+ null;
7647
+ const parentSpanId =
7648
+ readAmqpHeader(headers, KAFKA_HEADER_SPAN_ID) ??
7649
+ readAmqpHeader(headers, KAFKA_HEADER_PARENT_SPAN_ID) ??
7650
+ null;
7651
+ const requestRid = readAmqpHeader(headers, KAFKA_HEADER_REQUEST_ID);
7652
+
7653
+ return { sid, aid, traceId, parentSpanId, requestRid };
7654
+ }
7655
+
7656
+ function withOptionalTraceContextSync<T>(traceId: string | null, fn: () => T): T {
7657
+ const tracerWithTrace = (__TRACER__ as (TracerApi & { withTrace?: <R>(id: string, runner: () => R) => R }) | null)?.withTrace;
7658
+ if (traceId && typeof tracerWithTrace === 'function') {
7659
+ let invoked = false;
7660
+ try {
7661
+ return tracerWithTrace(traceId, () => {
7662
+ invoked = true;
7663
+ return fn();
7664
+ });
7665
+ } catch (error) {
7666
+ if (invoked) throw error;
7667
+ return fn();
7668
+ }
7669
+ }
7670
+ return fn();
7671
+ }
7672
+
7673
+ function buildAmqpTraceEntries(
7674
+ actionId: string | null | undefined,
7675
+ requestRid: string,
7676
+ events: TraceEventRecord[],
7677
+ ): LegacyEntry[] {
7678
+ return buildKafkaTraceEntries(actionId, requestRid, events);
7679
+ }
7680
+
7681
+ function scheduleAmqpTraceFlush(
7682
+ cfg: AmqplibPatchConfig,
7683
+ amqpCtx: Ctx,
7684
+ actionId: string | null,
7685
+ requestRid: string,
7686
+ rawTraceEvents: any[],
7687
+ maskReq: MaskRequestContext,
7688
+ captureScope: RequestCaptureScopeState,
7689
+ ): void {
7690
+ const sid = amqpCtx.sid;
7691
+ if (!sid) return;
7692
+ const rawEvents = rawTraceEvents.slice();
7693
+
7694
+ scheduleSdkBackgroundWork(async () => {
7695
+ await als.run(amqpCtx, async () => {
7696
+ await drainPrivacyTasks(amqpCtx);
7697
+ const pendingEvents: PendingTraceEventRecord[] = [];
7698
+ for (const rawTraceEvent of rawEvents) {
7699
+ const evt = buildPendingKafkaTraceEvent(rawTraceEvent);
7700
+ if (evt) pendingEvents.push(evt);
7701
+ }
7702
+
7703
+ let entries: LegacyEntry[] = [];
7704
+ if (pendingEvents.length) {
7705
+ const masking = normalizeMaskingConfig(cfg.masking);
7706
+ const preparedEvents = preparePendingTraceEventsForFlush(pendingEvents);
7707
+ const materializedEvents = await materializePendingTraceEventsAsync(preparedEvents, {
7708
+ cfg,
7709
+ maskReq,
7710
+ masking,
7711
+ resolvePrivacy: () => getRuntimePrivacyState(cfg).policy ?? null,
7712
+ });
7713
+ entries = buildAmqpTraceEntries(actionId, requestRid, materializedEvents);
7714
+ captureScope.traceBatchCount = entries.length;
7715
+ if (entries.length) {
7716
+ post(cfg, sid, { entries });
7717
+ }
7718
+ } else {
7719
+ captureScope.traceBatchCount = 0;
7720
+ }
7721
+
7722
+ post(cfg, sid, {
7723
+ entries: [{
7724
+ actionId: actionId ?? null,
7725
+ requestRid,
7726
+ requestCaptureManifest: buildRequestCaptureManifestPayload(
7727
+ captureScope,
7728
+ entries.length,
7729
+ ),
7730
+ t: alignedNow(),
7731
+ }],
7732
+ });
7733
+ });
7734
+ }, { priority: 'high' });
7735
+ }
7736
+
7737
+ function setAmqpPublishOptionsArg(
7738
+ args: any[],
7739
+ optionsIndex: number,
7740
+ nextOptions: Record<string, any>,
7741
+ callbackIndex: number,
7742
+ wrappedCallback?: (...callbackArgs: any[]) => any,
7743
+ ): any[] {
7744
+ const nextArgs = args.slice();
7745
+ while (nextArgs.length <= optionsIndex) {
7746
+ nextArgs.push(undefined);
7747
+ }
7748
+ nextArgs[optionsIndex] = nextOptions;
7749
+ if (callbackIndex >= 0 && wrappedCallback) {
7750
+ if (callbackIndex === optionsIndex) {
7751
+ nextArgs[optionsIndex + 1] = wrappedCallback;
7752
+ } else {
7753
+ nextArgs[callbackIndex] = wrappedCallback;
7754
+ }
7755
+ }
7756
+ return nextArgs;
7757
+ }
7758
+
7759
+ function scheduleAmqpPublishCapture(params: {
7760
+ cfg: AmqplibPatchConfig;
7761
+ sid: string | null;
7762
+ aid: string | null;
7763
+ publishRid: string;
7764
+ captureScope: RequestCaptureScopeState;
7765
+ method: 'AMQP_PUBLISH' | 'AMQP_SEND_TO_QUEUE';
7766
+ path: string;
7767
+ key: string;
7768
+ exchange?: string;
7769
+ routingKey?: string;
7770
+ queue?: string;
7771
+ content: any;
7772
+ options: any;
7773
+ span: SpanContext | null;
7774
+ t0: number;
7775
+ status: number;
7776
+ error?: any;
7777
+ result?: any;
7778
+ }): void {
7779
+ if (!params.sid) return;
7780
+ const maskReq: MaskRequestContext = {
7781
+ method: params.method,
7782
+ path: params.path,
7783
+ key: params.key,
7784
+ };
7785
+ const messagePayload = buildAmqpMessagePayload(params.content, params.options);
7786
+ const body = {
7787
+ transport: 'amqp',
7788
+ exchange: params.exchange,
7789
+ routingKey: params.routingKey,
7790
+ queue: params.queue,
7791
+ messageBytes: amqpContentSize(params.content),
7792
+ message: messagePayload,
7793
+ value: cloneKafkaTelemetryValue(messagePayload.value),
7794
+ rawValue: messagePayload.rawValue,
7795
+ valueEncoding: messagePayload.valueEncoding,
7796
+ valueBytes: messagePayload.valueBytes,
7797
+ properties: buildAmqpMessagePropertiesPayload(params.options),
7798
+ traceId: params.span?.traceId ?? null,
7799
+ spanId: params.span?.spanId ?? null,
7800
+ parentSpanId: params.span?.parentSpanId ?? null,
7801
+ };
7802
+
7803
+ scheduleSdkBackgroundWork(async () => {
7804
+ const bodyMaterialization = await materializeKafkaRequestBody(params.cfg, maskReq, body);
7805
+ const requestPayload: Record<string, any> = {
7806
+ rid: params.publishRid,
7807
+ method: params.method,
7808
+ url: params.path,
7809
+ path: params.path,
7810
+ status: params.status,
7811
+ durMs: Date.now() - params.t0,
7812
+ key: params.key,
7813
+ headers: {
7814
+ transport: 'amqp',
7815
+ exchange: params.exchange,
7816
+ routingKey: params.routingKey,
7817
+ queue: params.queue,
7818
+ messageBytes: amqpContentSize(params.content),
7819
+ backpressure: params.result === false ? true : undefined,
7820
+ },
7821
+ body: bodyMaterialization.value,
7822
+ bodyMaterialization: bodyMaterialization.skipped,
7823
+ respBody: params.status >= 400
7824
+ ? { error: summarizeAmqpError(params.error) }
7825
+ : { accepted: params.result !== false, backpressure: params.result === false ? true : undefined },
7826
+ };
7827
+
7828
+ post(params.cfg, params.sid!, {
7829
+ entries: [{
7830
+ actionId: params.aid ?? null,
7831
+ request: requestPayload,
7832
+ t: alignedNow(),
7833
+ }],
7834
+ });
7835
+ params.captureScope.backendRequestCount += 1;
7836
+ post(params.cfg, params.sid!, {
7837
+ entries: [{
7838
+ actionId: params.aid ?? null,
7839
+ requestRid: params.publishRid,
7840
+ requestCaptureManifest: buildRequestCaptureManifestPayload(
7841
+ params.captureScope,
7842
+ 0,
7843
+ ),
7844
+ t: alignedNow(),
7845
+ }],
7846
+ });
7847
+ }, { priority: 'high' });
7848
+ }
7849
+
7850
+ function patchAmqplibPublishMethod(
7851
+ channel: any,
7852
+ cfg: AmqplibPatchConfig,
7853
+ methodName: 'publish' | 'sendToQueue',
7854
+ ) {
7855
+ const orig = channel?.[methodName];
7856
+ if (typeof orig !== 'function' || orig.__repro_amqplib_patched) return;
7857
+
7858
+ const optionsIndex = methodName === 'publish' ? 3 : 2;
7859
+ const method = methodName === 'publish' ? 'AMQP_PUBLISH' : 'AMQP_SEND_TO_QUEUE';
7860
+
7861
+ const wrapped = function reproPatchedAmqplibPublish(this: any, ...args: any[]) {
7862
+ const ctx = getCtx();
7863
+ const fallback = cfg.resolveContext?.() ?? {};
7864
+ const sid = ctx.sid ?? fallback.sid ?? null;
7865
+ const aid = ctx.aid ?? fallback.aid ?? null;
7866
+ const span = captureSpanContextFromTracer();
7867
+ const publishRid = nextRequestId(alignedNow());
7868
+ const propagationValues = {
7869
+ [KAFKA_HEADER_TRACE_ID]: span?.traceId ?? null,
7870
+ [KAFKA_HEADER_SPAN_ID]: span?.spanId === null || span?.spanId === undefined ? null : String(span.spanId),
7871
+ [KAFKA_HEADER_PARENT_SPAN_ID]:
7872
+ span?.parentSpanId === null || span?.parentSpanId === undefined ? null : String(span.parentSpanId),
7873
+ [KAFKA_HEADER_REQUEST_ID]: publishRid,
7874
+ [KAFKA_HEADER_SESSION_ID]: sid,
7875
+ [KAFKA_HEADER_ACTION_ID]: aid,
7876
+ };
7877
+ const shouldPropagate = hasAmqpPropagationValues(propagationValues);
7878
+ const callbackIndex = args.length > 0 && typeof args[args.length - 1] === 'function'
7879
+ ? args.length - 1
7880
+ : -1;
7881
+ const originalOptions = callbackIndex === optionsIndex ? undefined : args[optionsIndex];
7882
+ const nextOptions = shouldPropagate
7883
+ ? withAmqpPropagationOptions(originalOptions, propagationValues)
7884
+ : originalOptions;
7885
+ const exchange = methodName === 'publish' ? sanitizeAmqpExchange(args[0]) : '';
7886
+ const routingKey = methodName === 'publish' ? sanitizeAmqpRoutingKey(args[1]) : undefined;
7887
+ const queue = methodName === 'sendToQueue' ? sanitizeAmqpQueue(args[0]) : undefined;
7888
+ const path = methodName === 'publish'
7889
+ ? `amqp://${exchange || 'default'}/${routingKey}`
7890
+ : `amqp://queue/${queue}`;
7891
+ const key = `${method} ${methodName === 'publish' ? `${exchange || 'default'}/${routingKey}` : queue}`;
7892
+ const content = methodName === 'publish' ? args[2] : args[1];
7893
+ const captureScope = createRequestCaptureScope(publishRid, 'amqp');
7894
+ const t0 = Date.now();
7895
+ let finished = false;
7896
+
7897
+ const finish = (status: number, error?: any, result?: any) => {
7898
+ if (finished) return;
7899
+ finished = true;
7900
+ scheduleAmqpPublishCapture({
7901
+ cfg,
7902
+ sid,
7903
+ aid,
7904
+ publishRid,
7905
+ captureScope,
7906
+ method,
7907
+ path,
7908
+ key,
7909
+ exchange,
7910
+ routingKey,
7911
+ queue,
7912
+ content,
7913
+ options: nextOptions,
7914
+ span,
7915
+ t0,
7916
+ status,
7917
+ error,
7918
+ result,
7919
+ });
7920
+ };
7921
+
7922
+ const originalCallback = callbackIndex >= 0 ? args[callbackIndex] : null;
7923
+ const wrappedCallback = typeof originalCallback === 'function'
7924
+ ? function reproAmqplibPublishCallback(this: any, ...callbackArgs: any[]) {
7925
+ const error = callbackArgs[0];
7926
+ finish(error ? 500 : 200, error, callbackArgs[1]);
7927
+ return originalCallback.apply(this, callbackArgs);
7928
+ }
7929
+ : undefined;
7930
+
7931
+ const nextArgs = shouldPropagate
7932
+ ? setAmqpPublishOptionsArg(args, optionsIndex, nextOptions, callbackIndex, wrappedCallback)
7933
+ : (wrappedCallback
7934
+ ? setAmqpPublishOptionsArg(args, optionsIndex, nextOptions, callbackIndex, wrappedCallback)
7935
+ : args);
7936
+
7937
+ try {
7938
+ const result = orig.apply(this, nextArgs);
7939
+ if (!wrappedCallback) {
7940
+ finish(200, undefined, result);
7941
+ }
7942
+ return result;
7943
+ } catch (error) {
7944
+ finish(500, error);
7945
+ throw error;
7946
+ }
7947
+ };
7948
+
7949
+ (wrapped as any).__repro_amqplib_patched = true;
7950
+ channel[methodName] = wrapped;
7951
+ }
7952
+
7953
+ function wrapAmqplibConsumeHandler(onMessage: any, cfg: AmqplibPatchConfig, queue: any) {
7954
+ return function reproAmqplibConsumeMessage(this: any, message: any) {
7955
+ if (message === null || message === undefined) {
7956
+ return onMessage.call(this, message);
7957
+ }
7958
+
7959
+ const queueName = sanitizeAmqpQueue(queue);
7960
+ const properties = message?.properties ?? {};
7961
+ const headers = properties?.headers;
7962
+ const runtime = resolveAmqpContext(cfg, headers);
7963
+ const sid = runtime.sid;
7964
+ const aid = runtime.aid;
7965
+ const deliveryTag = message?.fields?.deliveryTag ?? alignedNow();
7966
+ const consumeRid = nextRequestId(`${queueName}-${deliveryTag}`);
7967
+ const traceId = consumeRid;
7968
+ const captureScope = createRequestCaptureScope(consumeRid, 'amqp');
7969
+ const rawTraceEvents: any[] = [];
7970
+ const maskReq: MaskRequestContext = {
7971
+ method: 'AMQP_CONSUME',
7972
+ path: `amqp://queue/${queueName}`,
7973
+ key: `AMQP_CONSUME ${queueName}`,
7974
+ };
7975
+ const amqpCtx: Ctx = {
7976
+ sid: sid ?? undefined,
7977
+ aid: aid ?? undefined,
7978
+ rid: consumeRid,
7979
+ clockSkewMs: 0,
7980
+ excludedSpanIds: new Set<string>(),
7981
+ maskReq,
7982
+ privacyTasks: new Set<Promise<void>>(),
7983
+ captureScope,
7984
+ };
7985
+ let unsubscribe: undefined | (() => void);
7986
+
7987
+ const subscribeTrace = () => {
7988
+ if (!__TRACER__?.tracer?.on) return;
7989
+ try {
7990
+ unsubscribe = __TRACER__.tracer.on((event: any) => {
7991
+ if (!event || event.traceId !== traceId) return;
7992
+ rawTraceEvents.push(event);
7993
+ });
7994
+ } catch {}
7995
+ };
7996
+ const unsubscribeTrace = () => {
7997
+ try { unsubscribe && unsubscribe(); } catch {}
7998
+ unsubscribe = undefined;
7999
+ };
8000
+
8001
+ const invoke = () => {
8002
+ subscribeTrace();
8003
+ try {
8004
+ const result = onMessage.call(this, message);
8005
+ if (result && typeof result.then === 'function') {
8006
+ return result.finally(() => {
8007
+ unsubscribeTrace();
8008
+ });
8009
+ }
8010
+ unsubscribeTrace();
8011
+ return result;
8012
+ } catch (error) {
8013
+ unsubscribeTrace();
8014
+ throw error;
8015
+ }
8016
+ };
8017
+
8018
+ const runInAls = () => {
8019
+ if (!sid && !aid) return invoke();
8020
+ return als.run(amqpCtx, () => invoke());
8021
+ };
8022
+
8023
+ const t0 = Date.now();
8024
+ let finished = false;
8025
+ const finish = (threw: boolean, error?: any) => {
8026
+ if (finished) return;
8027
+ finished = true;
8028
+ if (!sid) return;
8029
+ const fields = message?.fields ?? {};
8030
+ const messagePayload = buildAmqpMessagePayload(message?.content, properties);
8031
+ const body = {
8032
+ transport: 'amqp',
8033
+ queue: queueName,
8034
+ exchange: sanitizeAmqpExchange(fields.exchange),
8035
+ routingKey: sanitizeAmqpRoutingKey(fields.routingKey),
8036
+ deliveryTag: fields.deliveryTag ?? null,
8037
+ redelivered: fields.redelivered ?? null,
8038
+ consumerTag: fields.consumerTag ?? null,
8039
+ parentRequestRid: runtime.requestRid,
8040
+ parentTraceId: runtime.traceId,
8041
+ parentSpanId: runtime.parentSpanId,
8042
+ messageBytes: amqpContentSize(message?.content),
8043
+ message: messagePayload,
8044
+ value: cloneKafkaTelemetryValue(messagePayload.value),
8045
+ rawValue: messagePayload.rawValue,
8046
+ valueEncoding: messagePayload.valueEncoding,
8047
+ valueBytes: messagePayload.valueBytes,
8048
+ properties: buildAmqpMessagePropertiesPayload(properties),
8049
+ };
8050
+
8051
+ scheduleSdkBackgroundWork(async () => {
8052
+ const bodyMaterialization = await materializeKafkaRequestBody(cfg, maskReq, body);
8053
+ const requestPayload: Record<string, any> = {
8054
+ rid: consumeRid,
8055
+ method: 'AMQP_CONSUME',
8056
+ url: `amqp://queue/${queueName}`,
8057
+ path: `amqp://queue/${queueName}`,
8058
+ status: threw ? 500 : 200,
8059
+ durMs: Date.now() - t0,
8060
+ key: `AMQP_CONSUME ${queueName}`,
8061
+ headers: normalizeAmqpHeadersForPayload(headers),
8062
+ body: bodyMaterialization.value,
8063
+ bodyMaterialization: bodyMaterialization.skipped,
8064
+ respBody: threw ? { error: summarizeAmqpError(error) } : undefined,
8065
+ };
8066
+
8067
+ post(cfg, sid, {
8068
+ entries: [{
8069
+ actionId: aid ?? null,
8070
+ request: requestPayload,
8071
+ t: alignedNow(),
8072
+ }],
8073
+ });
8074
+ captureScope.backendRequestCount += 1;
8075
+ scheduleAmqpTraceFlush(cfg, amqpCtx, aid, consumeRid, rawTraceEvents, maskReq, captureScope);
8076
+ }, { priority: 'high' });
8077
+ };
8078
+
8079
+ try {
8080
+ const result = withOptionalTraceContextSync(traceId, runInAls);
8081
+ if (result && typeof (result as any).then === 'function') {
8082
+ return (result as Promise<any>).then(
8083
+ (value) => {
8084
+ finish(false);
8085
+ return value;
8086
+ },
8087
+ (error) => {
8088
+ finish(true, error);
8089
+ throw error;
8090
+ },
8091
+ );
8092
+ }
8093
+ finish(false);
8094
+ return result;
8095
+ } catch (error) {
8096
+ finish(true, error);
8097
+ throw error;
8098
+ }
8099
+ };
8100
+ }
8101
+
8102
+ function patchAmqplibChannel(channel: any, cfg: AmqplibPatchConfig) {
8103
+ if (!channel || channel.__repro_amqplib_channel_patched) return;
8104
+ channel.__repro_amqplib_channel_patched = true;
8105
+
8106
+ patchAmqplibPublishMethod(channel, cfg, 'publish');
8107
+ patchAmqplibPublishMethod(channel, cfg, 'sendToQueue');
8108
+
8109
+ const origConsume = channel.consume;
8110
+ if (typeof origConsume === 'function' && !origConsume.__repro_amqplib_patched) {
8111
+ const wrapped = function reproPatchedAmqplibConsume(this: any, queue: any, onMessage: any, ...rest: any[]) {
8112
+ if (typeof onMessage !== 'function') {
8113
+ return origConsume.call(this, queue, onMessage, ...rest);
8114
+ }
8115
+ return origConsume.call(this, queue, wrapAmqplibConsumeHandler(onMessage, cfg, queue), ...rest);
8116
+ };
8117
+ (wrapped as any).__repro_amqplib_patched = true;
8118
+ channel.consume = wrapped;
8119
+ }
8120
+ }
8121
+
8122
+ function wrapAmqplibChannelFactory(
8123
+ connection: any,
8124
+ cfg: AmqplibPatchConfig,
8125
+ methodName: 'createChannel' | 'createConfirmChannel',
8126
+ ) {
8127
+ const orig = connection?.[methodName];
8128
+ if (typeof orig !== 'function' || orig.__repro_amqplib_patched) return;
8129
+
8130
+ const wrapped = function reproPatchedAmqplibCreateChannel(this: any, ...args: any[]) {
8131
+ const callbackIndex = args.length > 0 && typeof args[args.length - 1] === 'function'
8132
+ ? args.length - 1
8133
+ : -1;
8134
+ const nextArgs = args.slice();
8135
+ if (callbackIndex >= 0) {
8136
+ const originalCallback = args[callbackIndex];
8137
+ nextArgs[callbackIndex] = function reproAmqplibCreateChannelCallback(this: any, error: any, channel: any, ...rest: any[]) {
8138
+ if (channel) patchAmqplibChannel(channel, cfg);
8139
+ return originalCallback.call(this, error, channel, ...rest);
8140
+ };
8141
+ }
8142
+
8143
+ const result = orig.apply(this, nextArgs);
8144
+ if (result && typeof result.then === 'function') {
8145
+ return result.then((channel: any) => {
8146
+ patchAmqplibChannel(channel, cfg);
8147
+ return channel;
8148
+ });
8149
+ }
8150
+ if (result) patchAmqplibChannel(result, cfg);
8151
+ return result;
8152
+ };
8153
+
8154
+ (wrapped as any).__repro_amqplib_patched = true;
8155
+ connection[methodName] = wrapped;
8156
+ }
8157
+
8158
+ function patchAmqplibConnection(connection: any, cfg: AmqplibPatchConfig) {
8159
+ if (!connection || connection.__repro_amqplib_connection_patched) return;
8160
+ connection.__repro_amqplib_connection_patched = true;
8161
+ wrapAmqplibChannelFactory(connection, cfg, 'createChannel');
8162
+ wrapAmqplibChannelFactory(connection, cfg, 'createConfirmChannel');
8163
+ }
8164
+
8165
+ function patchAmqplibModule(amqpModule: any, cfg: AmqplibPatchConfig) {
8166
+ if (!amqpModule || typeof amqpModule !== 'object') return;
8167
+ const targets = new Set<any>([amqpModule]);
8168
+ if (amqpModule.default && typeof amqpModule.default === 'object') {
8169
+ targets.add(amqpModule.default);
8170
+ }
8171
+
8172
+ for (const target of targets) {
8173
+ if (!target || target.__repro_amqplib_module_patched) continue;
8174
+ const origConnect = target.connect;
8175
+ if (typeof origConnect !== 'function') continue;
8176
+ target.__repro_amqplib_module_patched = true;
8177
+ target.connect = function reproPatchedAmqplibConnect(this: any, ...args: any[]) {
8178
+ const callbackIndex = args.length > 0 && typeof args[args.length - 1] === 'function'
8179
+ ? args.length - 1
8180
+ : -1;
8181
+ const nextArgs = args.slice();
8182
+ if (callbackIndex >= 0) {
8183
+ const originalCallback = args[callbackIndex];
8184
+ nextArgs[callbackIndex] = function reproAmqplibConnectCallback(this: any, error: any, connection: any, ...rest: any[]) {
8185
+ if (connection) patchAmqplibConnection(connection, cfg);
8186
+ return originalCallback.call(this, error, connection, ...rest);
8187
+ };
8188
+ }
8189
+
8190
+ const result = origConnect.apply(this, nextArgs);
8191
+ if (result && typeof result.then === 'function') {
8192
+ return result.then((connection: any) => {
8193
+ patchAmqplibConnection(connection, cfg);
8194
+ return connection;
8195
+ });
8196
+ }
8197
+ if (result) patchAmqplibConnection(result, cfg);
8198
+ return result;
8199
+ };
8200
+ }
8201
+ }
8202
+
8203
+ const amqplibPatchConfigs: AmqplibPatchConfig[] = [];
8204
+ let amqplibRequireHookInstalled = false;
8205
+
8206
+ function installAmqplibRequireHook() {
8207
+ if (amqplibRequireHookInstalled) return;
8208
+ amqplibRequireHookInstalled = true;
8209
+
8210
+ try {
8211
+ const rim = requireOptionalHostModule('require-in-the-middle');
8212
+ const Hook = rim?.Hook ?? rim;
8213
+ if (typeof Hook !== 'function') return;
8214
+
8215
+ // eslint-disable-next-line no-new
8216
+ new Hook(['amqplib', 'amqplib/callback_api'], (exports: any) => {
8217
+ for (const cfg of amqplibPatchConfigs) {
8218
+ patchAmqplibModule(exports, cfg);
8219
+ }
8220
+ return exports;
8221
+ });
8222
+ } catch {}
8223
+ }
8224
+
8225
+ export function patchAmqplib(cfg: AmqplibPatchConfig) {
8226
+ amqplibPatchConfigs.push(cfg);
8227
+ installAmqplibRequireHook();
8228
+
8229
+ const amqpModules = new Set<any>();
8230
+ const resolvedAmqpModule = requireOptionalHostModule('amqplib');
8231
+ if (resolvedAmqpModule) {
8232
+ amqpModules.add(resolvedAmqpModule);
8233
+ }
8234
+ const resolvedAmqpCallbackModule = requireOptionalHostModule('amqplib/callback_api');
8235
+ if (resolvedAmqpCallbackModule) {
8236
+ amqpModules.add(resolvedAmqpCallbackModule);
8237
+ }
8238
+ for (const loadedAmqpModule of getLoadedModuleExportsByPackage('amqplib')) {
8239
+ amqpModules.add(loadedAmqpModule);
8240
+ }
8241
+ if (!amqpModules.size) return;
8242
+
8243
+ for (const amqpModule of amqpModules) {
8244
+ patchAmqplibModule(amqpModule, cfg);
8245
+ }
8246
+ }
8247
+
7382
8248
  export type RedisPatchConfig = ReproMiddlewareConfig & {
7383
8249
  resolveContext?: () => { sid?: string; aid?: string } | undefined;
7384
8250
  };
@@ -8128,6 +8994,7 @@ export async function initRepro(cfg: ReproInitConfig): Promise<void> {
8128
8994
  skipUrls: [resolveIngestUrl(ingestCfg)],
8129
8995
  });
8130
8996
  patchKafkaJs(integrationCfg);
8997
+ patchAmqplib(integrationCfg);
8131
8998
  patchRedis(integrationCfg);
8132
8999
  patchSendgridMail(integrationCfg);
8133
9000
  patchMongooseIntegration(reproAutoMiddlewareConfig);
@@ -8176,6 +9043,10 @@ export const __reproTestHooks = {
8176
9043
  materializeInlinePrivacyValueAsyncForTest: materializeInlinePrivacyValueAsync,
8177
9044
  patchKafkaProducerInstanceForTest: patchKafkaProducerInstance,
8178
9045
  wrapKafkaEachMessageHandlerForTest: wrapKafkaEachMessageHandler,
9046
+ patchAmqplibModuleForTest: patchAmqplibModule,
9047
+ patchAmqplibConnectionForTest: patchAmqplibConnection,
9048
+ patchAmqplibChannelForTest: patchAmqplibChannel,
9049
+ wrapAmqplibConsumeHandlerForTest: wrapAmqplibConsumeHandler,
8179
9050
  chunkTraceEventsForTransportForTest: chunkTraceEventsForTransport,
8180
9051
  estimateTraceBatchSerializedBytesForTest: estimateTraceBatchSerializedBytes,
8181
9052
  sanitizeTraceValueForPrivacyForTest: sanitizeTraceValueForPrivacy,