@pingops/otel 0.2.2 → 0.2.4
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 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +83 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -60,6 +60,21 @@ function getGlobalConfig() {
|
|
|
60
60
|
//#endregion
|
|
61
61
|
//#region src/span-processor.ts
|
|
62
62
|
const logger$2 = (0, _pingops_core.createLogger)("[PingOps Processor]");
|
|
63
|
+
function normalizePath$1(pathname) {
|
|
64
|
+
return pathname.replace(/\/+/g, "/").replace(/\/$/, "");
|
|
65
|
+
}
|
|
66
|
+
function isExporterRequestUrl$1(url$1, exporterUrl) {
|
|
67
|
+
try {
|
|
68
|
+
const request = new URL(url$1);
|
|
69
|
+
const exporter = new URL(exporterUrl);
|
|
70
|
+
if (request.origin !== exporter.origin) return false;
|
|
71
|
+
const requestPath = normalizePath$1(request.pathname);
|
|
72
|
+
const exporterPath = normalizePath$1(exporter.pathname);
|
|
73
|
+
return requestPath === exporterPath || requestPath.startsWith(`${exporterPath}/`);
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
63
78
|
/**
|
|
64
79
|
* Creates a filtered span wrapper that applies header filtering to attributes
|
|
65
80
|
*
|
|
@@ -93,6 +108,7 @@ function createFilteredSpan(span, domainAllowList, globalHeadersAllowList, globa
|
|
|
93
108
|
*/
|
|
94
109
|
var PingopsSpanProcessor = class {
|
|
95
110
|
processor;
|
|
111
|
+
exporterTraceUrl;
|
|
96
112
|
config;
|
|
97
113
|
/**
|
|
98
114
|
* Creates a new PingopsSpanProcessor instance.
|
|
@@ -102,8 +118,9 @@ var PingopsSpanProcessor = class {
|
|
|
102
118
|
constructor(config) {
|
|
103
119
|
const exportMode = config.exportMode ?? "batched";
|
|
104
120
|
const apiKey = config.apiKey || process.env.PINGOPS_API_KEY || "";
|
|
121
|
+
this.exporterTraceUrl = `${config.baseUrl}/v1/traces`;
|
|
105
122
|
const exporter = new _opentelemetry_exporter_trace_otlp_http.OTLPTraceExporter({
|
|
106
|
-
url:
|
|
123
|
+
url: this.exporterTraceUrl,
|
|
107
124
|
headers: {
|
|
108
125
|
Authorization: apiKey ? `Bearer ${apiKey}` : "",
|
|
109
126
|
"Content-Type": "application/json"
|
|
@@ -131,7 +148,7 @@ var PingopsSpanProcessor = class {
|
|
|
131
148
|
domainAllowList: config.domainAllowList,
|
|
132
149
|
maxRequestBodySize: config.maxRequestBodySize,
|
|
133
150
|
maxResponseBodySize: config.maxResponseBodySize,
|
|
134
|
-
exportTraceUrl:
|
|
151
|
+
exportTraceUrl: this.exporterTraceUrl
|
|
135
152
|
});
|
|
136
153
|
logger$2.info("Initialized PingopsSpanProcessor", {
|
|
137
154
|
baseUrl: config.baseUrl,
|
|
@@ -192,6 +209,14 @@ var PingopsSpanProcessor = class {
|
|
|
192
209
|
}
|
|
193
210
|
const attributes = span.attributes;
|
|
194
211
|
const url$1 = (0, _pingops_core.getHttpUrlFromAttributes)(attributes) ?? "";
|
|
212
|
+
if (url$1 && isExporterRequestUrl$1(url$1, this.exporterTraceUrl)) {
|
|
213
|
+
logger$2.debug("Skipping exporter span to prevent self-instrumentation", {
|
|
214
|
+
spanName: span.name,
|
|
215
|
+
spanId: spanContext.spanId,
|
|
216
|
+
url: url$1
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
195
220
|
logger$2.debug("Extracted URL for domain filtering", {
|
|
196
221
|
spanName: span.name,
|
|
197
222
|
url: url$1,
|
|
@@ -363,9 +388,12 @@ async function shutdownTracerProvider() {
|
|
|
363
388
|
//#region src/instrumentations/suppression-guard.ts
|
|
364
389
|
const logger = (0, _pingops_core.createLogger)("[PingOps SuppressionGuard]");
|
|
365
390
|
let hasLoggedSuppressionLeakWarning = false;
|
|
366
|
-
function
|
|
391
|
+
function normalizePath(pathname) {
|
|
392
|
+
return pathname.replace(/\/+/g, "/").replace(/\/$/, "");
|
|
393
|
+
}
|
|
394
|
+
function parseUrl(url$1) {
|
|
367
395
|
try {
|
|
368
|
-
return new URL(url$1)
|
|
396
|
+
return new URL(url$1);
|
|
369
397
|
} catch {
|
|
370
398
|
return null;
|
|
371
399
|
}
|
|
@@ -374,10 +402,20 @@ function isExporterRequestUrl(requestUrl) {
|
|
|
374
402
|
if (!requestUrl) return false;
|
|
375
403
|
const exporterUrl = getGlobalConfig()?.exportTraceUrl;
|
|
376
404
|
if (!exporterUrl) return false;
|
|
377
|
-
const
|
|
378
|
-
const
|
|
379
|
-
if (!
|
|
380
|
-
|
|
405
|
+
const parsedRequestUrl = parseUrl(requestUrl);
|
|
406
|
+
const parsedExporterUrl = parseUrl(exporterUrl);
|
|
407
|
+
if (!parsedRequestUrl || !parsedExporterUrl) return false;
|
|
408
|
+
if (parsedRequestUrl.origin !== parsedExporterUrl.origin) return false;
|
|
409
|
+
const requestPath = normalizePath(parsedRequestUrl.pathname);
|
|
410
|
+
const exporterPath = normalizePath(parsedExporterUrl.pathname);
|
|
411
|
+
return requestPath === exporterPath || requestPath.startsWith(`${exporterPath}/`);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Determines whether an outbound request should be skipped from instrumentation
|
|
415
|
+
* to prevent exporter self-instrumentation loops.
|
|
416
|
+
*/
|
|
417
|
+
function shouldIgnoreOutboundInstrumentation(requestUrl) {
|
|
418
|
+
return isExporterRequestUrl(requestUrl);
|
|
381
419
|
}
|
|
382
420
|
/**
|
|
383
421
|
* Returns a context for outbound span creation that neutralizes leaked suppression
|
|
@@ -555,6 +593,16 @@ function captureRequestHeaders(span, headers) {
|
|
|
555
593
|
function captureResponseHeaders(span, headers) {
|
|
556
594
|
for (const [key, value] of Object.entries(headers)) if (value !== void 0) span.setAttribute(`http.response.header.${key.toLowerCase()}`, Array.isArray(value) ? value.join(",") : String(value));
|
|
557
595
|
}
|
|
596
|
+
function extractRequestUrlFromSpanOptions(options) {
|
|
597
|
+
const attrs = options.attributes;
|
|
598
|
+
if (!attrs) return;
|
|
599
|
+
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];
|
|
601
|
+
const scheme = typeof attrs[_opentelemetry_semantic_conventions.ATTR_URL_SCHEME] === "string" ? attrs[_opentelemetry_semantic_conventions.ATTR_URL_SCHEME] : "http";
|
|
602
|
+
const host = typeof attrs[_opentelemetry_semantic_conventions.ATTR_SERVER_ADDRESS] === "string" ? attrs[_opentelemetry_semantic_conventions.ATTR_SERVER_ADDRESS] : void 0;
|
|
603
|
+
if (!host) return;
|
|
604
|
+
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
|
+
}
|
|
558
606
|
const PingopsHttpSemanticAttributes = PingopsSemanticAttributes;
|
|
559
607
|
var PingopsHttpInstrumentation = class extends _opentelemetry_instrumentation_http.HttpInstrumentation {
|
|
560
608
|
constructor(config) {
|
|
@@ -572,7 +620,7 @@ var PingopsHttpInstrumentation = class extends _opentelemetry_instrumentation_ht
|
|
|
572
620
|
const originalStartHttpSpan = target._startHttpSpan.bind(this);
|
|
573
621
|
target._startHttpSpan = (name, options, ctx = _opentelemetry_api.context.active()) => {
|
|
574
622
|
if (options.kind !== _opentelemetry_api.SpanKind.CLIENT) return originalStartHttpSpan(name, options, ctx);
|
|
575
|
-
return originalStartHttpSpan(name, options, resolveOutboundSpanParentContext(ctx,
|
|
623
|
+
return originalStartHttpSpan(name, options, resolveOutboundSpanParentContext(ctx, extractRequestUrlFromSpanOptions(options)));
|
|
576
624
|
};
|
|
577
625
|
}
|
|
578
626
|
_createConfig(config) {
|
|
@@ -635,6 +683,14 @@ var PingopsHttpInstrumentation = class extends _opentelemetry_instrumentation_ht
|
|
|
635
683
|
/**
|
|
636
684
|
* HTTP instrumentation for OpenTelemetry
|
|
637
685
|
*/
|
|
686
|
+
function toRequestUrl$1(request) {
|
|
687
|
+
if (typeof request.href === "string" && request.href.length > 0) return request.href;
|
|
688
|
+
const protocol = typeof request.protocol === "string" && request.protocol.length > 0 ? request.protocol : "http:";
|
|
689
|
+
const hostnameOrHost = typeof request.hostname === "string" && request.hostname.length > 0 ? request.hostname : request.host;
|
|
690
|
+
if (!hostnameOrHost) return;
|
|
691
|
+
const hasPortInHost = hostnameOrHost.includes(":");
|
|
692
|
+
return `${protocol}//${hostnameOrHost}${request.port != null && !hasPortInHost ? `:${request.port}` : ""}${typeof request.path === "string" ? request.path : typeof request.pathname === "string" ? request.pathname : "/"}`;
|
|
693
|
+
}
|
|
638
694
|
/**
|
|
639
695
|
* Creates an HTTP instrumentation instance.
|
|
640
696
|
* All outgoing HTTP requests are instrumented when the SDK is initialized.
|
|
@@ -644,12 +700,16 @@ var PingopsHttpInstrumentation = class extends _opentelemetry_instrumentation_ht
|
|
|
644
700
|
*/
|
|
645
701
|
function createHttpInstrumentation(config) {
|
|
646
702
|
const globalConfig$1 = getGlobalConfig();
|
|
703
|
+
const userIgnoreOutgoingRequestHook = config?.ignoreOutgoingRequestHook;
|
|
647
704
|
return new PingopsHttpInstrumentation({
|
|
705
|
+
...config,
|
|
648
706
|
ignoreIncomingRequestHook: () => true,
|
|
649
|
-
ignoreOutgoingRequestHook: () =>
|
|
707
|
+
ignoreOutgoingRequestHook: (request) => {
|
|
708
|
+
if (shouldIgnoreOutboundInstrumentation(toRequestUrl$1(request))) return true;
|
|
709
|
+
return userIgnoreOutgoingRequestHook?.(request) ?? false;
|
|
710
|
+
},
|
|
650
711
|
maxRequestBodySize: globalConfig$1?.maxRequestBodySize,
|
|
651
|
-
maxResponseBodySize: globalConfig$1?.maxResponseBodySize
|
|
652
|
-
...config
|
|
712
|
+
maxResponseBodySize: globalConfig$1?.maxResponseBodySize
|
|
653
713
|
});
|
|
654
714
|
}
|
|
655
715
|
|
|
@@ -1028,6 +1088,13 @@ var UndiciInstrumentation = class extends _opentelemetry_instrumentation.Instrum
|
|
|
1028
1088
|
/**
|
|
1029
1089
|
* Undici instrumentation for OpenTelemetry
|
|
1030
1090
|
*/
|
|
1091
|
+
function toRequestUrl(request) {
|
|
1092
|
+
try {
|
|
1093
|
+
return new URL(request.path, request.origin).toString();
|
|
1094
|
+
} catch {
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1031
1098
|
/**
|
|
1032
1099
|
* Creates an Undici instrumentation instance.
|
|
1033
1100
|
* All requests are instrumented when the SDK is initialized.
|
|
@@ -1038,7 +1105,9 @@ function createUndiciInstrumentation() {
|
|
|
1038
1105
|
const globalConfig$1 = getGlobalConfig();
|
|
1039
1106
|
return new UndiciInstrumentation({
|
|
1040
1107
|
enabled: true,
|
|
1041
|
-
ignoreRequestHook: () =>
|
|
1108
|
+
ignoreRequestHook: (request) => {
|
|
1109
|
+
return shouldIgnoreOutboundInstrumentation(toRequestUrl(request));
|
|
1110
|
+
},
|
|
1042
1111
|
maxRequestBodySize: globalConfig$1?.maxRequestBodySize,
|
|
1043
1112
|
maxResponseBodySize: globalConfig$1?.maxResponseBodySize
|
|
1044
1113
|
});
|