@pingops/core 0.1.3 → 0.2.1
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 +131 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -48
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +63 -48
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +124 -107
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -3
package/dist/index.d.mts
CHANGED
|
@@ -29,9 +29,10 @@ interface SpanPayload {
|
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
|
-
* Attributes to propagate to
|
|
32
|
+
* Attributes to propagate to spans (e.g. when starting a trace with startTrace).
|
|
33
33
|
*/
|
|
34
|
-
interface
|
|
34
|
+
interface PingopsTraceAttributes {
|
|
35
|
+
traceId?: string;
|
|
35
36
|
userId?: string;
|
|
36
37
|
sessionId?: string;
|
|
37
38
|
tags?: string[];
|
|
@@ -53,7 +54,10 @@ interface WrapHttpAttributes {
|
|
|
53
54
|
* Checks if a span is eligible for capture based on span kind and attributes.
|
|
54
55
|
* A span is eligible if:
|
|
55
56
|
* 1. span.kind === SpanKind.CLIENT
|
|
56
|
-
* 2. AND has HTTP attributes
|
|
57
|
+
* 2. AND has HTTP attributes
|
|
58
|
+
* - method: http.method or http.request.method
|
|
59
|
+
* - url: http.url or url.full
|
|
60
|
+
* - host: server.address
|
|
57
61
|
* OR has GenAI attributes (gen_ai.system, gen_ai.operation.name)
|
|
58
62
|
*/
|
|
59
63
|
declare function isSpanEligible(span: ReadableSpan): boolean;
|
|
@@ -177,12 +181,54 @@ declare function extractHeadersFromAttributes(attributes: Record<string, unknown
|
|
|
177
181
|
*/
|
|
178
182
|
declare function normalizeHeaders(headers: unknown): Record<string, string | string[] | undefined>;
|
|
179
183
|
//#endregion
|
|
184
|
+
//#region src/filtering/body-decoder.d.ts
|
|
185
|
+
/**
|
|
186
|
+
* Minimal body handling: buffer to string for span attributes.
|
|
187
|
+
* No decompression or truncation; for compressed responses the instrumentation
|
|
188
|
+
* sends base64 + content-encoding so the backend can decompress.
|
|
189
|
+
*/
|
|
190
|
+
/** Span attribute for response content-encoding when body is sent as base64. */
|
|
191
|
+
declare const HTTP_RESPONSE_CONTENT_ENCODING = "http.response.content_encoding";
|
|
192
|
+
/**
|
|
193
|
+
* Returns true if the content-encoding header indicates a compressed body
|
|
194
|
+
* (gzip, br, deflate, x-gzip, x-deflate). Used to decide whether to send
|
|
195
|
+
* body as base64 + content-encoding for backend decompression.
|
|
196
|
+
*/
|
|
197
|
+
declare function isCompressedContentEncoding(headerValue: unknown): boolean;
|
|
198
|
+
/**
|
|
199
|
+
* Converts a buffer to a UTF-8 string for use as request/response body on spans.
|
|
200
|
+
* Returns null for null, undefined, or empty buffer.
|
|
201
|
+
*/
|
|
202
|
+
declare function bufferToBodyString(buffer: Buffer | null | undefined): string | null;
|
|
203
|
+
//#endregion
|
|
180
204
|
//#region src/utils/span-extractor.d.ts
|
|
181
205
|
/**
|
|
182
206
|
* Extracts structured payload from a span
|
|
183
207
|
*/
|
|
184
208
|
declare function extractSpanPayload(span: ReadableSpan, domainAllowList?: DomainRule[], globalHeadersAllowList?: string[], globalHeadersDenyList?: string[], globalCaptureRequestBody?: boolean, globalCaptureResponseBody?: boolean, headerRedaction?: HeaderRedactionConfig): SpanPayload | null;
|
|
185
209
|
//#endregion
|
|
210
|
+
//#region src/utils/http-attributes.d.ts
|
|
211
|
+
/**
|
|
212
|
+
* Helpers for reading HTTP-related span attributes across legacy and modern
|
|
213
|
+
* OpenTelemetry semantic conventions.
|
|
214
|
+
*/
|
|
215
|
+
type SpanAttributes = Record<string, unknown>;
|
|
216
|
+
/**
|
|
217
|
+
* Returns true when either legacy or modern HTTP method attribute is present.
|
|
218
|
+
*/
|
|
219
|
+
declare function hasHttpMethodAttribute(attributes: SpanAttributes): boolean;
|
|
220
|
+
/**
|
|
221
|
+
* Returns true when either legacy or modern HTTP URL attribute is present.
|
|
222
|
+
*/
|
|
223
|
+
declare function hasHttpUrlAttribute(attributes: SpanAttributes): boolean;
|
|
224
|
+
/**
|
|
225
|
+
* Extracts URL from known HTTP attributes with support for legacy + modern keys.
|
|
226
|
+
*
|
|
227
|
+
* If no explicit URL exists but server.address is available, falls back to a
|
|
228
|
+
* synthetic HTTPS URL for downstream domain filtering.
|
|
229
|
+
*/
|
|
230
|
+
declare function getHttpUrlFromAttributes(attributes: SpanAttributes): string | undefined;
|
|
231
|
+
//#endregion
|
|
186
232
|
//#region src/utils/context-extractor.d.ts
|
|
187
233
|
/**
|
|
188
234
|
* Extracts propagated attributes from the given context and returns them
|
|
@@ -220,11 +266,10 @@ declare function createLogger(prefix: string): Logger;
|
|
|
220
266
|
* OpenTelemetry context keys for PingOps
|
|
221
267
|
*/
|
|
222
268
|
/**
|
|
223
|
-
* Context key for
|
|
224
|
-
*
|
|
225
|
-
* This allows wrapHttp to control which HTTP calls are captured.
|
|
269
|
+
* Context key for trace ID attribute.
|
|
270
|
+
* Used to propagate trace identifier to all spans in the context.
|
|
226
271
|
*/
|
|
227
|
-
declare const
|
|
272
|
+
declare const PINGOPS_TRACE_ID: symbol;
|
|
228
273
|
/**
|
|
229
274
|
* Context key for user ID attribute.
|
|
230
275
|
* Used to propagate user identifier to all spans in the context.
|
|
@@ -248,58 +293,28 @@ declare const PINGOPS_METADATA: symbol;
|
|
|
248
293
|
/**
|
|
249
294
|
* Context key for capturing request body.
|
|
250
295
|
* When set, controls whether request bodies should be captured for HTTP spans.
|
|
251
|
-
* This allows wrapHttp to control body capture per-request.
|
|
252
296
|
*/
|
|
253
297
|
declare const PINGOPS_CAPTURE_REQUEST_BODY: symbol;
|
|
254
298
|
/**
|
|
255
299
|
* Context key for capturing response body.
|
|
256
300
|
* When set, controls whether response bodies should be captured for HTTP spans.
|
|
257
|
-
* This allows wrapHttp to control body capture per-request.
|
|
258
301
|
*/
|
|
259
302
|
declare const PINGOPS_CAPTURE_RESPONSE_BODY: symbol;
|
|
260
303
|
//#endregion
|
|
261
|
-
//#region src/
|
|
304
|
+
//#region src/trace-id.d.ts
|
|
262
305
|
/**
|
|
263
|
-
*
|
|
306
|
+
* Deterministic and random trace ID generation for PingOps
|
|
264
307
|
*/
|
|
265
|
-
interface WrapHttpOptions {
|
|
266
|
-
attributes?: WrapHttpAttributes;
|
|
267
|
-
/**
|
|
268
|
-
* Callback to check if SDK is initialized.
|
|
269
|
-
* Required to determine if global instrumentation is enabled.
|
|
270
|
-
*/
|
|
271
|
-
checkInitialized: () => boolean;
|
|
272
|
-
/**
|
|
273
|
-
* Callback to check if global instrumentation is enabled.
|
|
274
|
-
* Required to determine instrumentation behavior.
|
|
275
|
-
*/
|
|
276
|
-
isGlobalInstrumentationEnabled: () => boolean;
|
|
277
|
-
/**
|
|
278
|
-
* Optional callback to ensure SDK is initialized (auto-initialization).
|
|
279
|
-
* If not provided, wrapHttp will try to auto-initialize from environment variables.
|
|
280
|
-
*/
|
|
281
|
-
ensureInitialized?: () => Promise<void>;
|
|
282
|
-
}
|
|
283
308
|
/**
|
|
284
|
-
*
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
* -
|
|
291
|
-
* `wrapHttp` only adds attributes to spans created within the wrapped block.
|
|
292
|
-
* - If `initializePingops` was NOT called: Only HTTP requests within `wrapHttp` blocks
|
|
293
|
-
* are instrumented. Requests outside `wrapHttp` are not instrumented.
|
|
294
|
-
*
|
|
295
|
-
* Note: This is the low-level API. For a simpler API with automatic setup,
|
|
296
|
-
* use `wrapHttp` from `@pingops/sdk` instead.
|
|
297
|
-
*
|
|
298
|
-
* @param options - Options including attributes and required callbacks
|
|
299
|
-
* @param fn - Function to execute within the attribute context
|
|
300
|
-
* @returns The result of the function
|
|
309
|
+
* Converts a Uint8Array to a lowercase hex string.
|
|
310
|
+
*/
|
|
311
|
+
declare function uint8ArrayToHex(bytes: Uint8Array): string;
|
|
312
|
+
/**
|
|
313
|
+
* Creates a trace ID (32 hex chars).
|
|
314
|
+
* - If `seed` is provided: deterministic via SHA-256 of the seed (first 32 hex chars).
|
|
315
|
+
* - Otherwise: random 16 bytes as 32 hex chars.
|
|
301
316
|
*/
|
|
302
|
-
declare function
|
|
317
|
+
declare function createTraceId(seed?: string): Promise<string>;
|
|
303
318
|
//#endregion
|
|
304
|
-
export { DEFAULT_REDACTION_CONFIG, DEFAULT_SENSITIVE_HEADER_PATTERNS, DomainRule, HeaderRedactionConfig, HeaderRedactionStrategy, LogLevel, Logger, PINGOPS_CAPTURE_REQUEST_BODY, PINGOPS_CAPTURE_RESPONSE_BODY,
|
|
319
|
+
export { DEFAULT_REDACTION_CONFIG, DEFAULT_SENSITIVE_HEADER_PATTERNS, DomainRule, HTTP_RESPONSE_CONTENT_ENCODING, HeaderRedactionConfig, HeaderRedactionStrategy, LogLevel, Logger, PINGOPS_CAPTURE_REQUEST_BODY, PINGOPS_CAPTURE_RESPONSE_BODY, PINGOPS_METADATA, PINGOPS_SESSION_ID, PINGOPS_TAGS, PINGOPS_TRACE_ID, PINGOPS_USER_ID, PingopsTraceAttributes, SpanPayload, bufferToBodyString, createLogger, createTraceId, extractHeadersFromAttributes, extractSpanPayload, filterHeaders, getHttpUrlFromAttributes, getPropagatedAttributesFromContext, hasHttpMethodAttribute, hasHttpUrlAttribute, isCompressedContentEncoding, isSensitiveHeader, isSpanEligible, normalizeHeaders, redactHeaderValue, shouldCaptureSpan, uint8ArrayToHex };
|
|
305
320
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/filtering/span-filter.ts","../src/filtering/domain-filter.ts","../src/filtering/sensitive-headers.ts","../src/filtering/header-filter.ts","../src/utils/span-extractor.ts","../src/utils/context-extractor.ts","../src/logger.ts","../src/context-keys.ts","../src/
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/filtering/span-filter.ts","../src/filtering/domain-filter.ts","../src/filtering/sensitive-headers.ts","../src/filtering/header-filter.ts","../src/filtering/body-decoder.ts","../src/utils/span-extractor.ts","../src/utils/http-attributes.ts","../src/utils/context-extractor.ts","../src/logger.ts","../src/context-keys.ts","../src/trace-id.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SpanKind,
|
|
1
|
+
import { SpanKind, createContextKey } from "@opentelemetry/api";
|
|
2
2
|
|
|
3
3
|
//#region src/logger.ts
|
|
4
4
|
/**
|
|
@@ -28,6 +28,35 @@ function createLogger(prefix) {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/utils/http-attributes.ts
|
|
33
|
+
/**
|
|
34
|
+
* Returns true when either legacy or modern HTTP method attribute is present.
|
|
35
|
+
*/
|
|
36
|
+
function hasHttpMethodAttribute(attributes) {
|
|
37
|
+
return attributes["http.method"] !== void 0 || attributes["http.request.method"] !== void 0;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Returns true when either legacy or modern HTTP URL attribute is present.
|
|
41
|
+
*/
|
|
42
|
+
function hasHttpUrlAttribute(attributes) {
|
|
43
|
+
return attributes["http.url"] !== void 0 || attributes["url.full"] !== void 0;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Extracts URL from known HTTP attributes with support for legacy + modern keys.
|
|
47
|
+
*
|
|
48
|
+
* If no explicit URL exists but server.address is available, falls back to a
|
|
49
|
+
* synthetic HTTPS URL for downstream domain filtering.
|
|
50
|
+
*/
|
|
51
|
+
function getHttpUrlFromAttributes(attributes) {
|
|
52
|
+
const legacyUrl = attributes["http.url"];
|
|
53
|
+
if (typeof legacyUrl === "string" && legacyUrl.length > 0) return legacyUrl;
|
|
54
|
+
const modernUrl = attributes["url.full"];
|
|
55
|
+
if (typeof modernUrl === "string" && modernUrl.length > 0) return modernUrl;
|
|
56
|
+
const serverAddress = attributes["server.address"];
|
|
57
|
+
if (typeof serverAddress === "string" && serverAddress.length > 0) return `https://${serverAddress}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
31
60
|
//#endregion
|
|
32
61
|
//#region src/filtering/span-filter.ts
|
|
33
62
|
/**
|
|
@@ -38,7 +67,10 @@ const log$2 = createLogger("[PingOps SpanFilter]");
|
|
|
38
67
|
* Checks if a span is eligible for capture based on span kind and attributes.
|
|
39
68
|
* A span is eligible if:
|
|
40
69
|
* 1. span.kind === SpanKind.CLIENT
|
|
41
|
-
* 2. AND has HTTP attributes
|
|
70
|
+
* 2. AND has HTTP attributes
|
|
71
|
+
* - method: http.method or http.request.method
|
|
72
|
+
* - url: http.url or url.full
|
|
73
|
+
* - host: server.address
|
|
42
74
|
* OR has GenAI attributes (gen_ai.system, gen_ai.operation.name)
|
|
43
75
|
*/
|
|
44
76
|
function isSpanEligible(span) {
|
|
@@ -56,8 +88,8 @@ function isSpanEligible(span) {
|
|
|
56
88
|
return false;
|
|
57
89
|
}
|
|
58
90
|
const attributes = span.attributes;
|
|
59
|
-
const hasHttpMethod = attributes
|
|
60
|
-
const hasHttpUrl = attributes
|
|
91
|
+
const hasHttpMethod = hasHttpMethodAttribute(attributes);
|
|
92
|
+
const hasHttpUrl = hasHttpUrlAttribute(attributes);
|
|
61
93
|
const hasServerAddress = attributes["server.address"] !== void 0;
|
|
62
94
|
const isEligible = hasHttpMethod || hasHttpUrl || hasServerAddress;
|
|
63
95
|
log$2.debug("Span eligibility check result", {
|
|
@@ -66,6 +98,10 @@ function isSpanEligible(span) {
|
|
|
66
98
|
httpAttributes: {
|
|
67
99
|
hasMethod: hasHttpMethod,
|
|
68
100
|
hasUrl: hasHttpUrl,
|
|
101
|
+
hasLegacyMethod: attributes["http.method"] !== void 0,
|
|
102
|
+
hasModernMethod: attributes["http.request.method"] !== void 0,
|
|
103
|
+
hasLegacyUrl: attributes["http.url"] !== void 0,
|
|
104
|
+
hasModernUrl: attributes["url.full"] !== void 0,
|
|
69
105
|
hasServerAddress
|
|
70
106
|
}
|
|
71
107
|
});
|
|
@@ -367,6 +403,10 @@ function redactSingleValue(value, config) {
|
|
|
367
403
|
* Header filtering logic - applies allow/deny list rules and redaction
|
|
368
404
|
*/
|
|
369
405
|
const log = createLogger("[PingOps HeaderFilter]");
|
|
406
|
+
function toHeaderString(value) {
|
|
407
|
+
if (typeof value === "string") return value;
|
|
408
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
|
|
409
|
+
}
|
|
370
410
|
/**
|
|
371
411
|
* Normalizes header name to lowercase for case-insensitive matching
|
|
372
412
|
*/
|
|
@@ -528,7 +568,8 @@ function extractHeadersFromAttributes(attributes, headerPrefix) {
|
|
|
528
568
|
if (directKeyValueHeaders.length > 0) for (const { key, headerName } of directKeyValueHeaders) {
|
|
529
569
|
const headerValue = attributes[key];
|
|
530
570
|
if (headerValue !== void 0 && headerValue !== null) {
|
|
531
|
-
const stringValue =
|
|
571
|
+
const stringValue = toHeaderString(headerValue);
|
|
572
|
+
if (stringValue === void 0) continue;
|
|
532
573
|
const normalizedName = headerName.toLowerCase();
|
|
533
574
|
const existingKey = Object.keys(headerMap).find((k) => k.toLowerCase() === normalizedName);
|
|
534
575
|
if (existingKey) {
|
|
@@ -552,6 +593,13 @@ function normalizeHeaders(headers) {
|
|
|
552
593
|
const result = {};
|
|
553
594
|
if (!headers) return result;
|
|
554
595
|
try {
|
|
596
|
+
if (Array.isArray(headers)) {
|
|
597
|
+
for (let i = 0; i < headers.length; i += 2) if (i + 1 < headers.length) {
|
|
598
|
+
const key = String(headers[i]);
|
|
599
|
+
result[key] = headers[i + 1];
|
|
600
|
+
}
|
|
601
|
+
return result;
|
|
602
|
+
}
|
|
555
603
|
if (isHeadersLike(headers)) {
|
|
556
604
|
for (const [key, value] of headers.entries()) if (result[key]) {
|
|
557
605
|
const existing = result[key];
|
|
@@ -563,17 +611,57 @@ function normalizeHeaders(headers) {
|
|
|
563
611
|
for (const [key, value] of Object.entries(headers)) if (!/^\d+$/.test(key)) result[key] = value;
|
|
564
612
|
return result;
|
|
565
613
|
}
|
|
566
|
-
if (Array.isArray(headers)) {
|
|
567
|
-
for (let i = 0; i < headers.length; i += 2) if (i + 1 < headers.length) {
|
|
568
|
-
const key = String(headers[i]);
|
|
569
|
-
result[key] = headers[i + 1];
|
|
570
|
-
}
|
|
571
|
-
return result;
|
|
572
|
-
}
|
|
573
614
|
} catch {}
|
|
574
615
|
return result;
|
|
575
616
|
}
|
|
576
617
|
|
|
618
|
+
//#endregion
|
|
619
|
+
//#region src/filtering/body-decoder.ts
|
|
620
|
+
/**
|
|
621
|
+
* Minimal body handling: buffer to string for span attributes.
|
|
622
|
+
* No decompression or truncation; for compressed responses the instrumentation
|
|
623
|
+
* sends base64 + content-encoding so the backend can decompress.
|
|
624
|
+
*/
|
|
625
|
+
/** Span attribute for response content-encoding when body is sent as base64. */
|
|
626
|
+
const HTTP_RESPONSE_CONTENT_ENCODING = "http.response.content_encoding";
|
|
627
|
+
const COMPRESSED_ENCODINGS = new Set([
|
|
628
|
+
"gzip",
|
|
629
|
+
"br",
|
|
630
|
+
"deflate",
|
|
631
|
+
"x-gzip",
|
|
632
|
+
"x-deflate"
|
|
633
|
+
]);
|
|
634
|
+
function safeStringify(value) {
|
|
635
|
+
if (typeof value === "string") return value;
|
|
636
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
|
|
637
|
+
}
|
|
638
|
+
function normalizeHeaderValue(v) {
|
|
639
|
+
if (v == null) return void 0;
|
|
640
|
+
if (Array.isArray(v)) return v.map((item) => safeStringify(item)).filter((item) => item !== void 0).join(", ").trim() || void 0;
|
|
641
|
+
const s = safeStringify(v);
|
|
642
|
+
if (!s) return void 0;
|
|
643
|
+
return s.trim() || void 0;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Returns true if the content-encoding header indicates a compressed body
|
|
647
|
+
* (gzip, br, deflate, x-gzip, x-deflate). Used to decide whether to send
|
|
648
|
+
* body as base64 + content-encoding for backend decompression.
|
|
649
|
+
*/
|
|
650
|
+
function isCompressedContentEncoding(headerValue) {
|
|
651
|
+
const raw = normalizeHeaderValue(headerValue);
|
|
652
|
+
if (!raw) return false;
|
|
653
|
+
const first = raw.split(",")[0].trim().toLowerCase();
|
|
654
|
+
return COMPRESSED_ENCODINGS.has(first);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Converts a buffer to a UTF-8 string for use as request/response body on spans.
|
|
658
|
+
* Returns null for null, undefined, or empty buffer.
|
|
659
|
+
*/
|
|
660
|
+
function bufferToBodyString(buffer) {
|
|
661
|
+
if (buffer == null || buffer.length === 0) return null;
|
|
662
|
+
return buffer.toString("utf8");
|
|
663
|
+
}
|
|
664
|
+
|
|
577
665
|
//#endregion
|
|
578
666
|
//#region src/utils/span-extractor.ts
|
|
579
667
|
/**
|
|
@@ -612,7 +700,7 @@ function shouldCaptureBody(domainRule, globalConfig, bodyType) {
|
|
|
612
700
|
*/
|
|
613
701
|
function extractSpanPayload(span, domainAllowList, globalHeadersAllowList, globalHeadersDenyList, globalCaptureRequestBody, globalCaptureResponseBody, headerRedaction) {
|
|
614
702
|
const attributes = span.attributes;
|
|
615
|
-
const url = attributes
|
|
703
|
+
const url = getHttpUrlFromAttributes(attributes);
|
|
616
704
|
const domainRule = url ? getDomainRule(url, domainAllowList) : void 0;
|
|
617
705
|
const headersAllowList = domainRule?.headersAllowList ?? globalHeadersAllowList;
|
|
618
706
|
const headersDenyList = domainRule?.headersDenyList ?? globalHeadersDenyList;
|
|
@@ -662,11 +750,10 @@ function extractSpanPayload(span, domainAllowList, globalHeadersAllowList, globa
|
|
|
662
750
|
* OpenTelemetry context keys for PingOps
|
|
663
751
|
*/
|
|
664
752
|
/**
|
|
665
|
-
* Context key for
|
|
666
|
-
*
|
|
667
|
-
* This allows wrapHttp to control which HTTP calls are captured.
|
|
753
|
+
* Context key for trace ID attribute.
|
|
754
|
+
* Used to propagate trace identifier to all spans in the context.
|
|
668
755
|
*/
|
|
669
|
-
const
|
|
756
|
+
const PINGOPS_TRACE_ID = createContextKey("pingops-trace-id");
|
|
670
757
|
/**
|
|
671
758
|
* Context key for user ID attribute.
|
|
672
759
|
* Used to propagate user identifier to all spans in the context.
|
|
@@ -690,13 +777,11 @@ const PINGOPS_METADATA = createContextKey("pingops-metadata");
|
|
|
690
777
|
/**
|
|
691
778
|
* Context key for capturing request body.
|
|
692
779
|
* When set, controls whether request bodies should be captured for HTTP spans.
|
|
693
|
-
* This allows wrapHttp to control body capture per-request.
|
|
694
780
|
*/
|
|
695
781
|
const PINGOPS_CAPTURE_REQUEST_BODY = createContextKey("pingops-capture-request-body");
|
|
696
782
|
/**
|
|
697
783
|
* Context key for capturing response body.
|
|
698
784
|
* When set, controls whether response bodies should be captured for HTTP spans.
|
|
699
|
-
* This allows wrapHttp to control body capture per-request.
|
|
700
785
|
*/
|
|
701
786
|
const PINGOPS_CAPTURE_RESPONSE_BODY = createContextKey("pingops-capture-response-body");
|
|
702
787
|
|
|
@@ -711,6 +796,8 @@ const PINGOPS_CAPTURE_RESPONSE_BODY = createContextKey("pingops-capture-response
|
|
|
711
796
|
*/
|
|
712
797
|
function getPropagatedAttributesFromContext(parentContext) {
|
|
713
798
|
const attributes = {};
|
|
799
|
+
const traceId = parentContext.getValue(PINGOPS_TRACE_ID);
|
|
800
|
+
if (traceId !== void 0 && typeof traceId === "string") attributes["pingops.trace_id"] = traceId;
|
|
714
801
|
const userId = parentContext.getValue(PINGOPS_USER_ID);
|
|
715
802
|
if (userId !== void 0 && typeof userId === "string") attributes["pingops.user_id"] = userId;
|
|
716
803
|
const sessionId = parentContext.getValue(PINGOPS_SESSION_ID);
|
|
@@ -725,100 +812,30 @@ function getPropagatedAttributesFromContext(parentContext) {
|
|
|
725
812
|
}
|
|
726
813
|
|
|
727
814
|
//#endregion
|
|
728
|
-
//#region src/
|
|
815
|
+
//#region src/trace-id.ts
|
|
729
816
|
/**
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
* This function sets attributes (userId, sessionId, tags, metadata) in the OpenTelemetry
|
|
733
|
-
* context, which are automatically propagated to all spans created within the wrapped function.
|
|
734
|
-
*
|
|
735
|
-
* Instrumentation behavior:
|
|
736
|
-
* - If `initializePingops` was called: All HTTP requests are instrumented by default.
|
|
737
|
-
* `wrapHttp` only adds attributes to spans created within the wrapped block.
|
|
738
|
-
* - If `initializePingops` was NOT called: Only HTTP requests within `wrapHttp` blocks
|
|
739
|
-
* are instrumented. Requests outside `wrapHttp` are not instrumented.
|
|
817
|
+
* Deterministic and random trace ID generation for PingOps
|
|
740
818
|
*/
|
|
741
|
-
const logger = createLogger("[PingOps wrapHttp]");
|
|
742
819
|
/**
|
|
743
|
-
*
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
*
|
|
748
|
-
* Instrumentation behavior:
|
|
749
|
-
* - If `initializePingops` was called: All HTTP requests are instrumented by default.
|
|
750
|
-
* `wrapHttp` only adds attributes to spans created within the wrapped block.
|
|
751
|
-
* - If `initializePingops` was NOT called: Only HTTP requests within `wrapHttp` blocks
|
|
752
|
-
* are instrumented. Requests outside `wrapHttp` are not instrumented.
|
|
753
|
-
*
|
|
754
|
-
* Note: This is the low-level API. For a simpler API with automatic setup,
|
|
755
|
-
* use `wrapHttp` from `@pingops/sdk` instead.
|
|
756
|
-
*
|
|
757
|
-
* @param options - Options including attributes and required callbacks
|
|
758
|
-
* @param fn - Function to execute within the attribute context
|
|
759
|
-
* @returns The result of the function
|
|
760
|
-
*/
|
|
761
|
-
function wrapHttp(options, fn) {
|
|
762
|
-
logger.debug("wrapHttp called", {
|
|
763
|
-
hasAttributes: !!options.attributes,
|
|
764
|
-
hasUserId: !!options.attributes?.userId,
|
|
765
|
-
hasSessionId: !!options.attributes?.sessionId,
|
|
766
|
-
hasTags: !!options.attributes?.tags,
|
|
767
|
-
hasMetadata: !!options.attributes?.metadata
|
|
768
|
-
});
|
|
769
|
-
const normalizedOptions = "checkInitialized" in options && "isGlobalInstrumentationEnabled" in options ? options : (() => {
|
|
770
|
-
throw new Error("wrapHttp requires checkInitialized and isGlobalInstrumentationEnabled callbacks. Use wrapHttp from @pingops/sdk for automatic setup.");
|
|
771
|
-
})();
|
|
772
|
-
const { checkInitialized, ensureInitialized } = normalizedOptions;
|
|
773
|
-
if (checkInitialized()) {
|
|
774
|
-
logger.debug("SDK already initialized, executing wrapHttp synchronously");
|
|
775
|
-
return executeWrapHttpWithContext(normalizedOptions, fn);
|
|
776
|
-
}
|
|
777
|
-
if (ensureInitialized) {
|
|
778
|
-
logger.debug("SDK not initialized, using provided ensureInitialized callback");
|
|
779
|
-
return ensureInitialized().then(() => {
|
|
780
|
-
logger.debug("SDK initialized, executing wrapHttp");
|
|
781
|
-
return executeWrapHttpWithContext(normalizedOptions, fn);
|
|
782
|
-
}).catch((error) => {
|
|
783
|
-
logger.error("Failed to initialize SDK for wrapHttp", { error: error instanceof Error ? error.message : String(error) });
|
|
784
|
-
throw error;
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
logger.debug("SDK not initialized and no ensureInitialized callback provided, executing wrapHttp");
|
|
788
|
-
return executeWrapHttpWithContext(normalizedOptions, fn);
|
|
820
|
+
* Converts a Uint8Array to a lowercase hex string.
|
|
821
|
+
*/
|
|
822
|
+
function uint8ArrayToHex(bytes) {
|
|
823
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
789
824
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
if (attributes.userId !== void 0) contextWithAttributes = contextWithAttributes.setValue(PINGOPS_USER_ID, attributes.userId);
|
|
801
|
-
if (attributes.sessionId !== void 0) contextWithAttributes = contextWithAttributes.setValue(PINGOPS_SESSION_ID, attributes.sessionId);
|
|
802
|
-
if (attributes.tags !== void 0) contextWithAttributes = contextWithAttributes.setValue(PINGOPS_TAGS, attributes.tags);
|
|
803
|
-
if (attributes.metadata !== void 0) contextWithAttributes = contextWithAttributes.setValue(PINGOPS_METADATA, attributes.metadata);
|
|
804
|
-
if (attributes.captureRequestBody !== void 0) contextWithAttributes = contextWithAttributes.setValue(PINGOPS_CAPTURE_REQUEST_BODY, attributes.captureRequestBody);
|
|
805
|
-
if (attributes.captureResponseBody !== void 0) contextWithAttributes = contextWithAttributes.setValue(PINGOPS_CAPTURE_RESPONSE_BODY, attributes.captureResponseBody);
|
|
825
|
+
/**
|
|
826
|
+
* Creates a trace ID (32 hex chars).
|
|
827
|
+
* - If `seed` is provided: deterministic via SHA-256 of the seed (first 32 hex chars).
|
|
828
|
+
* - Otherwise: random 16 bytes as 32 hex chars.
|
|
829
|
+
*/
|
|
830
|
+
async function createTraceId(seed) {
|
|
831
|
+
if (seed) {
|
|
832
|
+
const data = new TextEncoder().encode(seed);
|
|
833
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
834
|
+
return uint8ArrayToHex(new Uint8Array(hashBuffer)).slice(0, 32);
|
|
806
835
|
}
|
|
807
|
-
return
|
|
808
|
-
try {
|
|
809
|
-
const result = fn();
|
|
810
|
-
if (result instanceof Promise) return result.catch((err) => {
|
|
811
|
-
logger.error("Error in wrapHttp async execution", { error: err instanceof Error ? err.message : String(err) });
|
|
812
|
-
throw err;
|
|
813
|
-
});
|
|
814
|
-
return result;
|
|
815
|
-
} catch (err) {
|
|
816
|
-
logger.error("Error in wrapHttp sync execution", { error: err instanceof Error ? err.message : String(err) });
|
|
817
|
-
throw err;
|
|
818
|
-
}
|
|
819
|
-
});
|
|
836
|
+
return uint8ArrayToHex(crypto.getRandomValues(new Uint8Array(16)));
|
|
820
837
|
}
|
|
821
838
|
|
|
822
839
|
//#endregion
|
|
823
|
-
export { DEFAULT_REDACTION_CONFIG, DEFAULT_SENSITIVE_HEADER_PATTERNS, HeaderRedactionStrategy, PINGOPS_CAPTURE_REQUEST_BODY, PINGOPS_CAPTURE_RESPONSE_BODY,
|
|
840
|
+
export { DEFAULT_REDACTION_CONFIG, DEFAULT_SENSITIVE_HEADER_PATTERNS, HTTP_RESPONSE_CONTENT_ENCODING, HeaderRedactionStrategy, PINGOPS_CAPTURE_REQUEST_BODY, PINGOPS_CAPTURE_RESPONSE_BODY, PINGOPS_METADATA, PINGOPS_SESSION_ID, PINGOPS_TAGS, PINGOPS_TRACE_ID, PINGOPS_USER_ID, bufferToBodyString, createLogger, createTraceId, extractHeadersFromAttributes, extractSpanPayload, filterHeaders, getHttpUrlFromAttributes, getPropagatedAttributesFromContext, hasHttpMethodAttribute, hasHttpUrlAttribute, isCompressedContentEncoding, isSensitiveHeader, isSpanEligible, normalizeHeaders, redactHeaderValue, shouldCaptureSpan, uint8ArrayToHex };
|
|
824
841
|
//# sourceMappingURL=index.mjs.map
|