@pingops/otel 0.2.4 → 0.2.5

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
@@ -439,6 +439,7 @@ function resolveOutboundSpanParentContext(activeContext, requestUrl) {
439
439
  */
440
440
  const DEFAULT_MAX_REQUEST_BODY_SIZE$1 = 4 * 1024;
441
441
  const DEFAULT_MAX_RESPONSE_BODY_SIZE$1 = 4 * 1024;
442
+ const LEGACY_ATTR_HTTP_URL = "http.url";
442
443
  const PingopsSemanticAttributes = {
443
444
  HTTP_REQUEST_BODY: "http.request.body",
444
445
  HTTP_RESPONSE_BODY: "http.response.body"
@@ -446,12 +447,18 @@ const PingopsSemanticAttributes = {
446
447
  /**
447
448
  * Manually flattens a nested object into dot-notation keys
448
449
  */
450
+ function isPlainObject(value) {
451
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Buffer);
452
+ }
453
+ function isPrimitiveArray(value) {
454
+ return value.every((item) => typeof item === "string" || typeof item === "number" || typeof item === "boolean");
455
+ }
449
456
  function flatten(obj, prefix = "") {
450
457
  const result = {};
451
458
  for (const key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) {
452
459
  const newKey = prefix ? `${prefix}.${key}` : key;
453
460
  const value = obj[key];
454
- if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Buffer)) Object.assign(result, flatten(value, newKey));
461
+ if (isPlainObject(value)) Object.assign(result, flatten(value, newKey));
455
462
  else result[newKey] = value;
456
463
  }
457
464
  return result;
@@ -462,11 +469,9 @@ function flatten(obj, prefix = "") {
462
469
  function setAttributeValue(span, attrName, attrValue) {
463
470
  if (typeof attrValue === "string" || typeof attrValue === "number" || typeof attrValue === "boolean") span.setAttribute(attrName, attrValue);
464
471
  else if (attrValue instanceof Buffer) span.setAttribute(attrName, attrValue.toString("utf8"));
465
- else if (typeof attrValue == "object") span.setAttributes(flatten({ [attrName]: attrValue }));
466
- else if (Array.isArray(attrValue)) if (attrValue.length) {
467
- const firstElement = attrValue[0];
468
- if (typeof firstElement === "string" || typeof firstElement === "number" || typeof firstElement === "boolean") span.setAttribute(attrName, attrValue);
469
- } else span.setAttribute(attrName, attrValue);
472
+ else if (Array.isArray(attrValue)) {
473
+ if (attrValue.length === 0 || isPrimitiveArray(attrValue)) span.setAttribute(attrName, attrValue);
474
+ } else if (isPlainObject(attrValue)) span.setAttributes(flatten({ [attrName]: attrValue }));
470
475
  }
471
476
  /**
472
477
  * Extracts domain from URL
@@ -532,9 +537,16 @@ function captureRequestBody(span, data, maxSize, semanticAttr, url$1) {
532
537
  /**
533
538
  * Captures response body from chunks
534
539
  */
535
- function captureResponseBody(span, chunks, semanticAttr, responseHeaders, url$1) {
540
+ function captureResponseBody(span, chunks, semanticAttr, responseHeaders, url$1, maxSize) {
536
541
  if (!shouldCaptureResponseBody$1(url$1)) return;
537
- if (chunks && chunks.length) try {
542
+ if (chunks === null) {
543
+ const contentEncoding = responseHeaders?.["content-encoding"];
544
+ const contentType = responseHeaders?.["content-type"];
545
+ 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)}]`);
547
+ return;
548
+ }
549
+ if (chunks.length) try {
538
550
  const concatedChunks = Buffer.concat(chunks);
539
551
  const contentEncoding = responseHeaders?.["content-encoding"];
540
552
  if ((0, _pingops_core.isCompressedContentEncoding)(contentEncoding)) {
@@ -597,7 +609,7 @@ function extractRequestUrlFromSpanOptions(options) {
597
609
  const attrs = options.attributes;
598
610
  if (!attrs) return;
599
611
  if (typeof attrs[_opentelemetry_semantic_conventions.ATTR_URL_FULL] === "string") return attrs[_opentelemetry_semantic_conventions.ATTR_URL_FULL];
600
- if (typeof attrs[_opentelemetry_semantic_conventions.ATTR_HTTP_URL] === "string") return attrs[_opentelemetry_semantic_conventions.ATTR_HTTP_URL];
612
+ if (typeof attrs[LEGACY_ATTR_HTTP_URL] === "string") return attrs[LEGACY_ATTR_HTTP_URL];
601
613
  const scheme = typeof attrs[_opentelemetry_semantic_conventions.ATTR_URL_SCHEME] === "string" ? attrs[_opentelemetry_semantic_conventions.ATTR_URL_SCHEME] : "http";
602
614
  const host = typeof attrs[_opentelemetry_semantic_conventions.ATTR_SERVER_ADDRESS] === "string" ? attrs[_opentelemetry_semantic_conventions.ATTR_SERVER_ADDRESS] : void 0;
603
615
  if (!host) return;
@@ -636,17 +648,19 @@ var PingopsHttpInstrumentation = class extends _opentelemetry_instrumentation_ht
636
648
  if (headers) captureRequestHeaders(span, headers);
637
649
  if (request instanceof http.ClientRequest) {
638
650
  const maxRequestBodySize = config?.maxRequestBodySize || DEFAULT_MAX_REQUEST_BODY_SIZE$1;
639
- const url$1 = request.path && request.getHeader("host") ? `${request.protocol || "http:"}//${request.getHeader("host")}${request.path}` : void 0;
651
+ const hostHeader = request.getHeader("host");
652
+ const host = typeof hostHeader === "string" ? hostHeader : Array.isArray(hostHeader) ? hostHeader.join(",") : typeof hostHeader === "number" ? String(hostHeader) : void 0;
653
+ const url$1 = request.path && host ? `${request.protocol || "http:"}//${host}${request.path}` : void 0;
640
654
  const originalWrite = request.write.bind(request);
641
655
  const originalEnd = request.end.bind(request);
642
- request.write = (data) => {
656
+ request.write = ((data, ...rest) => {
643
657
  if (typeof data === "string" || data instanceof Buffer) captureRequestBody(span, data, maxRequestBodySize, PingopsSemanticAttributes.HTTP_REQUEST_BODY, url$1);
644
- return originalWrite(data);
645
- };
646
- request.end = (data) => {
658
+ return originalWrite(data, ...rest);
659
+ });
660
+ request.end = ((data, ...rest) => {
647
661
  if (typeof data === "string" || data instanceof Buffer) captureRequestBody(span, data, maxRequestBodySize, PingopsSemanticAttributes.HTTP_REQUEST_BODY, url$1);
648
- return originalEnd(data);
649
- };
662
+ return originalEnd(data, ...rest);
663
+ });
650
664
  }
651
665
  if (originalRequestHook) originalRequestHook(span, request);
652
666
  };
@@ -663,15 +677,24 @@ var PingopsHttpInstrumentation = class extends _opentelemetry_instrumentation_ht
663
677
  const shouldCapture = shouldCaptureResponseBody$1(url$1);
664
678
  response.prependListener("data", (chunk) => {
665
679
  if (!chunk || !shouldCapture) return;
666
- if (typeof chunk === "string" || chunk instanceof Buffer) {
667
- totalSize += chunk.length;
668
- if (chunks && totalSize <= maxResponseBodySize) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
669
- else chunks = null;
670
- }
671
- });
672
- response.prependOnceListener("end", () => {
673
- captureResponseBody(span, chunks, PingopsSemanticAttributes.HTTP_RESPONSE_BODY, headers, url$1);
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);
684
+ if (!chunkBuffer) return;
685
+ totalSize += chunkBuffer.length;
686
+ if (chunks && totalSize <= maxResponseBodySize) chunks.push(chunkBuffer);
687
+ else chunks = null;
674
688
  });
689
+ let finalized = false;
690
+ const finalizeCapture = () => {
691
+ if (finalized) return;
692
+ finalized = true;
693
+ captureResponseBody(span, chunks, PingopsSemanticAttributes.HTTP_RESPONSE_BODY, headers, url$1, maxResponseBodySize);
694
+ };
695
+ response.prependOnceListener("end", finalizeCapture);
696
+ response.prependOnceListener("close", finalizeCapture);
697
+ response.prependOnceListener("aborted", finalizeCapture);
675
698
  }
676
699
  if (originalResponseHook) originalResponseHook(span, response);
677
700
  };
@@ -925,11 +948,11 @@ var UndiciInstrumentation = class extends _opentelemetry_instrumentation.Instrum
925
948
  const record = this._recordFromReq.get(request);
926
949
  if (!record) return;
927
950
  const { span } = record;
928
- const { remoteAddress, remotePort } = socket;
929
- const spanAttributes = {
930
- [_opentelemetry_semantic_conventions.ATTR_NETWORK_PEER_ADDRESS]: remoteAddress,
931
- [_opentelemetry_semantic_conventions.ATTR_NETWORK_PEER_PORT]: remotePort
932
- };
951
+ const spanAttributes = {};
952
+ const remoteAddress = typeof socket.remoteAddress === "string" ? socket.remoteAddress : void 0;
953
+ const remotePort = typeof socket.remotePort === "number" ? socket.remotePort : void 0;
954
+ if (remoteAddress) spanAttributes[_opentelemetry_semantic_conventions.ATTR_NETWORK_PEER_ADDRESS] = remoteAddress;
955
+ if (remotePort !== void 0) spanAttributes[_opentelemetry_semantic_conventions.ATTR_NETWORK_PEER_PORT] = remotePort;
933
956
  const headersMap = this.parseRequestHeaders(request);
934
957
  for (const [name, value] of headersMap.entries()) {
935
958
  const attrValue = Array.isArray(value) ? value.join(", ") : value;
@@ -998,14 +1021,15 @@ var UndiciInstrumentation = class extends _opentelemetry_instrumentation.Instrum
998
1021
  this._diag.error("Error occurred while capturing request body:", e);
999
1022
  }
1000
1023
  }
1024
+ const errorMessage = error.message;
1001
1025
  span.recordException(error);
1002
1026
  span.setStatus({
1003
1027
  code: _opentelemetry_api.SpanStatusCode.ERROR,
1004
- message: error.message
1028
+ message: errorMessage
1005
1029
  });
1006
1030
  span.end();
1007
1031
  this._recordFromReq.delete(request);
1008
- attributes[_opentelemetry_semantic_conventions.ATTR_ERROR_TYPE] = error.message;
1032
+ attributes[_opentelemetry_semantic_conventions.ATTR_ERROR_TYPE] = errorMessage;
1009
1033
  this.recordRequestDuration(attributes, startTime);
1010
1034
  }
1011
1035
  onBodyChunkSent({ request, chunk }) {