@pingops/otel 0.2.5 → 0.3.0

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.cjs CHANGED
@@ -134,6 +134,7 @@ var PingopsSpanProcessor = class {
134
134
  });
135
135
  this.config = {
136
136
  debug: config.debug ?? false,
137
+ sdkVersion: config.sdkVersion,
137
138
  headersAllowList: config.headersAllowList,
138
139
  headersDenyList: config.headersDenyList,
139
140
  domainAllowList: config.domainAllowList,
@@ -171,6 +172,7 @@ var PingopsSpanProcessor = class {
171
172
  spanId: spanContext.spanId,
172
173
  traceId: spanContext.traceId
173
174
  });
175
+ if (this.config.sdkVersion) span.setAttribute("pingops.sdk.version", this.config.sdkVersion);
174
176
  const propagatedAttributes = (0, _pingops_core.getPropagatedAttributesFromContext)(parentContext);
175
177
  if (Object.keys(propagatedAttributes).length > 0) {
176
178
  for (const [key, value] of Object.entries(propagatedAttributes)) if (typeof value === "string" || Array.isArray(value)) span.setAttribute(key, value);
@@ -423,6 +425,7 @@ function shouldIgnoreOutboundInstrumentation(requestUrl) {
423
425
  */
424
426
  function resolveOutboundSpanParentContext(activeContext, requestUrl) {
425
427
  if (!(0, _opentelemetry_core.isTracingSuppressed)(activeContext)) return activeContext;
428
+ if (activeContext.getValue(_pingops_core.PINGOPS_INTENTIONAL_SUPPRESSION) === true) return activeContext;
426
429
  if (isExporterRequestUrl(requestUrl)) return activeContext;
427
430
  if (!hasLoggedSuppressionLeakWarning) {
428
431
  logger.warn("Detected suppressed context for outbound user request; running instrumentation on ROOT_CONTEXT to prevent Noop spans from suppression leakage");
@@ -431,18 +434,74 @@ function resolveOutboundSpanParentContext(activeContext, requestUrl) {
431
434
  return _opentelemetry_api.ROOT_CONTEXT;
432
435
  }
433
436
 
437
+ //#endregion
438
+ //#region src/instrumentations/body-utils.ts
439
+ const HTTP_REQUEST_BODY = "http.request.body";
440
+ const HTTP_RESPONSE_BODY = "http.response.body";
441
+ const HTTP_REQUEST_BODY_SIZE = "http.request.body.size";
442
+ const HTTP_RESPONSE_BODY_SIZE = "http.response.body.size";
443
+ const DEFAULT_MAX_REQUEST_BODY_SIZE = 10 * 1024;
444
+ const DEFAULT_MAX_RESPONSE_BODY_SIZE = 10 * 1024;
445
+ /**
446
+ * Gets domain rule configuration for a given URL.
447
+ */
448
+ function getDomainRule(url$1, domainAllowList) {
449
+ if (!domainAllowList) return;
450
+ const domain = (0, _pingops_core.extractDomainFromUrl)(url$1);
451
+ for (const rule of domainAllowList) if (domain === rule.domain || domain.endsWith(`.${rule.domain}`) || domain === rule.domain.slice(1)) return rule;
452
+ }
453
+ /**
454
+ * Determines if request body should be captured based on priority:
455
+ * context > domain rule > global config > default (false).
456
+ */
457
+ function shouldCaptureRequestBody(url$1) {
458
+ const contextValue = _opentelemetry_api.context.active().getValue(_pingops_core.PINGOPS_CAPTURE_REQUEST_BODY);
459
+ if (contextValue !== void 0) return contextValue;
460
+ if (url$1) {
461
+ const domainRule = getDomainRule(url$1, getGlobalConfig()?.domainAllowList);
462
+ if (domainRule?.captureRequestBody !== void 0) return domainRule.captureRequestBody;
463
+ }
464
+ const globalConfig$1 = getGlobalConfig();
465
+ if (globalConfig$1?.captureRequestBody !== void 0) return globalConfig$1.captureRequestBody;
466
+ return false;
467
+ }
468
+ /**
469
+ * Determines if response body should be captured based on priority:
470
+ * context > domain rule > global config > default (false).
471
+ */
472
+ function shouldCaptureResponseBody(url$1) {
473
+ const contextValue = _opentelemetry_api.context.active().getValue(_pingops_core.PINGOPS_CAPTURE_RESPONSE_BODY);
474
+ if (contextValue !== void 0) return contextValue;
475
+ if (url$1) {
476
+ const domainRule = getDomainRule(url$1, getGlobalConfig()?.domainAllowList);
477
+ if (domainRule?.captureResponseBody !== void 0) return domainRule.captureResponseBody;
478
+ }
479
+ const globalConfig$1 = getGlobalConfig();
480
+ if (globalConfig$1?.captureResponseBody !== void 0) return globalConfig$1.captureResponseBody;
481
+ return false;
482
+ }
483
+ /**
484
+ * Normalizes supported HTTP chunk types into a Buffer.
485
+ */
486
+ function toBufferChunk(data) {
487
+ if (typeof data === "string") return Buffer.from(data);
488
+ if (Buffer.isBuffer(data)) return data;
489
+ if (data instanceof Uint8Array) return Buffer.from(data);
490
+ return null;
491
+ }
492
+
434
493
  //#endregion
435
494
  //#region src/instrumentations/http/pingops-http.ts
436
495
  /**
437
496
  * Pingops HTTP instrumentation that extends HttpInstrumentation
438
497
  * with request/response body capture
439
498
  */
440
- const DEFAULT_MAX_REQUEST_BODY_SIZE$1 = 4 * 1024;
441
- const DEFAULT_MAX_RESPONSE_BODY_SIZE$1 = 4 * 1024;
442
499
  const LEGACY_ATTR_HTTP_URL = "http.url";
443
500
  const PingopsSemanticAttributes = {
444
- HTTP_REQUEST_BODY: "http.request.body",
445
- HTTP_RESPONSE_BODY: "http.response.body"
501
+ HTTP_REQUEST_BODY,
502
+ HTTP_RESPONSE_BODY,
503
+ HTTP_REQUEST_BODY_SIZE,
504
+ HTTP_RESPONSE_BODY_SIZE
446
505
  };
447
506
  /**
448
507
  * Manually flattens a nested object into dot-notation keys
@@ -455,7 +514,7 @@ function isPrimitiveArray(value) {
455
514
  }
456
515
  function flatten(obj, prefix = "") {
457
516
  const result = {};
458
- for (const key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) {
517
+ for (const key in obj) if (Object.hasOwn(obj, key)) {
459
518
  const newKey = prefix ? `${prefix}.${key}` : key;
460
519
  const value = obj[key];
461
520
  if (isPlainObject(value)) Object.assign(result, flatten(value, newKey));
@@ -474,61 +533,12 @@ function setAttributeValue(span, attrName, attrValue) {
474
533
  } else if (isPlainObject(attrValue)) span.setAttributes(flatten({ [attrName]: attrValue }));
475
534
  }
476
535
  /**
477
- * Extracts domain from URL
478
- */
479
- function extractDomainFromUrl$1(url$1) {
480
- try {
481
- return new URL(url$1).hostname;
482
- } catch {
483
- const match = url$1.match(/^(?:https?:\/\/)?([^/]+)/);
484
- return match ? match[1] : "";
485
- }
486
- }
487
- /**
488
- * Gets domain rule configuration for a given URL
489
- */
490
- function getDomainRule$1(url$1, domainAllowList) {
491
- if (!domainAllowList) return;
492
- const domain = extractDomainFromUrl$1(url$1);
493
- for (const rule of domainAllowList) if (domain === rule.domain || domain.endsWith(`.${rule.domain}`) || domain === rule.domain.slice(1)) return rule;
494
- }
495
- /**
496
- * Determines if request body should be captured based on priority:
497
- * context > domain rule > global config > default (false)
498
- */
499
- function shouldCaptureRequestBody$1(url$1) {
500
- const contextValue = _opentelemetry_api.context.active().getValue(_pingops_core.PINGOPS_CAPTURE_REQUEST_BODY);
501
- if (contextValue !== void 0) return contextValue;
502
- if (url$1) {
503
- const domainRule = getDomainRule$1(url$1, getGlobalConfig()?.domainAllowList);
504
- if (domainRule?.captureRequestBody !== void 0) return domainRule.captureRequestBody;
505
- }
506
- const globalConfig$1 = getGlobalConfig();
507
- if (globalConfig$1?.captureRequestBody !== void 0) return globalConfig$1.captureRequestBody;
508
- return false;
509
- }
510
- /**
511
- * Determines if response body should be captured based on priority:
512
- * context > domain rule > global config > default (false)
513
- */
514
- function shouldCaptureResponseBody$1(url$1) {
515
- const contextValue = _opentelemetry_api.context.active().getValue(_pingops_core.PINGOPS_CAPTURE_RESPONSE_BODY);
516
- if (contextValue !== void 0) return contextValue;
517
- if (url$1) {
518
- const domainRule = getDomainRule$1(url$1, getGlobalConfig()?.domainAllowList);
519
- if (domainRule?.captureResponseBody !== void 0) return domainRule.captureResponseBody;
520
- }
521
- const globalConfig$1 = getGlobalConfig();
522
- if (globalConfig$1?.captureResponseBody !== void 0) return globalConfig$1.captureResponseBody;
523
- return false;
524
- }
525
- /**
526
- * Captures request body from string or Buffer data
536
+ * Captures request body from a chunk buffer.
527
537
  */
528
538
  function captureRequestBody(span, data, maxSize, semanticAttr, url$1) {
529
- if (!shouldCaptureRequestBody$1(url$1)) return;
539
+ if (!shouldCaptureRequestBody(url$1)) return;
530
540
  if (data.length && data.length <= maxSize) try {
531
- const requestBody = typeof data === "string" ? data : data.toString("utf-8");
541
+ const requestBody = data.toString("utf-8");
532
542
  if (requestBody) setAttributeValue(span, semanticAttr, requestBody);
533
543
  } catch (e) {
534
544
  console.error("Error occurred while capturing request body:", e);
@@ -538,12 +548,12 @@ function captureRequestBody(span, data, maxSize, semanticAttr, url$1) {
538
548
  * Captures response body from chunks
539
549
  */
540
550
  function captureResponseBody(span, chunks, semanticAttr, responseHeaders, url$1, maxSize) {
541
- if (!shouldCaptureResponseBody$1(url$1)) return;
551
+ if (!shouldCaptureResponseBody(url$1)) return;
542
552
  if (chunks === null) {
543
553
  const contentEncoding = responseHeaders?.["content-encoding"];
544
554
  const contentType = responseHeaders?.["content-type"];
545
555
  const toHeaderString = (value) => typeof value === "string" ? value : Array.isArray(value) ? value.join(", ") : "unknown";
546
- setAttributeValue(span, semanticAttr, `[truncated response body; exceeded maxResponseBodySize=${maxSize ?? DEFAULT_MAX_RESPONSE_BODY_SIZE$1}; content-type=${toHeaderString(contentType)}; content-encoding=${toHeaderString(contentEncoding)}]`);
556
+ setAttributeValue(span, semanticAttr, `[truncated response body; exceeded maxResponseBodySize=${maxSize ?? DEFAULT_MAX_RESPONSE_BODY_SIZE}; content-type=${toHeaderString(contentType)}; content-encoding=${toHeaderString(contentEncoding)}]`);
547
557
  return;
548
558
  }
549
559
  if (chunks.length) try {
@@ -615,6 +625,21 @@ function extractRequestUrlFromSpanOptions(options) {
615
625
  if (!host) return;
616
626
  return `${scheme}://${host}${typeof attrs[_opentelemetry_semantic_conventions.ATTR_SERVER_PORT] === "number" ? `:${attrs[_opentelemetry_semantic_conventions.ATTR_SERVER_PORT]}` : ""}${typeof attrs[_opentelemetry_semantic_conventions.ATTR_URL_PATH] === "string" ? attrs[_opentelemetry_semantic_conventions.ATTR_URL_PATH] : "/"}${typeof attrs[_opentelemetry_semantic_conventions.ATTR_URL_QUERY] === "string" && attrs[_opentelemetry_semantic_conventions.ATTR_URL_QUERY].length > 0 ? `?${attrs[_opentelemetry_semantic_conventions.ATTR_URL_QUERY]}` : ""}`;
617
627
  }
628
+ function parseRequestPathAndQuery(pathWithQuery) {
629
+ const queryIndex = pathWithQuery.indexOf("?");
630
+ if (queryIndex < 0) return { path: pathWithQuery || "/" };
631
+ const path = pathWithQuery.slice(0, queryIndex) || "/";
632
+ const queryPart = pathWithQuery.slice(queryIndex);
633
+ return {
634
+ path,
635
+ query: queryPart.length > 0 ? queryPart : void 0
636
+ };
637
+ }
638
+ function extractClientRequestPath(request) {
639
+ if (!request || typeof request !== "object" || !("path" in request)) return;
640
+ const path = request.path;
641
+ return typeof path === "string" && path.length > 0 ? path : void 0;
642
+ }
618
643
  const PingopsHttpSemanticAttributes = PingopsSemanticAttributes;
619
644
  var PingopsHttpInstrumentation = class extends _opentelemetry_instrumentation_http.HttpInstrumentation {
620
645
  constructor(config) {
@@ -647,18 +672,36 @@ var PingopsHttpInstrumentation = class extends _opentelemetry_instrumentation_ht
647
672
  const headers = extractRequestHeaders(request);
648
673
  if (headers) captureRequestHeaders(span, headers);
649
674
  if (request instanceof http.ClientRequest) {
650
- const maxRequestBodySize = config?.maxRequestBodySize || DEFAULT_MAX_REQUEST_BODY_SIZE$1;
675
+ const maxRequestBodySize = config?.maxRequestBodySize || DEFAULT_MAX_REQUEST_BODY_SIZE;
676
+ let requestBodySize = 0;
677
+ span.setAttribute(PingopsSemanticAttributes.HTTP_REQUEST_BODY_SIZE, requestBodySize);
651
678
  const hostHeader = request.getHeader("host");
652
679
  const host = typeof hostHeader === "string" ? hostHeader : Array.isArray(hostHeader) ? hostHeader.join(",") : typeof hostHeader === "number" ? String(hostHeader) : void 0;
653
680
  const url$1 = request.path && host ? `${request.protocol || "http:"}//${host}${request.path}` : void 0;
681
+ if (typeof request.path === "string" && request.path.length > 0) {
682
+ const { path, query } = parseRequestPathAndQuery(request.path);
683
+ span.setAttribute(_opentelemetry_semantic_conventions.ATTR_URL_PATH, path);
684
+ if (query) span.setAttribute(_opentelemetry_semantic_conventions.ATTR_URL_QUERY, query);
685
+ }
686
+ if (url$1) span.setAttribute(_opentelemetry_semantic_conventions.ATTR_URL_FULL, url$1);
654
687
  const originalWrite = request.write.bind(request);
655
688
  const originalEnd = request.end.bind(request);
656
689
  request.write = ((data, ...rest) => {
657
- if (typeof data === "string" || data instanceof Buffer) captureRequestBody(span, data, maxRequestBodySize, PingopsSemanticAttributes.HTTP_REQUEST_BODY, url$1);
690
+ const chunkBuffer = toBufferChunk(data);
691
+ if (chunkBuffer) {
692
+ requestBodySize += chunkBuffer.length;
693
+ span.setAttribute(PingopsSemanticAttributes.HTTP_REQUEST_BODY_SIZE, requestBodySize);
694
+ captureRequestBody(span, chunkBuffer, maxRequestBodySize, PingopsSemanticAttributes.HTTP_REQUEST_BODY, url$1);
695
+ }
658
696
  return originalWrite(data, ...rest);
659
697
  });
660
698
  request.end = ((data, ...rest) => {
661
- if (typeof data === "string" || data instanceof Buffer) captureRequestBody(span, data, maxRequestBodySize, PingopsSemanticAttributes.HTTP_REQUEST_BODY, url$1);
699
+ const chunkBuffer = toBufferChunk(data);
700
+ if (chunkBuffer) {
701
+ requestBodySize += chunkBuffer.length;
702
+ span.setAttribute(PingopsSemanticAttributes.HTTP_REQUEST_BODY_SIZE, requestBodySize);
703
+ captureRequestBody(span, chunkBuffer, maxRequestBodySize, PingopsSemanticAttributes.HTTP_REQUEST_BODY, url$1);
704
+ }
662
705
  return originalEnd(data, ...rest);
663
706
  });
664
707
  }
@@ -670,19 +713,25 @@ var PingopsHttpInstrumentation = class extends _opentelemetry_instrumentation_ht
670
713
  const headers = extractResponseHeaders(response);
671
714
  if (headers) captureResponseHeaders(span, headers);
672
715
  if (response instanceof http.IncomingMessage) {
673
- const maxResponseBodySize = config?.maxResponseBodySize || DEFAULT_MAX_RESPONSE_BODY_SIZE$1;
716
+ const requestPath = response.req instanceof http.ClientRequest ? extractClientRequestPath(response.req) : void 0;
717
+ if (requestPath) {
718
+ const { path, query } = parseRequestPathAndQuery(requestPath);
719
+ span.setAttribute(_opentelemetry_semantic_conventions.ATTR_URL_PATH, path);
720
+ if (query) span.setAttribute(_opentelemetry_semantic_conventions.ATTR_URL_QUERY, query);
721
+ }
722
+ const maxResponseBodySize = config?.maxResponseBodySize || DEFAULT_MAX_RESPONSE_BODY_SIZE;
674
723
  const url$1 = response.url || void 0;
675
724
  let chunks = [];
676
725
  let totalSize = 0;
677
- const shouldCapture = shouldCaptureResponseBody$1(url$1);
726
+ span.setAttribute(PingopsSemanticAttributes.HTTP_RESPONSE_BODY_SIZE, 0);
727
+ const shouldCapture = shouldCaptureResponseBody(url$1);
678
728
  response.prependListener("data", (chunk) => {
679
- if (!chunk || !shouldCapture) return;
680
- let chunkBuffer = null;
681
- if (typeof chunk === "string") chunkBuffer = Buffer.from(chunk);
682
- else if (Buffer.isBuffer(chunk)) chunkBuffer = chunk;
683
- else if (chunk instanceof Uint8Array) chunkBuffer = Buffer.from(chunk);
729
+ if (!chunk) return;
730
+ const chunkBuffer = toBufferChunk(chunk);
684
731
  if (!chunkBuffer) return;
685
732
  totalSize += chunkBuffer.length;
733
+ span.setAttribute(PingopsSemanticAttributes.HTTP_RESPONSE_BODY_SIZE, totalSize);
734
+ if (!shouldCapture) return;
686
735
  if (chunks && totalSize <= maxResponseBodySize) chunks.push(chunkBuffer);
687
736
  else chunks = null;
688
737
  });
@@ -690,6 +739,7 @@ var PingopsHttpInstrumentation = class extends _opentelemetry_instrumentation_ht
690
739
  const finalizeCapture = () => {
691
740
  if (finalized) return;
692
741
  finalized = true;
742
+ span.setAttribute(PingopsSemanticAttributes.HTTP_RESPONSE_BODY_SIZE, totalSize);
693
743
  captureResponseBody(span, chunks, PingopsSemanticAttributes.HTTP_RESPONSE_BODY, headers, url$1, maxResponseBodySize);
694
744
  };
695
745
  response.prependOnceListener("end", finalizeCapture);
@@ -738,59 +788,6 @@ function createHttpInstrumentation(config) {
738
788
 
739
789
  //#endregion
740
790
  //#region src/instrumentations/undici/pingops-undici.ts
741
- const DEFAULT_MAX_REQUEST_BODY_SIZE = 4 * 1024;
742
- const DEFAULT_MAX_RESPONSE_BODY_SIZE = 4 * 1024;
743
- const HTTP_REQUEST_BODY = "http.request.body";
744
- const HTTP_RESPONSE_BODY = "http.response.body";
745
- /**
746
- * Extracts domain from URL
747
- */
748
- function extractDomainFromUrl(url$1) {
749
- try {
750
- return new url.URL(url$1).hostname;
751
- } catch {
752
- const match = url$1.match(/^(?:https?:\/\/)?([^/]+)/);
753
- return match ? match[1] : "";
754
- }
755
- }
756
- /**
757
- * Gets domain rule configuration for a given URL
758
- */
759
- function getDomainRule(url$1, domainAllowList) {
760
- if (!domainAllowList) return;
761
- const domain = extractDomainFromUrl(url$1);
762
- for (const rule of domainAllowList) if (domain === rule.domain || domain.endsWith(`.${rule.domain}`) || domain === rule.domain.slice(1)) return rule;
763
- }
764
- /**
765
- * Determines if request body should be captured based on priority:
766
- * context > domain rule > global config > default (false)
767
- */
768
- function shouldCaptureRequestBody(url$1) {
769
- const contextValue = _opentelemetry_api.context.active().getValue(_pingops_core.PINGOPS_CAPTURE_REQUEST_BODY);
770
- if (contextValue !== void 0) return contextValue;
771
- if (url$1) {
772
- const domainRule = getDomainRule(url$1, getGlobalConfig()?.domainAllowList);
773
- if (domainRule?.captureRequestBody !== void 0) return domainRule.captureRequestBody;
774
- }
775
- const globalConfig$1 = getGlobalConfig();
776
- if (globalConfig$1?.captureRequestBody !== void 0) return globalConfig$1.captureRequestBody;
777
- return false;
778
- }
779
- /**
780
- * Determines if response body should be captured based on priority:
781
- * context > domain rule > global config > default (false)
782
- */
783
- function shouldCaptureResponseBody(url$1) {
784
- const contextValue = _opentelemetry_api.context.active().getValue(_pingops_core.PINGOPS_CAPTURE_RESPONSE_BODY);
785
- if (contextValue !== void 0) return contextValue;
786
- if (url$1) {
787
- const domainRule = getDomainRule(url$1, getGlobalConfig()?.domainAllowList);
788
- if (domainRule?.captureResponseBody !== void 0) return domainRule.captureResponseBody;
789
- }
790
- const globalConfig$1 = getGlobalConfig();
791
- if (globalConfig$1?.captureResponseBody !== void 0) return globalConfig$1.captureResponseBody;
792
- return false;
793
- }
794
791
  var UndiciInstrumentation = class extends _opentelemetry_instrumentation.InstrumentationBase {
795
792
  _recordFromReq = /* @__PURE__ */ new WeakMap();
796
793
  constructor(config = {}) {
@@ -898,7 +895,9 @@ var UndiciInstrumentation = class extends _opentelemetry_instrumentation.Instrum
898
895
  [_opentelemetry_semantic_conventions.ATTR_URL_FULL]: requestUrl.toString(),
899
896
  [_opentelemetry_semantic_conventions.ATTR_URL_PATH]: requestUrl.pathname,
900
897
  [_opentelemetry_semantic_conventions.ATTR_URL_QUERY]: requestUrl.search,
901
- [_opentelemetry_semantic_conventions.ATTR_URL_SCHEME]: urlScheme
898
+ [_opentelemetry_semantic_conventions.ATTR_URL_SCHEME]: urlScheme,
899
+ [HTTP_REQUEST_BODY_SIZE]: 0,
900
+ [HTTP_RESPONSE_BODY_SIZE]: 0
902
901
  };
903
902
  const schemePorts = {
904
903
  https: "443",
@@ -941,6 +940,10 @@ var UndiciInstrumentation = class extends _opentelemetry_instrumentation.Instrum
941
940
  responseBodyChunks: [],
942
941
  requestBodySize: 0,
943
942
  responseBodySize: 0,
943
+ requestBodyCaptureSize: 0,
944
+ responseBodyCaptureSize: 0,
945
+ requestBodyCaptureExceeded: false,
946
+ responseBodyCaptureExceeded: false,
944
947
  url: requestUrl.toString()
945
948
  });
946
949
  }
@@ -987,11 +990,13 @@ var UndiciInstrumentation = class extends _opentelemetry_instrumentation.Instrum
987
990
  const record = this._recordFromReq.get(request);
988
991
  if (!record) return;
989
992
  const { span, attributes, startTime } = record;
993
+ span.setAttribute(HTTP_REQUEST_BODY_SIZE, record.requestBodySize);
994
+ span.setAttribute(HTTP_RESPONSE_BODY_SIZE, record.responseBodySize);
990
995
  if (shouldCaptureResponseBody(record.url)) {
991
996
  const maxResponseBodySize = this.getConfig().maxResponseBodySize ?? DEFAULT_MAX_RESPONSE_BODY_SIZE;
992
997
  const contentEncoding = record.attributes?.["http.response.header.content-encoding"] ?? void 0;
993
998
  const contentType = record.attributes?.["http.response.header.content-type"] ?? void 0;
994
- if (record.responseBodySize === Infinity) span.setAttribute(HTTP_RESPONSE_BODY, `[truncated response body; exceeded maxResponseBodySize=${maxResponseBodySize}; content-type=${contentType ?? "unknown"}; content-encoding=${contentEncoding ?? "identity"}]`);
999
+ if (record.responseBodyCaptureExceeded) span.setAttribute(HTTP_RESPONSE_BODY, `[truncated response body; exceeded maxResponseBodySize=${maxResponseBodySize}; content-type=${contentType ?? "unknown"}; content-encoding=${contentEncoding ?? "identity"}]`);
995
1000
  else if (record.responseBodyChunks.length > 0) try {
996
1001
  const responseBodyBuffer = Buffer.concat(record.responseBodyChunks);
997
1002
  if ((0, _pingops_core.isCompressedContentEncoding)(contentEncoding)) {
@@ -1013,8 +1018,10 @@ var UndiciInstrumentation = class extends _opentelemetry_instrumentation.Instrum
1013
1018
  const record = this._recordFromReq.get(request);
1014
1019
  if (!record) return;
1015
1020
  const { span, attributes, startTime } = record;
1021
+ span.setAttribute(HTTP_REQUEST_BODY_SIZE, record.requestBodySize);
1022
+ span.setAttribute(HTTP_RESPONSE_BODY_SIZE, record.responseBodySize);
1016
1023
  if (shouldCaptureRequestBody(record.url)) {
1017
- if (record.requestBodyChunks.length > 0 && record.requestBodySize !== Infinity) try {
1024
+ if (record.requestBodyChunks.length > 0 && !record.requestBodyCaptureExceeded) try {
1018
1025
  const requestBody = Buffer.concat(record.requestBodyChunks).toString("utf-8");
1019
1026
  if (requestBody) span.setAttribute(HTTP_REQUEST_BODY, requestBody);
1020
1027
  } catch (e) {
@@ -1035,24 +1042,29 @@ var UndiciInstrumentation = class extends _opentelemetry_instrumentation.Instrum
1035
1042
  onBodyChunkSent({ request, chunk }) {
1036
1043
  const record = this._recordFromReq.get(request);
1037
1044
  if (!record) return;
1045
+ record.requestBodySize += chunk.length;
1038
1046
  if (!shouldCaptureRequestBody(record.url)) return;
1039
1047
  const maxRequestBodySize = this.getConfig().maxRequestBodySize ?? DEFAULT_MAX_REQUEST_BODY_SIZE;
1040
- if (record.requestBodySize + chunk.length <= maxRequestBodySize) {
1048
+ if (!record.requestBodyCaptureExceeded && record.requestBodyCaptureSize + chunk.length <= maxRequestBodySize) {
1041
1049
  record.requestBodyChunks.push(chunk);
1042
- record.requestBodySize += chunk.length;
1050
+ record.requestBodyCaptureSize += chunk.length;
1043
1051
  } else {
1044
- record.requestBodySize = Infinity;
1052
+ record.requestBodyCaptureExceeded = true;
1045
1053
  record.requestBodyChunks = [];
1054
+ record.requestBodyCaptureSize = 0;
1046
1055
  }
1047
1056
  }
1048
1057
  onBodySent({ request }) {
1049
1058
  const record = this._recordFromReq.get(request);
1050
1059
  if (!record) return;
1060
+ record.span.setAttribute(HTTP_REQUEST_BODY_SIZE, record.requestBodySize);
1051
1061
  if (!shouldCaptureRequestBody(record.url)) {
1052
1062
  record.requestBodyChunks = [];
1063
+ record.requestBodyCaptureSize = 0;
1064
+ record.requestBodyCaptureExceeded = false;
1053
1065
  return;
1054
1066
  }
1055
- if (record.requestBodySize === Infinity) {
1067
+ if (record.requestBodyCaptureExceeded) {
1056
1068
  const maxRequestBodySize = this.getConfig().maxRequestBodySize ?? DEFAULT_MAX_REQUEST_BODY_SIZE;
1057
1069
  record.span.setAttribute(HTTP_REQUEST_BODY, `[truncated request body; exceeded maxRequestBodySize=${maxRequestBodySize}]`);
1058
1070
  } else if (record.requestBodyChunks.length > 0) try {
@@ -1062,18 +1074,22 @@ var UndiciInstrumentation = class extends _opentelemetry_instrumentation.Instrum
1062
1074
  this._diag.error("Error occurred while capturing request body:", e);
1063
1075
  }
1064
1076
  record.requestBodyChunks = [];
1077
+ record.requestBodyCaptureSize = 0;
1078
+ record.requestBodyCaptureExceeded = false;
1065
1079
  }
1066
1080
  onBodyChunkReceived({ request, chunk }) {
1067
1081
  const record = this._recordFromReq.get(request);
1068
1082
  if (!record) return;
1083
+ record.responseBodySize += chunk.length;
1069
1084
  if (!shouldCaptureResponseBody(record.url)) return;
1070
1085
  const maxResponseBodySize = this.getConfig().maxResponseBodySize ?? DEFAULT_MAX_RESPONSE_BODY_SIZE;
1071
- if (record.responseBodySize + chunk.length <= maxResponseBodySize) {
1086
+ if (!record.responseBodyCaptureExceeded && record.responseBodyCaptureSize + chunk.length <= maxResponseBodySize) {
1072
1087
  record.responseBodyChunks.push(chunk);
1073
- record.responseBodySize += chunk.length;
1088
+ record.responseBodyCaptureSize += chunk.length;
1074
1089
  } else {
1075
- record.responseBodySize = Infinity;
1090
+ record.responseBodyCaptureExceeded = true;
1076
1091
  record.responseBodyChunks = [];
1092
+ record.responseBodyCaptureSize = 0;
1077
1093
  }
1078
1094
  }
1079
1095
  recordRequestDuration(attributes, startTime) {