@foam-ai/node 0.1.0-alpha.1 → 0.1.0-alpha.10
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/README.md +649 -0
- package/dist/before-send.d.ts +16 -0
- package/dist/before-send.js +41 -0
- package/dist/constants.d.ts +44 -0
- package/dist/constants.js +67 -0
- package/dist/endpoint.d.ts +2 -0
- package/dist/endpoint.js +11 -0
- package/dist/exporters.d.ts +33 -0
- package/dist/exporters.js +179 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +28 -0
- package/dist/ingest.d.ts +12 -0
- package/dist/ingest.js +70 -0
- package/dist/init.d.ts +26 -0
- package/dist/init.js +169 -0
- package/dist/instrumentations.d.ts +4 -0
- package/dist/instrumentations.js +120 -0
- package/dist/library-versions.d.ts +1 -0
- package/dist/library-versions.js +59 -0
- package/dist/logs.d.ts +10 -0
- package/dist/logs.js +61 -0
- package/dist/metrics.d.ts +5 -0
- package/dist/metrics.js +29 -0
- package/dist/network-capture/collector.d.ts +27 -0
- package/dist/network-capture/collector.js +411 -0
- package/dist/network-capture/http.d.ts +5 -0
- package/dist/network-capture/http.js +221 -0
- package/dist/network-capture/index.d.ts +3 -0
- package/dist/network-capture/index.js +20 -0
- package/dist/network-capture/redact.d.ts +9 -0
- package/dist/network-capture/redact.js +401 -0
- package/dist/network-capture/support.d.ts +2 -0
- package/dist/network-capture/support.js +38 -0
- package/dist/network-capture/undici.d.ts +5 -0
- package/dist/network-capture/undici.js +177 -0
- package/dist/otlp.d.ts +8 -0
- package/dist/otlp.js +46 -0
- package/dist/propagation.d.ts +5 -0
- package/dist/propagation.js +47 -0
- package/dist/redaction-keys.d.ts +3 -0
- package/dist/redaction-keys.js +421 -0
- package/dist/redaction.d.ts +24 -0
- package/dist/redaction.js +270 -0
- package/dist/report.d.ts +9 -0
- package/dist/report.js +56 -0
- package/dist/resource.d.ts +3 -0
- package/dist/resource.js +24 -0
- package/dist/state.d.ts +26 -0
- package/dist/state.js +61 -0
- package/dist/traces.d.ts +1 -0
- package/dist/traces.js +21 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +20 -0
- package/package.json +47 -19
- package/dist/node/src/capture-exception.d.ts +0 -9
- package/dist/node/src/capture-exception.js +0 -31
- package/dist/node/src/index.d.ts +0 -9
- package/dist/node/src/index.js +0 -12
- package/dist/node/src/init.d.ts +0 -27
- package/dist/node/src/init.js +0 -203
- package/dist/shared/constants.d.ts +0 -1
- package/dist/shared/constants.js +0 -4
- package/dist/shared/util.d.ts +0 -9
- package/dist/shared/util.js +0 -21
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setActiveRedactionConfig = setActiveRedactionConfig;
|
|
4
|
+
exports.getActiveRedactionConfig = getActiveRedactionConfig;
|
|
5
|
+
exports.normalizeKey = normalizeKey;
|
|
6
|
+
exports.isFloorKey = isFloorKey;
|
|
7
|
+
exports.isSensitiveQueryKey = isSensitiveQueryKey;
|
|
8
|
+
exports.keyMatch = keyMatch;
|
|
9
|
+
exports.tailMask = tailMask;
|
|
10
|
+
exports.maskForMatch = maskForMatch;
|
|
11
|
+
exports.redactQueryPairs = redactQueryPairs;
|
|
12
|
+
exports.redactUrlLike = redactUrlLike;
|
|
13
|
+
exports.redactString = redactString;
|
|
14
|
+
exports.redactDeep = redactDeep;
|
|
15
|
+
exports.redactAttributeValue = redactAttributeValue;
|
|
16
|
+
exports.redactAttributesCopy = redactAttributesCopy;
|
|
17
|
+
exports.resolveRedactionConfig = resolveRedactionConfig;
|
|
18
|
+
const constants_js_1 = require("./constants.js");
|
|
19
|
+
const redaction_keys_js_1 = require("./redaction-keys.js");
|
|
20
|
+
const EMPTY_CONFIG = {
|
|
21
|
+
secretKeys: new Set(),
|
|
22
|
+
piiKeys: new Set(),
|
|
23
|
+
};
|
|
24
|
+
const FLOOR_KEYS = new Set(redaction_keys_js_1.SENSITIVE_KEYS);
|
|
25
|
+
const HTTP_HEADERS = new Set(redaction_keys_js_1.SENSITIVE_HTTP_HEADERS);
|
|
26
|
+
const QUERY_KEYS = new Set(redaction_keys_js_1.SENSITIVE_QUERY_KEYS);
|
|
27
|
+
const HEADER_PREFIXES = ["http.request.header.", "http.response.header."];
|
|
28
|
+
const BARE_QUERY_KEYS = new Set(["url.query"]);
|
|
29
|
+
const BODY_KEYS = new Set([
|
|
30
|
+
"http.request.body.content",
|
|
31
|
+
"http.response.body.content",
|
|
32
|
+
]);
|
|
33
|
+
const MAX_DEPTH = 10;
|
|
34
|
+
const QUERY_PAIR_CAP = 256;
|
|
35
|
+
let activeConfig = EMPTY_CONFIG;
|
|
36
|
+
function setActiveRedactionConfig(config) {
|
|
37
|
+
activeConfig = config;
|
|
38
|
+
}
|
|
39
|
+
function getActiveRedactionConfig() {
|
|
40
|
+
return activeConfig;
|
|
41
|
+
}
|
|
42
|
+
function normalizeKey(key) {
|
|
43
|
+
return key
|
|
44
|
+
.trim()
|
|
45
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
|
|
46
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
47
|
+
.replace(/-/g, "_")
|
|
48
|
+
.toLowerCase();
|
|
49
|
+
}
|
|
50
|
+
function isFloorKey(key) {
|
|
51
|
+
return FLOOR_KEYS.has(normalizeKey(key));
|
|
52
|
+
}
|
|
53
|
+
function isSensitiveHeader(key) {
|
|
54
|
+
return HTTP_HEADERS.has(normalizeKey(key));
|
|
55
|
+
}
|
|
56
|
+
function isSensitiveQueryKey(key) {
|
|
57
|
+
const normalized = normalizeKey(key);
|
|
58
|
+
return FLOOR_KEYS.has(normalized) || QUERY_KEYS.has(normalized);
|
|
59
|
+
}
|
|
60
|
+
function keyMatch(key, config = activeConfig) {
|
|
61
|
+
const normalized = normalizeKey(key);
|
|
62
|
+
if (FLOOR_KEYS.has(normalized))
|
|
63
|
+
return "floor";
|
|
64
|
+
if (config.piiKeys.has(normalized))
|
|
65
|
+
return "pii";
|
|
66
|
+
if (config.secretKeys.has(normalized))
|
|
67
|
+
return "secret";
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
function tailMask(value) {
|
|
71
|
+
if (value === constants_js_1.REDACTED_VALUE)
|
|
72
|
+
return constants_js_1.REDACTED_VALUE;
|
|
73
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
74
|
+
const text = String(value);
|
|
75
|
+
if (text.length >= constants_js_1.TAIL_MASK_THRESHOLD)
|
|
76
|
+
return constants_js_1.FULL_MASK + text.slice(-4);
|
|
77
|
+
}
|
|
78
|
+
return constants_js_1.FULL_MASK;
|
|
79
|
+
}
|
|
80
|
+
function maskForMatch(kind, value) {
|
|
81
|
+
return kind === "secret" ? tailMask(value) : constants_js_1.REDACTED_VALUE;
|
|
82
|
+
}
|
|
83
|
+
function decodeKey(value) {
|
|
84
|
+
try {
|
|
85
|
+
return decodeURIComponent(value.replace(/\+/g, " "));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function redactPair(pair, config) {
|
|
92
|
+
const separator = pair.indexOf("=");
|
|
93
|
+
if (separator < 0)
|
|
94
|
+
return pair;
|
|
95
|
+
const key = decodeKey(pair.slice(0, separator));
|
|
96
|
+
if (keyMatch(key, config) || isSensitiveQueryKey(key)) {
|
|
97
|
+
return `${pair.slice(0, separator)}=${constants_js_1.REDACTED_VALUE}`;
|
|
98
|
+
}
|
|
99
|
+
return pair;
|
|
100
|
+
}
|
|
101
|
+
function redactQueryPairs(query, config = activeConfig) {
|
|
102
|
+
const result = [];
|
|
103
|
+
let start = 0;
|
|
104
|
+
let count = 0;
|
|
105
|
+
while (start <= query.length) {
|
|
106
|
+
if (count >= QUERY_PAIR_CAP) {
|
|
107
|
+
result.push(constants_js_1.REDACTED_VALUE);
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
const next = query.indexOf("&", start);
|
|
111
|
+
result.push(redactPair(next < 0 ? query.slice(start) : query.slice(start, next), config));
|
|
112
|
+
count += 1;
|
|
113
|
+
if (next < 0)
|
|
114
|
+
break;
|
|
115
|
+
start = next + 1;
|
|
116
|
+
}
|
|
117
|
+
return result.join("&");
|
|
118
|
+
}
|
|
119
|
+
function redactSegment(segment, config) {
|
|
120
|
+
return segment.includes("=") ? redactQueryPairs(segment, config) : segment;
|
|
121
|
+
}
|
|
122
|
+
function redactUrlLike(text, config = activeConfig) {
|
|
123
|
+
const hash = text.indexOf("#");
|
|
124
|
+
const head = hash < 0 ? text : text.slice(0, hash);
|
|
125
|
+
const fragment = hash < 0 ? undefined : text.slice(hash + 1);
|
|
126
|
+
const query = head.indexOf("?");
|
|
127
|
+
const redactedHead = query < 0
|
|
128
|
+
? redactSegment(head, config)
|
|
129
|
+
: `${head.slice(0, query + 1)}${redactQueryPairs(head.slice(query + 1), config)}`;
|
|
130
|
+
if (fragment === undefined)
|
|
131
|
+
return redactedHead;
|
|
132
|
+
const fragmentQuery = fragment.indexOf("?");
|
|
133
|
+
const redactedFragment = fragmentQuery < 0
|
|
134
|
+
? redactSegment(fragment, config)
|
|
135
|
+
: `${redactSegment(fragment.slice(0, fragmentQuery), config)}?${redactQueryPairs(fragment.slice(fragmentQuery + 1), config)}`;
|
|
136
|
+
return `${redactedHead}#${redactedFragment}`;
|
|
137
|
+
}
|
|
138
|
+
function redactString(text, config = activeConfig) {
|
|
139
|
+
return text.includes("=") ? redactUrlLike(text, config) : text;
|
|
140
|
+
}
|
|
141
|
+
function redactNode(value, config, depth, ancestors) {
|
|
142
|
+
if (depth > MAX_DEPTH)
|
|
143
|
+
return constants_js_1.FULL_MASK;
|
|
144
|
+
if (typeof value === "string")
|
|
145
|
+
return redactString(value, config);
|
|
146
|
+
if (value === null ||
|
|
147
|
+
value === undefined ||
|
|
148
|
+
typeof value === "number" ||
|
|
149
|
+
typeof value === "boolean") {
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
if (typeof value !== "object")
|
|
153
|
+
return constants_js_1.FULL_MASK;
|
|
154
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer)
|
|
155
|
+
return value;
|
|
156
|
+
if (ancestors.has(value))
|
|
157
|
+
return constants_js_1.FULL_MASK;
|
|
158
|
+
ancestors.add(value);
|
|
159
|
+
try {
|
|
160
|
+
if (Array.isArray(value)) {
|
|
161
|
+
return value.map((item) => redactNode(item, config, depth + 1, ancestors));
|
|
162
|
+
}
|
|
163
|
+
const result = {};
|
|
164
|
+
for (const [key, item] of Object.entries(value)) {
|
|
165
|
+
const match = keyMatch(key, config);
|
|
166
|
+
result[key] = match
|
|
167
|
+
? maskForMatch(match, item)
|
|
168
|
+
: redactNode(item, config, depth + 1, ancestors);
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
ancestors.delete(value);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function redactDeep(value, config = activeConfig) {
|
|
177
|
+
try {
|
|
178
|
+
return redactNode(value, config, 0, new Set());
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return constants_js_1.FULL_MASK;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function headerSuffix(key) {
|
|
185
|
+
const lower = key.toLowerCase();
|
|
186
|
+
for (const prefix of HEADER_PREFIXES) {
|
|
187
|
+
if (lower.startsWith(prefix) && key.length > prefix.length) {
|
|
188
|
+
return key.slice(prefix.length);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
function maskElements(kind, value) {
|
|
194
|
+
return Array.isArray(value)
|
|
195
|
+
? value.map((item) => maskForMatch(kind, item))
|
|
196
|
+
: maskForMatch(kind, value);
|
|
197
|
+
}
|
|
198
|
+
function redactAttributeValue(key, value, config = activeConfig) {
|
|
199
|
+
try {
|
|
200
|
+
const match = keyMatch(key, config);
|
|
201
|
+
if (match)
|
|
202
|
+
return maskElements(match, value);
|
|
203
|
+
const header = headerSuffix(key);
|
|
204
|
+
if (header) {
|
|
205
|
+
const headerMatch = keyMatch(header, config);
|
|
206
|
+
if (headerMatch)
|
|
207
|
+
return maskElements(headerMatch, value);
|
|
208
|
+
if (isSensitiveHeader(header))
|
|
209
|
+
return maskElements("floor", value);
|
|
210
|
+
}
|
|
211
|
+
if (BARE_QUERY_KEYS.has(key.toLowerCase()) && typeof value === "string") {
|
|
212
|
+
return redactQueryPairs(value, config);
|
|
213
|
+
}
|
|
214
|
+
if (BODY_KEYS.has(key))
|
|
215
|
+
return value;
|
|
216
|
+
if (typeof value === "string")
|
|
217
|
+
return redactString(value, config);
|
|
218
|
+
if (Array.isArray(value) ||
|
|
219
|
+
(value !== null && typeof value === "object" && !ArrayBuffer.isView(value))) {
|
|
220
|
+
return redactDeep(value, config);
|
|
221
|
+
}
|
|
222
|
+
if (value === null ||
|
|
223
|
+
value === undefined ||
|
|
224
|
+
typeof value === "number" ||
|
|
225
|
+
typeof value === "boolean" ||
|
|
226
|
+
ArrayBuffer.isView(value)) {
|
|
227
|
+
return value;
|
|
228
|
+
}
|
|
229
|
+
return constants_js_1.FULL_MASK;
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return constants_js_1.FULL_MASK;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function redactAttributesCopy(attributes, config = activeConfig) {
|
|
236
|
+
const result = {};
|
|
237
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
238
|
+
result[key] = redactAttributeValue(key, value, config);
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
const REDACT_FIELDS = ["secrets", "pii"];
|
|
243
|
+
function keyList(field, value) {
|
|
244
|
+
if (value === undefined || value === null)
|
|
245
|
+
return [];
|
|
246
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
247
|
+
throw new TypeError(`[foam] redact.${field} must be an array of strings`);
|
|
248
|
+
}
|
|
249
|
+
return value;
|
|
250
|
+
}
|
|
251
|
+
function resolveRedactionConfig(redact) {
|
|
252
|
+
if (redact === undefined || redact === null)
|
|
253
|
+
return EMPTY_CONFIG;
|
|
254
|
+
if (typeof redact !== "object" || Array.isArray(redact)) {
|
|
255
|
+
throw new TypeError("[foam] redact must be an object");
|
|
256
|
+
}
|
|
257
|
+
const record = redact;
|
|
258
|
+
const unknown = Object.keys(record).filter((field) => !REDACT_FIELDS.includes(field));
|
|
259
|
+
if (unknown.length) {
|
|
260
|
+
throw new TypeError(`[foam] redact contains unknown fields: ${unknown.join(", ")}`);
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
secretKeys: new Set(keyList("secrets", record.secrets)
|
|
264
|
+
.map(normalizeKey)
|
|
265
|
+
.filter(Boolean)),
|
|
266
|
+
piiKeys: new Set(keyList("pii", record.pii)
|
|
267
|
+
.map(normalizeKey)
|
|
268
|
+
.filter(Boolean)),
|
|
269
|
+
};
|
|
270
|
+
}
|
package/dist/report.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SeverityNumber } from "@opentelemetry/api-logs";
|
|
2
|
+
export declare function report({ name, environment, token, severity, message, error, }: {
|
|
3
|
+
name?: string;
|
|
4
|
+
environment?: string;
|
|
5
|
+
token?: string;
|
|
6
|
+
severity: SeverityNumber;
|
|
7
|
+
message: string;
|
|
8
|
+
error?: string;
|
|
9
|
+
}): Promise<void>;
|
package/dist/report.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.report = report;
|
|
4
|
+
const api_1 = require("@opentelemetry/api");
|
|
5
|
+
const api_logs_1 = require("@opentelemetry/api-logs");
|
|
6
|
+
const core_1 = require("@opentelemetry/core");
|
|
7
|
+
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
|
|
8
|
+
const constants_js_1 = require("./constants.js");
|
|
9
|
+
const otlp_js_1 = require("./otlp.js");
|
|
10
|
+
const state_js_1 = require("./state.js");
|
|
11
|
+
const logger = api_1.diag.createComponentLogger({ namespace: constants_js_1.FOAM_IDENTIFIER_NAME });
|
|
12
|
+
// pcga11: Guarded so repeat report() calls don't trigger diag's "logger will be overwritten" warning.
|
|
13
|
+
let reportingConfigured = false;
|
|
14
|
+
function ensureReportingConfigured() {
|
|
15
|
+
if (reportingConfigured) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
reportingConfigured = true;
|
|
19
|
+
const fromEnv = (0, core_1.diagLogLevelFromString)((0, core_1.getStringFromEnv)(constants_js_1.DIAG_LOG_LEVEL));
|
|
20
|
+
api_1.diag.setLogger(new api_1.DiagConsoleLogger(), fromEnv ?? api_1.DiagLogLevel.INFO);
|
|
21
|
+
}
|
|
22
|
+
// pcga11: State reports go to Foam's OTLP endpoint directly instead of through the
|
|
23
|
+
// logger provider or exporter, since those may themselves be broken or unregistered.
|
|
24
|
+
async function report({ name, environment, token, severity, message, error, }) {
|
|
25
|
+
ensureReportingConfigured();
|
|
26
|
+
// pcga11: Local diag logging happens before the token check so misconfiguration
|
|
27
|
+
// is visible in the console even when nothing can be sent.
|
|
28
|
+
if (severity >= api_logs_1.SeverityNumber.ERROR) {
|
|
29
|
+
logger.error(message);
|
|
30
|
+
}
|
|
31
|
+
else if (severity >= api_logs_1.SeverityNumber.WARN) {
|
|
32
|
+
logger.warn(message);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
logger.info(message);
|
|
36
|
+
}
|
|
37
|
+
// pcga11: Token is the only hard requirement (nothing can be sent without auth).
|
|
38
|
+
// name/environment fall back to "unknown" so misconfiguration reports still arrive.
|
|
39
|
+
if (!token) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
await (0, otlp_js_1.sendOtlpLog)({
|
|
43
|
+
token: token,
|
|
44
|
+
resourceAttributes: {
|
|
45
|
+
[semantic_conventions_1.ATTR_SERVICE_NAME]: name?.trim() || "unknown",
|
|
46
|
+
[semantic_conventions_1.ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: environment?.trim() || "unknown",
|
|
47
|
+
},
|
|
48
|
+
scopeName: constants_js_1.FOAM_IDENTIFIER_NAME,
|
|
49
|
+
severityNumber: severity,
|
|
50
|
+
body: JSON.stringify({
|
|
51
|
+
state: { ...(0, state_js_1.getState)() },
|
|
52
|
+
message: `${constants_js_1.FOAM_IDENTIFIER_NAME} ${message}`,
|
|
53
|
+
error: error,
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
}
|
package/dist/resource.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createFoamResource = createFoamResource;
|
|
4
|
+
const resources_1 = require("@opentelemetry/resources");
|
|
5
|
+
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
|
|
6
|
+
const constants_js_1 = require("./constants.js");
|
|
7
|
+
function createFoamResource(name, environment, version, additionalResourceAttributes) {
|
|
8
|
+
return (0, resources_1.defaultResource)()
|
|
9
|
+
.merge((0, resources_1.detectResources)({
|
|
10
|
+
detectors: [resources_1.envDetector, resources_1.processDetector, resources_1.hostDetector, resources_1.osDetector],
|
|
11
|
+
}))
|
|
12
|
+
// pcga11: Later merges overwrite colliding keys, so Foam name/env/version will win over detectors.
|
|
13
|
+
// This is the desired behavior. Do not change.
|
|
14
|
+
.merge((0, resources_1.resourceFromAttributes)({
|
|
15
|
+
...additionalResourceAttributes,
|
|
16
|
+
[semantic_conventions_1.ATTR_SERVICE_NAME]: name,
|
|
17
|
+
[semantic_conventions_1.ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: environment,
|
|
18
|
+
...(version
|
|
19
|
+
? { [semantic_conventions_1.ATTR_SERVICE_VERSION]: version }
|
|
20
|
+
: {}),
|
|
21
|
+
[semantic_conventions_1.ATTR_TELEMETRY_DISTRO_NAME]: constants_js_1.FOAM_DISTRO_NAME,
|
|
22
|
+
[semantic_conventions_1.ATTR_TELEMETRY_DISTRO_VERSION]: constants_js_1.FOAM_DISTRO_VERSION,
|
|
23
|
+
}));
|
|
24
|
+
}
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare enum Signals {
|
|
2
|
+
traces = "traces",
|
|
3
|
+
metrics = "metrics",
|
|
4
|
+
logs = "logs",
|
|
5
|
+
baggage = "baggage",
|
|
6
|
+
profile = "profile"
|
|
7
|
+
}
|
|
8
|
+
export declare enum SignalSources {
|
|
9
|
+
none = "none",
|
|
10
|
+
global = "global",
|
|
11
|
+
ingest = "ingest",
|
|
12
|
+
local = "local"
|
|
13
|
+
}
|
|
14
|
+
export interface State {
|
|
15
|
+
readonly initialized: boolean;
|
|
16
|
+
readonly instrumentations: readonly string[];
|
|
17
|
+
readonly signals: Readonly<Record<Signals, SignalSources>>;
|
|
18
|
+
readonly params: Readonly<Record<string, unknown>>;
|
|
19
|
+
}
|
|
20
|
+
export declare const setInstrumentations: (names: readonly string[]) => void;
|
|
21
|
+
export declare const setSignal: (signal: Signals, source: SignalSources) => void;
|
|
22
|
+
export declare const getSignal: (signal: Signals) => SignalSources;
|
|
23
|
+
export declare const setInitialized: (initialized: boolean) => void;
|
|
24
|
+
export declare const getInitialized: () => boolean;
|
|
25
|
+
export declare const setParams: (params: Record<string, unknown>) => void;
|
|
26
|
+
export declare const getState: () => State;
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getState = exports.setParams = exports.getInitialized = exports.setInitialized = exports.getSignal = exports.setSignal = exports.setInstrumentations = exports.SignalSources = exports.Signals = void 0;
|
|
4
|
+
var Signals;
|
|
5
|
+
(function (Signals) {
|
|
6
|
+
Signals["traces"] = "traces";
|
|
7
|
+
Signals["metrics"] = "metrics";
|
|
8
|
+
Signals["logs"] = "logs";
|
|
9
|
+
Signals["baggage"] = "baggage";
|
|
10
|
+
Signals["profile"] = "profile";
|
|
11
|
+
})(Signals || (exports.Signals = Signals = {}));
|
|
12
|
+
var SignalSources;
|
|
13
|
+
(function (SignalSources) {
|
|
14
|
+
SignalSources["none"] = "none";
|
|
15
|
+
SignalSources["global"] = "global";
|
|
16
|
+
SignalSources["ingest"] = "ingest";
|
|
17
|
+
SignalSources["local"] = "local";
|
|
18
|
+
})(SignalSources || (exports.SignalSources = SignalSources = {}));
|
|
19
|
+
const state = {
|
|
20
|
+
initialized: false,
|
|
21
|
+
instrumentations: [], // pcga11: The registered instrumentations that are enabled in the current application.
|
|
22
|
+
signals: {
|
|
23
|
+
traces: SignalSources.none,
|
|
24
|
+
metrics: SignalSources.none,
|
|
25
|
+
logs: SignalSources.none,
|
|
26
|
+
baggage: SignalSources.none,
|
|
27
|
+
profile: SignalSources.none,
|
|
28
|
+
},
|
|
29
|
+
params: {},
|
|
30
|
+
};
|
|
31
|
+
const setInstrumentations = (names) => {
|
|
32
|
+
state.instrumentations = [...names];
|
|
33
|
+
};
|
|
34
|
+
exports.setInstrumentations = setInstrumentations;
|
|
35
|
+
const setSignal = (signal, source) => {
|
|
36
|
+
state.signals[signal] = source;
|
|
37
|
+
};
|
|
38
|
+
exports.setSignal = setSignal;
|
|
39
|
+
const getSignal = (signal) => state.signals[signal];
|
|
40
|
+
exports.getSignal = getSignal;
|
|
41
|
+
const setInitialized = (initialized) => {
|
|
42
|
+
state.initialized = initialized;
|
|
43
|
+
};
|
|
44
|
+
exports.setInitialized = setInitialized;
|
|
45
|
+
const getInitialized = () => state.initialized;
|
|
46
|
+
exports.getInitialized = getInitialized;
|
|
47
|
+
const setParams = (params) => {
|
|
48
|
+
// pcga11: Safety precaution: never store tokens in the state.
|
|
49
|
+
if (params.token) {
|
|
50
|
+
delete params.token;
|
|
51
|
+
}
|
|
52
|
+
state.params = { ...params };
|
|
53
|
+
};
|
|
54
|
+
exports.setParams = setParams;
|
|
55
|
+
const getState = () => ({
|
|
56
|
+
initialized: state.initialized,
|
|
57
|
+
instrumentations: [...state.instrumentations],
|
|
58
|
+
signals: { ...state.signals },
|
|
59
|
+
params: { ...state.params },
|
|
60
|
+
});
|
|
61
|
+
exports.getState = getState;
|
package/dist/traces.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function recordException(error: unknown): void;
|
package/dist/traces.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.recordException = recordException;
|
|
4
|
+
const api_1 = require("@opentelemetry/api");
|
|
5
|
+
const utils_js_1 = require("./utils.js");
|
|
6
|
+
// pcga11: Not gated on the traces signal on purpose: the active span belongs to
|
|
7
|
+
// whichever SDK owns tracing, and the exception should land on that span either way.
|
|
8
|
+
function recordException(error) {
|
|
9
|
+
(0, utils_js_1.safely)(() => {
|
|
10
|
+
const span = api_1.trace.getActiveSpan();
|
|
11
|
+
if (!span?.isRecording()) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const exception = error instanceof Error ? error : String(error);
|
|
15
|
+
span.recordException(exception);
|
|
16
|
+
span.setStatus({
|
|
17
|
+
code: api_1.SpanStatusCode.ERROR,
|
|
18
|
+
message: error instanceof Error ? error.message : String(error),
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Signals } from "./state.js";
|
|
2
|
+
export declare function safely<T>(operation: () => T, fallback: T): T;
|
|
3
|
+
export declare function safely(operation: () => void): void;
|
|
4
|
+
export declare function whenInitialized<A extends unknown[]>(signal: Signals, operation: (...args: A) => void): (...args: A) => void;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.safely = safely;
|
|
4
|
+
exports.whenInitialized = whenInitialized;
|
|
5
|
+
const state_js_1 = require("./state.js");
|
|
6
|
+
function safely(operation, fallback) {
|
|
7
|
+
try {
|
|
8
|
+
return operation();
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return fallback;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function whenInitialized(signal, operation) {
|
|
15
|
+
return (...args) => {
|
|
16
|
+
if ((0, state_js_1.getSignal)(signal) === state_js_1.SignalSources.none)
|
|
17
|
+
return;
|
|
18
|
+
safely(() => operation(...args));
|
|
19
|
+
};
|
|
20
|
+
}
|
package/package.json
CHANGED
|
@@ -1,35 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foam-ai/node",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0-alpha.10",
|
|
4
|
+
"description": "Foam JavaScript Node.js SDK",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/foam-ai/node.git"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public",
|
|
12
|
+
"tag": "alpha"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": "^18.19.0 || >=20.6.0"
|
|
16
|
+
},
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"module": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
6
20
|
"exports": {
|
|
7
21
|
".": {
|
|
8
|
-
"types": "./dist/
|
|
9
|
-
"import": "./dist/
|
|
10
|
-
"require": "./dist/
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"require": "./dist/index.js",
|
|
25
|
+
"default": "./dist/index.js"
|
|
11
26
|
}
|
|
12
27
|
},
|
|
13
28
|
"files": [
|
|
14
29
|
"dist"
|
|
15
30
|
],
|
|
16
31
|
"scripts": {
|
|
17
|
-
"build": "tsc -p tsconfig.json"
|
|
32
|
+
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"prepublishOnly": "npm run build"
|
|
18
36
|
},
|
|
19
37
|
"dependencies": {
|
|
20
|
-
"@opentelemetry/api": "^1.9.
|
|
21
|
-
"@opentelemetry/api-logs": "^0.
|
|
22
|
-
"@opentelemetry/
|
|
23
|
-
"@opentelemetry/
|
|
24
|
-
"@opentelemetry/
|
|
25
|
-
"@opentelemetry/
|
|
26
|
-
"@opentelemetry/
|
|
27
|
-
"@opentelemetry/
|
|
28
|
-
"@opentelemetry/
|
|
29
|
-
"@opentelemetry/
|
|
30
|
-
"@opentelemetry/
|
|
38
|
+
"@opentelemetry/api": "^1.9.1",
|
|
39
|
+
"@opentelemetry/api-logs": "^0.221.0",
|
|
40
|
+
"@opentelemetry/auto-instrumentations-node": "^0.79.0",
|
|
41
|
+
"@opentelemetry/context-async-hooks": "^2.10.0",
|
|
42
|
+
"@opentelemetry/core": "^2.10.0",
|
|
43
|
+
"@opentelemetry/exporter-logs-otlp-http": "^0.221.0",
|
|
44
|
+
"@opentelemetry/exporter-metrics-otlp-http": "^0.221.0",
|
|
45
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.221.0",
|
|
46
|
+
"@opentelemetry/instrumentation": "^0.221.0",
|
|
47
|
+
"@opentelemetry/instrumentation-console": "^0.3.0",
|
|
48
|
+
"@opentelemetry/instrumentation-http": "^0.221.0",
|
|
49
|
+
"@opentelemetry/instrumentation-undici": "^0.31.0",
|
|
50
|
+
"@opentelemetry/otlp-transformer": "^0.221.0",
|
|
51
|
+
"@opentelemetry/resources": "^2.10.0",
|
|
52
|
+
"@opentelemetry/sdk-logs": "^0.221.0",
|
|
53
|
+
"@opentelemetry/sdk-metrics": "^2.10.0",
|
|
54
|
+
"@opentelemetry/sdk-trace-base": "^2.10.0",
|
|
55
|
+
"@opentelemetry/semantic-conventions": "^1.43.0",
|
|
56
|
+
"@opentelemetry/winston-transport": "^0.31.0"
|
|
31
57
|
},
|
|
32
58
|
"devDependencies": {
|
|
33
|
-
"
|
|
59
|
+
"@types/node": "^26.2.0",
|
|
60
|
+
"typescript": "^7.0.2",
|
|
61
|
+
"vitest": "^4.1.11"
|
|
34
62
|
}
|
|
35
63
|
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Captures an exception and sends it to Foam.
|
|
3
|
-
*
|
|
4
|
-
* - Records on the active span if one exists (for trace-level visibility)
|
|
5
|
-
* - Always emits an OTEL log record so the error reaches Foam even when
|
|
6
|
-
* there is no active span or auto-instrumentation
|
|
7
|
-
* - Safe to call at any time — silently no-ops if the SDK isn't initialized
|
|
8
|
-
*/
|
|
9
|
-
export declare const captureException: (error: unknown) => void;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.captureException = void 0;
|
|
4
|
-
const api_1 = require("@opentelemetry/api");
|
|
5
|
-
const api_logs_1 = require("@opentelemetry/api-logs");
|
|
6
|
-
const util_1 = require("../../shared/util");
|
|
7
|
-
/**
|
|
8
|
-
* Captures an exception and sends it to Foam.
|
|
9
|
-
*
|
|
10
|
-
* - Records on the active span if one exists (for trace-level visibility)
|
|
11
|
-
* - Always emits an OTEL log record so the error reaches Foam even when
|
|
12
|
-
* there is no active span or auto-instrumentation
|
|
13
|
-
* - Safe to call at any time — silently no-ops if the SDK isn't initialized
|
|
14
|
-
*/
|
|
15
|
-
exports.captureException = (0, util_1.safe)((error) => {
|
|
16
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
17
|
-
const span = api_1.trace.getActiveSpan();
|
|
18
|
-
if (span) {
|
|
19
|
-
span.recordException(err);
|
|
20
|
-
span.setStatus({ code: api_1.SpanStatusCode.ERROR });
|
|
21
|
-
}
|
|
22
|
-
api_logs_1.logs.getLogger('foam').emit({
|
|
23
|
-
severityNumber: api_logs_1.SeverityNumber.ERROR,
|
|
24
|
-
severityText: 'ERROR',
|
|
25
|
-
body: err.message,
|
|
26
|
-
attributes: {
|
|
27
|
-
'exception.type': err.name,
|
|
28
|
-
'exception.stacktrace': err.stack ?? '',
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
});
|
package/dist/node/src/index.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { captureException } from './capture-exception';
|
|
2
|
-
import { init } from './init';
|
|
3
|
-
export { captureException, init };
|
|
4
|
-
export type { FoamNodeInitOptions } from './init';
|
|
5
|
-
declare const foam: {
|
|
6
|
-
captureException: (error: unknown) => void;
|
|
7
|
-
init: (options: import("./init").FoamNodeInitOptions) => void;
|
|
8
|
-
};
|
|
9
|
-
export default foam;
|
package/dist/node/src/index.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.init = exports.captureException = void 0;
|
|
4
|
-
const capture_exception_1 = require("./capture-exception");
|
|
5
|
-
Object.defineProperty(exports, "captureException", { enumerable: true, get: function () { return capture_exception_1.captureException; } });
|
|
6
|
-
const init_1 = require("./init");
|
|
7
|
-
Object.defineProperty(exports, "init", { enumerable: true, get: function () { return init_1.init; } });
|
|
8
|
-
const foam = {
|
|
9
|
-
captureException: capture_exception_1.captureException,
|
|
10
|
-
init: init_1.init,
|
|
11
|
-
};
|
|
12
|
-
exports.default = foam;
|