@pingops/core 0.1.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/context-keys.d.ts +30 -0
- package/dist/context-keys.d.ts.map +1 -0
- package/dist/context-keys.js +31 -0
- package/dist/context-keys.js.map +1 -0
- package/dist/filtering/domain-filter.d.ts +9 -0
- package/dist/filtering/domain-filter.d.ts.map +1 -0
- package/dist/filtering/domain-filter.js +136 -0
- package/dist/filtering/domain-filter.js.map +1 -0
- package/dist/filtering/header-filter.d.ts +31 -0
- package/dist/filtering/header-filter.d.ts.map +1 -0
- package/dist/filtering/header-filter.js +187 -0
- package/dist/filtering/header-filter.js.map +1 -0
- package/dist/filtering/span-filter.d.ts +13 -0
- package/dist/filtering/span-filter.d.ts.map +1 -0
- package/dist/filtering/span-filter.js +46 -0
- package/dist/filtering/span-filter.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +21 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +36 -0
- package/dist/logger.js.map +1 -0
- package/dist/transport/client.d.ts +46 -0
- package/dist/transport/client.d.ts.map +1 -0
- package/dist/transport/client.js +110 -0
- package/dist/transport/client.js.map +1 -0
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/context-extractor.d.ts +13 -0
- package/dist/utils/context-extractor.d.ts.map +1 -0
- package/dist/utils/context-extractor.js +44 -0
- package/dist/utils/context-extractor.js.map +1 -0
- package/dist/utils/span-extractor.d.ts +10 -0
- package/dist/utils/span-extractor.d.ts.map +1 -0
- package/dist/utils/span-extractor.js +125 -0
- package/dist/utils/span-extractor.js.map +1 -0
- package/dist/wrap-http.d.ts +55 -0
- package/dist/wrap-http.d.ts.map +1 -0
- package/dist/wrap-http.js +129 -0
- package/dist/wrap-http.js.map +1 -0
- package/package.json +34 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry context keys for PingOps
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Context key for enabling HTTP instrumentation.
|
|
6
|
+
* When set to true, HTTP requests will be automatically instrumented.
|
|
7
|
+
* This allows wrapHttp to control which HTTP calls are captured.
|
|
8
|
+
*/
|
|
9
|
+
export declare const PINGOPS_HTTP_ENABLED: symbol;
|
|
10
|
+
/**
|
|
11
|
+
* Context key for user ID attribute.
|
|
12
|
+
* Used to propagate user identifier to all spans in the context.
|
|
13
|
+
*/
|
|
14
|
+
export declare const PINGOPS_USER_ID: symbol;
|
|
15
|
+
/**
|
|
16
|
+
* Context key for session ID attribute.
|
|
17
|
+
* Used to propagate session identifier to all spans in the context.
|
|
18
|
+
*/
|
|
19
|
+
export declare const PINGOPS_SESSION_ID: symbol;
|
|
20
|
+
/**
|
|
21
|
+
* Context key for tags attribute.
|
|
22
|
+
* Used to propagate tags array to all spans in the context.
|
|
23
|
+
*/
|
|
24
|
+
export declare const PINGOPS_TAGS: symbol;
|
|
25
|
+
/**
|
|
26
|
+
* Context key for metadata attribute.
|
|
27
|
+
* Used to propagate metadata object to all spans in the context.
|
|
28
|
+
*/
|
|
29
|
+
export declare const PINGOPS_METADATA: symbol;
|
|
30
|
+
//# sourceMappingURL=context-keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-keys.d.ts","sourceRoot":"","sources":["../src/context-keys.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,QAA2C,CAAC;AAE7E;;;GAGG;AACH,eAAO,MAAM,eAAe,QAAsC,CAAC;AAEnE;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAyC,CAAC;AAEzE;;;GAGG;AACH,eAAO,MAAM,YAAY,QAAmC,CAAC;AAE7D;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QAAuC,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry context keys for PingOps
|
|
3
|
+
*/
|
|
4
|
+
import { createContextKey } from "@opentelemetry/api";
|
|
5
|
+
/**
|
|
6
|
+
* Context key for enabling HTTP instrumentation.
|
|
7
|
+
* When set to true, HTTP requests will be automatically instrumented.
|
|
8
|
+
* This allows wrapHttp to control which HTTP calls are captured.
|
|
9
|
+
*/
|
|
10
|
+
export const PINGOPS_HTTP_ENABLED = createContextKey("pingops-http-enabled");
|
|
11
|
+
/**
|
|
12
|
+
* Context key for user ID attribute.
|
|
13
|
+
* Used to propagate user identifier to all spans in the context.
|
|
14
|
+
*/
|
|
15
|
+
export const PINGOPS_USER_ID = createContextKey("pingops-user-id");
|
|
16
|
+
/**
|
|
17
|
+
* Context key for session ID attribute.
|
|
18
|
+
* Used to propagate session identifier to all spans in the context.
|
|
19
|
+
*/
|
|
20
|
+
export const PINGOPS_SESSION_ID = createContextKey("pingops-session-id");
|
|
21
|
+
/**
|
|
22
|
+
* Context key for tags attribute.
|
|
23
|
+
* Used to propagate tags array to all spans in the context.
|
|
24
|
+
*/
|
|
25
|
+
export const PINGOPS_TAGS = createContextKey("pingops-tags");
|
|
26
|
+
/**
|
|
27
|
+
* Context key for metadata attribute.
|
|
28
|
+
* Used to propagate metadata object to all spans in the context.
|
|
29
|
+
*/
|
|
30
|
+
export const PINGOPS_METADATA = createContextKey("pingops-metadata");
|
|
31
|
+
//# sourceMappingURL=context-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-keys.js","sourceRoot":"","sources":["../src/context-keys.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,gBAAgB,CAAC,sBAAsB,CAAC,CAAC;AAE7E;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,gBAAgB,CAAC,iBAAiB,CAAC,CAAC;AAEnE;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,gBAAgB,CAAC,oBAAoB,CAAC,CAAC;AAEzE;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,gBAAgB,CAAC,cAAc,CAAC,CAAC;AAE7D;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,kBAAkB,CAAC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain filtering logic - applies allow/deny list rules
|
|
3
|
+
*/
|
|
4
|
+
import type { DomainRule } from "../types";
|
|
5
|
+
/**
|
|
6
|
+
* Determines if a span should be captured based on domain rules
|
|
7
|
+
*/
|
|
8
|
+
export declare function shouldCaptureSpan(url: string, domainAllowList?: DomainRule[], domainDenyList?: DomainRule[]): boolean;
|
|
9
|
+
//# sourceMappingURL=domain-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain-filter.d.ts","sourceRoot":"","sources":["../../src/filtering/domain-filter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AA6D3C;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,eAAe,CAAC,EAAE,UAAU,EAAE,EAC9B,cAAc,CAAC,EAAE,UAAU,EAAE,GAC5B,OAAO,CAkFT"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain filtering logic - applies allow/deny list rules
|
|
3
|
+
*/
|
|
4
|
+
import { createLogger } from "../logger";
|
|
5
|
+
const log = createLogger("[PingOps DomainFilter]");
|
|
6
|
+
/**
|
|
7
|
+
* Extracts domain from a URL
|
|
8
|
+
*/
|
|
9
|
+
function extractDomain(url) {
|
|
10
|
+
try {
|
|
11
|
+
const urlObj = new URL(url);
|
|
12
|
+
const domain = urlObj.hostname;
|
|
13
|
+
log.debug("Extracted domain from URL", { url, domain });
|
|
14
|
+
return domain;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// If URL parsing fails, try to extract domain from string
|
|
18
|
+
const match = url.match(/^(?:https?:\/\/)?([^/]+)/);
|
|
19
|
+
const domain = match ? match[1] : "";
|
|
20
|
+
log.debug("Extracted domain from URL (fallback)", { url, domain });
|
|
21
|
+
return domain;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Checks if a domain matches a rule (exact or suffix match)
|
|
26
|
+
*/
|
|
27
|
+
function domainMatches(domain, ruleDomain) {
|
|
28
|
+
// Exact match
|
|
29
|
+
if (domain === ruleDomain) {
|
|
30
|
+
log.debug("Domain exact match", { domain, ruleDomain });
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
// Suffix match (e.g., .github.com matches api.github.com)
|
|
34
|
+
if (ruleDomain.startsWith(".")) {
|
|
35
|
+
const matches = domain.endsWith(ruleDomain) || domain === ruleDomain.slice(1);
|
|
36
|
+
log.debug("Domain suffix match check", { domain, ruleDomain, matches });
|
|
37
|
+
return matches;
|
|
38
|
+
}
|
|
39
|
+
log.debug("Domain does not match", { domain, ruleDomain });
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Checks if a path matches any of the allowed paths (prefix match)
|
|
44
|
+
*/
|
|
45
|
+
function pathMatches(path, allowedPaths) {
|
|
46
|
+
if (!allowedPaths || allowedPaths.length === 0) {
|
|
47
|
+
log.debug("No path restrictions, all paths match", { path });
|
|
48
|
+
return true; // No path restrictions means all paths match
|
|
49
|
+
}
|
|
50
|
+
const matches = allowedPaths.some((allowedPath) => path.startsWith(allowedPath));
|
|
51
|
+
log.debug("Path match check", { path, allowedPaths, matches });
|
|
52
|
+
return matches;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Determines if a span should be captured based on domain rules
|
|
56
|
+
*/
|
|
57
|
+
export function shouldCaptureSpan(url, domainAllowList, domainDenyList) {
|
|
58
|
+
log.debug("Checking domain filter rules", {
|
|
59
|
+
url,
|
|
60
|
+
hasAllowList: !!domainAllowList && domainAllowList.length > 0,
|
|
61
|
+
hasDenyList: !!domainDenyList && domainDenyList.length > 0,
|
|
62
|
+
allowListCount: domainAllowList?.length || 0,
|
|
63
|
+
denyListCount: domainDenyList?.length || 0,
|
|
64
|
+
});
|
|
65
|
+
const domain = extractDomain(url);
|
|
66
|
+
// Extract path from URL
|
|
67
|
+
let path = "/";
|
|
68
|
+
try {
|
|
69
|
+
const urlObj = new URL(url);
|
|
70
|
+
path = urlObj.pathname;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// If URL parsing fails, try to extract path from string
|
|
74
|
+
const pathMatch = url.match(/^(?:https?:\/\/)?[^/]+(\/.*)?$/);
|
|
75
|
+
path = pathMatch && pathMatch[1] ? pathMatch[1] : "/";
|
|
76
|
+
}
|
|
77
|
+
log.debug("Extracted domain and path", { url, domain, path });
|
|
78
|
+
// Deny list is evaluated first - if domain is denied, don't capture
|
|
79
|
+
if (domainDenyList) {
|
|
80
|
+
for (const rule of domainDenyList) {
|
|
81
|
+
if (domainMatches(domain, rule.domain)) {
|
|
82
|
+
log.info("Domain denied by deny list", {
|
|
83
|
+
domain,
|
|
84
|
+
ruleDomain: rule.domain,
|
|
85
|
+
url,
|
|
86
|
+
});
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
log.debug("Domain passed deny list check", { domain });
|
|
91
|
+
}
|
|
92
|
+
// If no allow list, capture all (except denied)
|
|
93
|
+
if (!domainAllowList || domainAllowList.length === 0) {
|
|
94
|
+
log.debug("No allow list configured, capturing span", { domain, url });
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
// Check if domain matches any allow list rule
|
|
98
|
+
for (const rule of domainAllowList) {
|
|
99
|
+
if (domainMatches(domain, rule.domain)) {
|
|
100
|
+
// If paths are specified, check path match
|
|
101
|
+
if (rule.paths && rule.paths.length > 0) {
|
|
102
|
+
const pathMatch = pathMatches(path, rule.paths);
|
|
103
|
+
if (pathMatch) {
|
|
104
|
+
log.info("Domain and path allowed by allow list", {
|
|
105
|
+
domain,
|
|
106
|
+
ruleDomain: rule.domain,
|
|
107
|
+
path,
|
|
108
|
+
allowedPaths: rule.paths,
|
|
109
|
+
url,
|
|
110
|
+
});
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
log.debug("Domain allowed but path not matched", {
|
|
115
|
+
domain,
|
|
116
|
+
ruleDomain: rule.domain,
|
|
117
|
+
path,
|
|
118
|
+
allowedPaths: rule.paths,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
log.info("Domain allowed by allow list", {
|
|
124
|
+
domain,
|
|
125
|
+
ruleDomain: rule.domain,
|
|
126
|
+
url,
|
|
127
|
+
});
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Domain not in allow list
|
|
133
|
+
log.info("Domain not in allow list, filtering out", { domain, url });
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=domain-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain-filter.js","sourceRoot":"","sources":["../../src/filtering/domain-filter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header filtering logic - applies allow/deny list rules
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Filters headers based on allow/deny lists
|
|
6
|
+
* - Deny list always wins (if header is in deny list, exclude it)
|
|
7
|
+
* - Allow list filters included headers (if specified, only include these)
|
|
8
|
+
* - Case-insensitive matching
|
|
9
|
+
*/
|
|
10
|
+
export declare function filterHeaders(headers: Record<string, string | string[] | undefined>, headersAllowList?: string[], headersDenyList?: string[]): Record<string, string | string[] | undefined>;
|
|
11
|
+
/**
|
|
12
|
+
* Extracts and normalizes headers from OpenTelemetry span attributes
|
|
13
|
+
*
|
|
14
|
+
* Handles flat array format headers (e.g., 'http.request.header.0', 'http.request.header.1')
|
|
15
|
+
* and converts them to proper key-value objects.
|
|
16
|
+
*
|
|
17
|
+
* Some OpenTelemetry instrumentations store headers as flat arrays:
|
|
18
|
+
* - 'http.request.header.0': 'Content-Type'
|
|
19
|
+
* - 'http.request.header.1': 'application/json'
|
|
20
|
+
* - 'http.request.header.2': 'Authorization'
|
|
21
|
+
* - 'http.request.header.3': 'Bearer token'
|
|
22
|
+
*
|
|
23
|
+
* This function converts them to:
|
|
24
|
+
* - { 'Content-Type': 'application/json', 'Authorization': 'Bearer token' }
|
|
25
|
+
*/
|
|
26
|
+
export declare function extractHeadersFromAttributes(attributes: Record<string, unknown>, headerPrefix: "http.request.header" | "http.response.header"): Record<string, string | string[] | undefined> | null;
|
|
27
|
+
/**
|
|
28
|
+
* Normalizes headers from various sources into a proper key-value object
|
|
29
|
+
*/
|
|
30
|
+
export declare function normalizeHeaders(headers: unknown): Record<string, string | string[] | undefined>;
|
|
31
|
+
//# sourceMappingURL=header-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"header-filter.d.ts","sourceRoot":"","sources":["../../src/filtering/header-filter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAaH;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,EACtD,gBAAgB,CAAC,EAAE,MAAM,EAAE,EAC3B,eAAe,CAAC,EAAE,MAAM,EAAE,GACzB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAkD/C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,YAAY,EAAE,qBAAqB,GAAG,sBAAsB,GAC3D,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAyDtD;AAgBD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,GACf,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAqD/C"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header filtering logic - applies allow/deny list rules
|
|
3
|
+
*/
|
|
4
|
+
import { createLogger } from "../logger";
|
|
5
|
+
const log = createLogger("[PingOps HeaderFilter]");
|
|
6
|
+
/**
|
|
7
|
+
* Normalizes header name to lowercase for case-insensitive matching
|
|
8
|
+
*/
|
|
9
|
+
function normalizeHeaderName(name) {
|
|
10
|
+
return name.toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Filters headers based on allow/deny lists
|
|
14
|
+
* - Deny list always wins (if header is in deny list, exclude it)
|
|
15
|
+
* - Allow list filters included headers (if specified, only include these)
|
|
16
|
+
* - Case-insensitive matching
|
|
17
|
+
*/
|
|
18
|
+
export function filterHeaders(headers, headersAllowList, headersDenyList) {
|
|
19
|
+
const originalCount = Object.keys(headers).length;
|
|
20
|
+
log.debug("Filtering headers", {
|
|
21
|
+
originalHeaderCount: originalCount,
|
|
22
|
+
hasAllowList: !!headersAllowList && headersAllowList.length > 0,
|
|
23
|
+
hasDenyList: !!headersDenyList && headersDenyList.length > 0,
|
|
24
|
+
allowListCount: headersAllowList?.length || 0,
|
|
25
|
+
denyListCount: headersDenyList?.length || 0,
|
|
26
|
+
});
|
|
27
|
+
const normalizedDenyList = headersDenyList?.map(normalizeHeaderName) ?? [];
|
|
28
|
+
const normalizedAllowList = headersAllowList?.map(normalizeHeaderName) ?? [];
|
|
29
|
+
const filtered = {};
|
|
30
|
+
const deniedHeaders = [];
|
|
31
|
+
const excludedHeaders = [];
|
|
32
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
33
|
+
const normalizedName = normalizeHeaderName(name);
|
|
34
|
+
// Deny list always wins
|
|
35
|
+
if (normalizedDenyList.includes(normalizedName)) {
|
|
36
|
+
deniedHeaders.push(name);
|
|
37
|
+
log.debug("Header denied by deny list", { headerName: name });
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
// If allow list exists, only include headers in the list
|
|
41
|
+
if (normalizedAllowList.length > 0) {
|
|
42
|
+
if (!normalizedAllowList.includes(normalizedName)) {
|
|
43
|
+
excludedHeaders.push(name);
|
|
44
|
+
log.debug("Header excluded (not in allow list)", { headerName: name });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
filtered[name] = value;
|
|
49
|
+
}
|
|
50
|
+
const filteredCount = Object.keys(filtered).length;
|
|
51
|
+
log.info("Header filtering complete", {
|
|
52
|
+
originalCount,
|
|
53
|
+
filteredCount,
|
|
54
|
+
deniedCount: deniedHeaders.length,
|
|
55
|
+
excludedCount: excludedHeaders.length,
|
|
56
|
+
deniedHeaders: deniedHeaders.length > 0 ? deniedHeaders : undefined,
|
|
57
|
+
excludedHeaders: excludedHeaders.length > 0 ? excludedHeaders : undefined,
|
|
58
|
+
});
|
|
59
|
+
return filtered;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Extracts and normalizes headers from OpenTelemetry span attributes
|
|
63
|
+
*
|
|
64
|
+
* Handles flat array format headers (e.g., 'http.request.header.0', 'http.request.header.1')
|
|
65
|
+
* and converts them to proper key-value objects.
|
|
66
|
+
*
|
|
67
|
+
* Some OpenTelemetry instrumentations store headers as flat arrays:
|
|
68
|
+
* - 'http.request.header.0': 'Content-Type'
|
|
69
|
+
* - 'http.request.header.1': 'application/json'
|
|
70
|
+
* - 'http.request.header.2': 'Authorization'
|
|
71
|
+
* - 'http.request.header.3': 'Bearer token'
|
|
72
|
+
*
|
|
73
|
+
* This function converts them to:
|
|
74
|
+
* - { 'Content-Type': 'application/json', 'Authorization': 'Bearer token' }
|
|
75
|
+
*/
|
|
76
|
+
export function extractHeadersFromAttributes(attributes, headerPrefix) {
|
|
77
|
+
const headerMap = {};
|
|
78
|
+
const headerKeys = [];
|
|
79
|
+
// Find all keys matching the pattern (e.g., 'http.request.header.0', 'http.request.header.1', etc.)
|
|
80
|
+
for (const key in attributes) {
|
|
81
|
+
if (key.startsWith(`${headerPrefix}.`) && key !== headerPrefix) {
|
|
82
|
+
const match = key.match(new RegExp(`^${headerPrefix}\\.(\\d+)$`));
|
|
83
|
+
if (match) {
|
|
84
|
+
const index = parseInt(match[1], 10);
|
|
85
|
+
headerKeys.push(index);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// If no flat array headers found, return null
|
|
90
|
+
if (headerKeys.length === 0) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
// Sort indices to process in order
|
|
94
|
+
headerKeys.sort((a, b) => a - b);
|
|
95
|
+
// Convert flat array to key-value pairs
|
|
96
|
+
// Even indices are header names, odd indices are header values
|
|
97
|
+
for (let i = 0; i < headerKeys.length; i += 2) {
|
|
98
|
+
const nameIndex = headerKeys[i];
|
|
99
|
+
const valueIndex = headerKeys[i + 1];
|
|
100
|
+
if (valueIndex !== undefined) {
|
|
101
|
+
const nameKey = `${headerPrefix}.${nameIndex}`;
|
|
102
|
+
const valueKey = `${headerPrefix}.${valueIndex}`;
|
|
103
|
+
const headerName = attributes[nameKey];
|
|
104
|
+
const headerValue = attributes[valueKey];
|
|
105
|
+
if (headerName && headerValue !== undefined) {
|
|
106
|
+
// Handle multiple values for the same header name (case-insensitive)
|
|
107
|
+
const normalizedName = headerName.toLowerCase();
|
|
108
|
+
const existingKey = Object.keys(headerMap).find((k) => k.toLowerCase() === normalizedName);
|
|
109
|
+
if (existingKey) {
|
|
110
|
+
const existing = headerMap[existingKey];
|
|
111
|
+
headerMap[existingKey] = Array.isArray(existing)
|
|
112
|
+
? [...existing, headerValue]
|
|
113
|
+
: [existing, headerValue];
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// Use original case for the first occurrence
|
|
117
|
+
headerMap[headerName] = headerValue;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return Object.keys(headerMap).length > 0 ? headerMap : null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Type guard to check if value is a Headers-like object
|
|
126
|
+
*/
|
|
127
|
+
function isHeadersLike(headers) {
|
|
128
|
+
return (typeof headers === "object" &&
|
|
129
|
+
headers !== null &&
|
|
130
|
+
"entries" in headers &&
|
|
131
|
+
typeof headers.entries === "function");
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Normalizes headers from various sources into a proper key-value object
|
|
135
|
+
*/
|
|
136
|
+
export function normalizeHeaders(headers) {
|
|
137
|
+
const result = {};
|
|
138
|
+
if (!headers) {
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
// Handle Headers object (from fetch/undici)
|
|
143
|
+
if (isHeadersLike(headers)) {
|
|
144
|
+
for (const [key, value] of headers.entries()) {
|
|
145
|
+
// Headers can have multiple values for the same key
|
|
146
|
+
if (result[key]) {
|
|
147
|
+
// Convert to array if not already
|
|
148
|
+
const existing = result[key];
|
|
149
|
+
result[key] = Array.isArray(existing)
|
|
150
|
+
? [...existing, value]
|
|
151
|
+
: [existing, value];
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
result[key] = value;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
// Handle plain object
|
|
160
|
+
if (typeof headers === "object" && !Array.isArray(headers)) {
|
|
161
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
162
|
+
// Skip numeric keys (array-like objects)
|
|
163
|
+
if (!/^\d+$/.test(key)) {
|
|
164
|
+
result[key] = value;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
// Handle array (shouldn't happen, but handle gracefully)
|
|
170
|
+
if (Array.isArray(headers)) {
|
|
171
|
+
// Try to reconstruct from array pairs
|
|
172
|
+
for (let i = 0; i < headers.length; i += 2) {
|
|
173
|
+
if (i + 1 < headers.length) {
|
|
174
|
+
const key = String(headers[i]);
|
|
175
|
+
const value = headers[i + 1];
|
|
176
|
+
result[key] = value;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// Fail silently - return empty object
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=header-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"header-filter.js","sourceRoot":"","sources":["../../src/filtering/header-filter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Span filtering logic - determines if a span is eligible for capture
|
|
3
|
+
*/
|
|
4
|
+
import type { ReadableSpan } from "@opentelemetry/sdk-trace-base";
|
|
5
|
+
/**
|
|
6
|
+
* Checks if a span is eligible for capture based on span kind and attributes.
|
|
7
|
+
* A span is eligible if:
|
|
8
|
+
* 1. span.kind === SpanKind.CLIENT
|
|
9
|
+
* 2. AND has HTTP attributes (http.method, http.url, or server.address)
|
|
10
|
+
* OR has GenAI attributes (gen_ai.system, gen_ai.operation.name)
|
|
11
|
+
*/
|
|
12
|
+
export declare function isSpanEligible(span: ReadableSpan): boolean;
|
|
13
|
+
//# sourceMappingURL=span-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"span-filter.d.ts","sourceRoot":"","sources":["../../src/filtering/span-filter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAKlE;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAqC1D"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Span filtering logic - determines if a span is eligible for capture
|
|
3
|
+
*/
|
|
4
|
+
import { SpanKind } from "@opentelemetry/api";
|
|
5
|
+
import { createLogger } from "../logger";
|
|
6
|
+
const log = createLogger("[PingOps SpanFilter]");
|
|
7
|
+
/**
|
|
8
|
+
* Checks if a span is eligible for capture based on span kind and attributes.
|
|
9
|
+
* A span is eligible if:
|
|
10
|
+
* 1. span.kind === SpanKind.CLIENT
|
|
11
|
+
* 2. AND has HTTP attributes (http.method, http.url, or server.address)
|
|
12
|
+
* OR has GenAI attributes (gen_ai.system, gen_ai.operation.name)
|
|
13
|
+
*/
|
|
14
|
+
export function isSpanEligible(span) {
|
|
15
|
+
log.debug("Checking span eligibility", {
|
|
16
|
+
spanName: span.name,
|
|
17
|
+
spanKind: span.kind,
|
|
18
|
+
spanId: span.spanContext().spanId,
|
|
19
|
+
traceId: span.spanContext().traceId,
|
|
20
|
+
});
|
|
21
|
+
// Must be a CLIENT span (outgoing request)
|
|
22
|
+
if (span.kind !== SpanKind.CLIENT) {
|
|
23
|
+
log.debug("Span not eligible: not CLIENT kind", {
|
|
24
|
+
spanName: span.name,
|
|
25
|
+
spanKind: span.kind,
|
|
26
|
+
});
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const attributes = span.attributes;
|
|
30
|
+
// Check for HTTP attributes
|
|
31
|
+
const hasHttpMethod = attributes["http.method"] !== undefined;
|
|
32
|
+
const hasHttpUrl = attributes["http.url"] !== undefined;
|
|
33
|
+
const hasServerAddress = attributes["server.address"] !== undefined;
|
|
34
|
+
const isEligible = hasHttpMethod || hasHttpUrl || hasServerAddress;
|
|
35
|
+
log.debug("Span eligibility check result", {
|
|
36
|
+
spanName: span.name,
|
|
37
|
+
isEligible,
|
|
38
|
+
httpAttributes: {
|
|
39
|
+
hasMethod: hasHttpMethod,
|
|
40
|
+
hasUrl: hasHttpUrl,
|
|
41
|
+
hasServerAddress,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
return isEligible;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=span-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"span-filter.js","sourceRoot":"","sources":["../../src/filtering/span-filter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,MAAM,GAAG,GAAG,YAAY,CAAC,sBAAsB,CAAC,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAkB;IAC/C,GAAG,CAAC,KAAK,CAAC,2BAA2B,EAAE;QACrC,QAAQ,EAAE,IAAI,CAAC,IAAI;QACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;QACnB,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,MAAM;QACjC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO;KACpC,CAAC,CAAC;IAEH,2CAA2C;IAC3C,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;QAClC,GAAG,CAAC,KAAK,CAAC,oCAAoC,EAAE;YAC9C,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;SACpB,CAAC,CAAC;QACH,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;IAEnC,4BAA4B;IAC5B,MAAM,aAAa,GAAG,UAAU,CAAC,aAAa,CAAC,KAAK,SAAS,CAAC;IAC9D,MAAM,UAAU,GAAG,UAAU,CAAC,UAAU,CAAC,KAAK,SAAS,CAAC;IACxD,MAAM,gBAAgB,GAAG,UAAU,CAAC,gBAAgB,CAAC,KAAK,SAAS,CAAC;IAEpE,MAAM,UAAU,GAAG,aAAa,IAAI,UAAU,IAAI,gBAAgB,CAAC;IAEnE,GAAG,CAAC,KAAK,CAAC,+BAA+B,EAAE;QACzC,QAAQ,EAAE,IAAI,CAAC,IAAI;QACnB,UAAU;QACV,cAAc,EAAE;YACd,SAAS,EAAE,aAAa;YACxB,MAAM,EAAE,UAAU;YAClB,gBAAgB;SACjB;KACF,CAAC,CAAC;IAEH,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pingops/core - Internal shared utilities
|
|
3
|
+
*/
|
|
4
|
+
export * from "./types";
|
|
5
|
+
export * from "./filtering/span-filter";
|
|
6
|
+
export * from "./filtering/domain-filter";
|
|
7
|
+
export * from "./filtering/header-filter";
|
|
8
|
+
export * from "./utils/span-extractor";
|
|
9
|
+
export * from "./utils/context-extractor";
|
|
10
|
+
export * from "./logger";
|
|
11
|
+
export * from "./context-keys";
|
|
12
|
+
export * from "./wrap-http";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pingops/core - Internal shared utilities
|
|
3
|
+
*/
|
|
4
|
+
export * from "./types";
|
|
5
|
+
export * from "./filtering/span-filter";
|
|
6
|
+
export * from "./filtering/domain-filter";
|
|
7
|
+
export * from "./filtering/header-filter";
|
|
8
|
+
export * from "./utils/span-extractor";
|
|
9
|
+
export * from "./utils/context-extractor";
|
|
10
|
+
export * from "./logger";
|
|
11
|
+
export * from "./context-keys";
|
|
12
|
+
export * from "./wrap-http";
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global logger utility for PingOps Core
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent logging across all core components with support for
|
|
5
|
+
* different log levels and debug mode control via PINGOPS_DEBUG environment variable.
|
|
6
|
+
*/
|
|
7
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
8
|
+
export interface Logger {
|
|
9
|
+
debug(message: string, ...args: unknown[]): void;
|
|
10
|
+
info(message: string, ...args: unknown[]): void;
|
|
11
|
+
warn(message: string, ...args: unknown[]): void;
|
|
12
|
+
error(message: string, ...args: unknown[]): void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates a logger instance with a specific prefix
|
|
16
|
+
*
|
|
17
|
+
* @param prefix - Prefix to add to all log messages (e.g., '[PingOps Filter]')
|
|
18
|
+
* @returns Logger instance
|
|
19
|
+
*/
|
|
20
|
+
export declare function createLogger(prefix: string): Logger;
|
|
21
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACjD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAClD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAwBnD"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global logger utility for PingOps Core
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent logging across all core components with support for
|
|
5
|
+
* different log levels and debug mode control via PINGOPS_DEBUG environment variable.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Creates a logger instance with a specific prefix
|
|
9
|
+
*
|
|
10
|
+
* @param prefix - Prefix to add to all log messages (e.g., '[PingOps Filter]')
|
|
11
|
+
* @returns Logger instance
|
|
12
|
+
*/
|
|
13
|
+
export function createLogger(prefix) {
|
|
14
|
+
const isDebugEnabled = process.env.PINGOPS_DEBUG === "true";
|
|
15
|
+
const formatMessage = (level, message) => {
|
|
16
|
+
const timestamp = new Date().toISOString();
|
|
17
|
+
return `[${timestamp}] ${prefix} [${level.toUpperCase()}] ${message}`;
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
debug(message, ...args) {
|
|
21
|
+
if (isDebugEnabled) {
|
|
22
|
+
console.debug(formatMessage("debug", message), ...args);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
info(message, ...args) {
|
|
26
|
+
console.log(formatMessage("info", message), ...args);
|
|
27
|
+
},
|
|
28
|
+
warn(message, ...args) {
|
|
29
|
+
console.warn(formatMessage("warn", message), ...args);
|
|
30
|
+
},
|
|
31
|
+
error(message, ...args) {
|
|
32
|
+
console.error(formatMessage("error", message), ...args);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,MAAM,CAAC;IAE5D,MAAM,aAAa,GAAG,CAAC,KAAe,EAAE,OAAe,EAAU,EAAE;QACjE,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC3C,OAAO,IAAI,SAAS,KAAK,MAAM,KAAK,KAAK,CAAC,WAAW,EAAE,KAAK,OAAO,EAAE,CAAC;IACxE,CAAC,CAAC;IAEF,OAAO;QACL,KAAK,CAAC,OAAe,EAAE,GAAG,IAAe;YACvC,IAAI,cAAc,EAAE,CAAC;gBACnB,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC;QACD,IAAI,CAAC,OAAe,EAAE,GAAG,IAAe;YACtC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;QACvD,CAAC;QACD,IAAI,CAAC,OAAe,EAAE,GAAG,IAAe;YACtC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;QACxD,CAAC;QACD,KAAK,CAAC,OAAe,EAAE,GAAG,IAAe;YACvC,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1D,CAAC;KACF,CAAC;AACJ,CAAC"}
|