@pingops/otel 0.2.4 → 0.2.6
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 +82 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +83 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
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 (
|
|
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 (
|
|
466
|
-
|
|
467
|
-
|
|
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
|
|
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,12 +609,27 @@ 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[
|
|
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;
|
|
604
616
|
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]}` : ""}`;
|
|
605
617
|
}
|
|
618
|
+
function parseRequestPathAndQuery(pathWithQuery) {
|
|
619
|
+
const queryIndex = pathWithQuery.indexOf("?");
|
|
620
|
+
if (queryIndex < 0) return { path: pathWithQuery || "/" };
|
|
621
|
+
const path = pathWithQuery.slice(0, queryIndex) || "/";
|
|
622
|
+
const queryPart = pathWithQuery.slice(queryIndex);
|
|
623
|
+
return {
|
|
624
|
+
path,
|
|
625
|
+
query: queryPart.length > 0 ? queryPart : void 0
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function extractClientRequestPath(request) {
|
|
629
|
+
if (!request || typeof request !== "object" || !("path" in request)) return;
|
|
630
|
+
const path = request.path;
|
|
631
|
+
return typeof path === "string" && path.length > 0 ? path : void 0;
|
|
632
|
+
}
|
|
606
633
|
const PingopsHttpSemanticAttributes = PingopsSemanticAttributes;
|
|
607
634
|
var PingopsHttpInstrumentation = class extends _opentelemetry_instrumentation_http.HttpInstrumentation {
|
|
608
635
|
constructor(config) {
|
|
@@ -636,17 +663,25 @@ var PingopsHttpInstrumentation = class extends _opentelemetry_instrumentation_ht
|
|
|
636
663
|
if (headers) captureRequestHeaders(span, headers);
|
|
637
664
|
if (request instanceof http.ClientRequest) {
|
|
638
665
|
const maxRequestBodySize = config?.maxRequestBodySize || DEFAULT_MAX_REQUEST_BODY_SIZE$1;
|
|
639
|
-
const
|
|
666
|
+
const hostHeader = request.getHeader("host");
|
|
667
|
+
const host = typeof hostHeader === "string" ? hostHeader : Array.isArray(hostHeader) ? hostHeader.join(",") : typeof hostHeader === "number" ? String(hostHeader) : void 0;
|
|
668
|
+
const url$1 = request.path && host ? `${request.protocol || "http:"}//${host}${request.path}` : void 0;
|
|
669
|
+
if (typeof request.path === "string" && request.path.length > 0) {
|
|
670
|
+
const { path, query } = parseRequestPathAndQuery(request.path);
|
|
671
|
+
span.setAttribute(_opentelemetry_semantic_conventions.ATTR_URL_PATH, path);
|
|
672
|
+
if (query) span.setAttribute(_opentelemetry_semantic_conventions.ATTR_URL_QUERY, query);
|
|
673
|
+
}
|
|
674
|
+
if (url$1) span.setAttribute(_opentelemetry_semantic_conventions.ATTR_URL_FULL, url$1);
|
|
640
675
|
const originalWrite = request.write.bind(request);
|
|
641
676
|
const originalEnd = request.end.bind(request);
|
|
642
|
-
request.write = (data) => {
|
|
677
|
+
request.write = ((data, ...rest) => {
|
|
643
678
|
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) => {
|
|
679
|
+
return originalWrite(data, ...rest);
|
|
680
|
+
});
|
|
681
|
+
request.end = ((data, ...rest) => {
|
|
647
682
|
if (typeof data === "string" || data instanceof Buffer) captureRequestBody(span, data, maxRequestBodySize, PingopsSemanticAttributes.HTTP_REQUEST_BODY, url$1);
|
|
648
|
-
return originalEnd(data);
|
|
649
|
-
};
|
|
683
|
+
return originalEnd(data, ...rest);
|
|
684
|
+
});
|
|
650
685
|
}
|
|
651
686
|
if (originalRequestHook) originalRequestHook(span, request);
|
|
652
687
|
};
|
|
@@ -656,6 +691,12 @@ var PingopsHttpInstrumentation = class extends _opentelemetry_instrumentation_ht
|
|
|
656
691
|
const headers = extractResponseHeaders(response);
|
|
657
692
|
if (headers) captureResponseHeaders(span, headers);
|
|
658
693
|
if (response instanceof http.IncomingMessage) {
|
|
694
|
+
const requestPath = response.req instanceof http.ClientRequest ? extractClientRequestPath(response.req) : void 0;
|
|
695
|
+
if (requestPath) {
|
|
696
|
+
const { path, query } = parseRequestPathAndQuery(requestPath);
|
|
697
|
+
span.setAttribute(_opentelemetry_semantic_conventions.ATTR_URL_PATH, path);
|
|
698
|
+
if (query) span.setAttribute(_opentelemetry_semantic_conventions.ATTR_URL_QUERY, query);
|
|
699
|
+
}
|
|
659
700
|
const maxResponseBodySize = config?.maxResponseBodySize || DEFAULT_MAX_RESPONSE_BODY_SIZE$1;
|
|
660
701
|
const url$1 = response.url || void 0;
|
|
661
702
|
let chunks = [];
|
|
@@ -663,15 +704,24 @@ var PingopsHttpInstrumentation = class extends _opentelemetry_instrumentation_ht
|
|
|
663
704
|
const shouldCapture = shouldCaptureResponseBody$1(url$1);
|
|
664
705
|
response.prependListener("data", (chunk) => {
|
|
665
706
|
if (!chunk || !shouldCapture) return;
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
707
|
+
let chunkBuffer = null;
|
|
708
|
+
if (typeof chunk === "string") chunkBuffer = Buffer.from(chunk);
|
|
709
|
+
else if (Buffer.isBuffer(chunk)) chunkBuffer = chunk;
|
|
710
|
+
else if (chunk instanceof Uint8Array) chunkBuffer = Buffer.from(chunk);
|
|
711
|
+
if (!chunkBuffer) return;
|
|
712
|
+
totalSize += chunkBuffer.length;
|
|
713
|
+
if (chunks && totalSize <= maxResponseBodySize) chunks.push(chunkBuffer);
|
|
714
|
+
else chunks = null;
|
|
674
715
|
});
|
|
716
|
+
let finalized = false;
|
|
717
|
+
const finalizeCapture = () => {
|
|
718
|
+
if (finalized) return;
|
|
719
|
+
finalized = true;
|
|
720
|
+
captureResponseBody(span, chunks, PingopsSemanticAttributes.HTTP_RESPONSE_BODY, headers, url$1, maxResponseBodySize);
|
|
721
|
+
};
|
|
722
|
+
response.prependOnceListener("end", finalizeCapture);
|
|
723
|
+
response.prependOnceListener("close", finalizeCapture);
|
|
724
|
+
response.prependOnceListener("aborted", finalizeCapture);
|
|
675
725
|
}
|
|
676
726
|
if (originalResponseHook) originalResponseHook(span, response);
|
|
677
727
|
};
|
|
@@ -925,11 +975,11 @@ var UndiciInstrumentation = class extends _opentelemetry_instrumentation.Instrum
|
|
|
925
975
|
const record = this._recordFromReq.get(request);
|
|
926
976
|
if (!record) return;
|
|
927
977
|
const { span } = record;
|
|
928
|
-
const
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
978
|
+
const spanAttributes = {};
|
|
979
|
+
const remoteAddress = typeof socket.remoteAddress === "string" ? socket.remoteAddress : void 0;
|
|
980
|
+
const remotePort = typeof socket.remotePort === "number" ? socket.remotePort : void 0;
|
|
981
|
+
if (remoteAddress) spanAttributes[_opentelemetry_semantic_conventions.ATTR_NETWORK_PEER_ADDRESS] = remoteAddress;
|
|
982
|
+
if (remotePort !== void 0) spanAttributes[_opentelemetry_semantic_conventions.ATTR_NETWORK_PEER_PORT] = remotePort;
|
|
933
983
|
const headersMap = this.parseRequestHeaders(request);
|
|
934
984
|
for (const [name, value] of headersMap.entries()) {
|
|
935
985
|
const attrValue = Array.isArray(value) ? value.join(", ") : value;
|
|
@@ -998,14 +1048,15 @@ var UndiciInstrumentation = class extends _opentelemetry_instrumentation.Instrum
|
|
|
998
1048
|
this._diag.error("Error occurred while capturing request body:", e);
|
|
999
1049
|
}
|
|
1000
1050
|
}
|
|
1051
|
+
const errorMessage = error.message;
|
|
1001
1052
|
span.recordException(error);
|
|
1002
1053
|
span.setStatus({
|
|
1003
1054
|
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
1004
|
-
message:
|
|
1055
|
+
message: errorMessage
|
|
1005
1056
|
});
|
|
1006
1057
|
span.end();
|
|
1007
1058
|
this._recordFromReq.delete(request);
|
|
1008
|
-
attributes[_opentelemetry_semantic_conventions.ATTR_ERROR_TYPE] =
|
|
1059
|
+
attributes[_opentelemetry_semantic_conventions.ATTR_ERROR_TYPE] = errorMessage;
|
|
1009
1060
|
this.recordRequestDuration(attributes, startTime);
|
|
1010
1061
|
}
|
|
1011
1062
|
onBodyChunkSent({ request, chunk }) {
|