@foam-ai/node 0.1.0-alpha.4 → 0.1.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +393 -214
- package/dist/before-send.d.ts +16 -0
- package/dist/before-send.js +41 -0
- package/dist/constants.d.ts +10 -8
- package/dist/constants.js +19 -6
- package/dist/endpoint.d.ts +2 -0
- package/dist/endpoint.js +11 -0
- package/dist/exporters.d.ts +11 -3
- package/dist/exporters.js +127 -53
- package/dist/index.d.ts +7 -2
- package/dist/index.js +8 -1
- package/dist/ingest.d.ts +8 -2
- package/dist/ingest.js +28 -16
- package/dist/init.d.ts +7 -3
- package/dist/init.js +85 -37
- package/dist/instrumentations.js +25 -3
- package/dist/logs.d.ts +10 -0
- package/dist/logs.js +61 -0
- package/dist/network-capture/collector.js +168 -69
- package/dist/network-capture/http.d.ts +0 -1
- package/dist/network-capture/http.js +23 -25
- package/dist/network-capture/index.js +10 -0
- package/dist/network-capture/redact.d.ts +9 -0
- package/dist/network-capture/redact.js +401 -0
- package/dist/network-capture/undici.d.ts +0 -4
- package/dist/network-capture/undici.js +55 -22
- package/dist/otlp.d.ts +8 -0
- package/dist/otlp.js +46 -0
- package/dist/propagation.d.ts +1 -1
- package/dist/propagation.js +6 -4
- 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/state.d.ts +11 -4
- package/dist/state.js +26 -9
- package/dist/traces.d.ts +1 -0
- package/dist/traces.js +21 -0
- package/dist/utils.js +1 -1
- package/package.json +1 -1
- package/dist/diagnostics.d.ts +0 -13
- package/dist/diagnostics.js +0 -83
|
@@ -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/state.d.ts
CHANGED
|
@@ -5,15 +5,22 @@ export declare enum Signals {
|
|
|
5
5
|
baggage = "baggage",
|
|
6
6
|
profile = "profile"
|
|
7
7
|
}
|
|
8
|
-
|
|
8
|
+
export declare enum SignalSources {
|
|
9
|
+
none = "none",
|
|
10
|
+
global = "global",
|
|
11
|
+
ingest = "ingest",
|
|
12
|
+
local = "local"
|
|
13
|
+
}
|
|
9
14
|
export interface State {
|
|
10
15
|
readonly initialized: boolean;
|
|
11
16
|
readonly instrumentations: readonly string[];
|
|
12
|
-
readonly signals: Readonly<Record<Signals,
|
|
17
|
+
readonly signals: Readonly<Record<Signals, SignalSources>>;
|
|
18
|
+
readonly params: Readonly<Record<string, unknown>>;
|
|
13
19
|
}
|
|
14
20
|
export declare const setInstrumentations: (names: readonly string[]) => void;
|
|
15
|
-
export declare const setSignal: (signal: Signals,
|
|
16
|
-
export declare const getSignal: (signal: Signals) =>
|
|
21
|
+
export declare const setSignal: (signal: Signals, source: SignalSources) => void;
|
|
22
|
+
export declare const getSignal: (signal: Signals) => SignalSources;
|
|
17
23
|
export declare const setInitialized: (initialized: boolean) => void;
|
|
18
24
|
export declare const getInitialized: () => boolean;
|
|
25
|
+
export declare const setParams: (params: Record<string, unknown>) => void;
|
|
19
26
|
export declare const getState: () => State;
|
package/dist/state.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getState = exports.getInitialized = exports.setInitialized = exports.getSignal = exports.setSignal = exports.setInstrumentations = exports.Signals = void 0;
|
|
3
|
+
exports.getState = exports.setParams = exports.getInitialized = exports.setInitialized = exports.getSignal = exports.setSignal = exports.setInstrumentations = exports.SignalSources = exports.Signals = void 0;
|
|
4
4
|
var Signals;
|
|
5
5
|
(function (Signals) {
|
|
6
6
|
Signals["traces"] = "traces";
|
|
@@ -9,23 +9,31 @@ var Signals;
|
|
|
9
9
|
Signals["baggage"] = "baggage";
|
|
10
10
|
Signals["profile"] = "profile";
|
|
11
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 = {}));
|
|
12
19
|
const state = {
|
|
13
20
|
initialized: false,
|
|
14
|
-
instrumentations: [],
|
|
21
|
+
instrumentations: [], // pcga11: The registered instrumentations that are enabled in the current application.
|
|
15
22
|
signals: {
|
|
16
|
-
traces:
|
|
17
|
-
metrics:
|
|
18
|
-
logs:
|
|
19
|
-
baggage:
|
|
20
|
-
profile:
|
|
23
|
+
traces: SignalSources.none,
|
|
24
|
+
metrics: SignalSources.none,
|
|
25
|
+
logs: SignalSources.none,
|
|
26
|
+
baggage: SignalSources.none,
|
|
27
|
+
profile: SignalSources.none,
|
|
21
28
|
},
|
|
29
|
+
params: {},
|
|
22
30
|
};
|
|
23
31
|
const setInstrumentations = (names) => {
|
|
24
32
|
state.instrumentations = [...names];
|
|
25
33
|
};
|
|
26
34
|
exports.setInstrumentations = setInstrumentations;
|
|
27
|
-
const setSignal = (signal,
|
|
28
|
-
state.signals[signal] =
|
|
35
|
+
const setSignal = (signal, source) => {
|
|
36
|
+
state.signals[signal] = source;
|
|
29
37
|
};
|
|
30
38
|
exports.setSignal = setSignal;
|
|
31
39
|
const getSignal = (signal) => state.signals[signal];
|
|
@@ -36,9 +44,18 @@ const setInitialized = (initialized) => {
|
|
|
36
44
|
exports.setInitialized = setInitialized;
|
|
37
45
|
const getInitialized = () => state.initialized;
|
|
38
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;
|
|
39
55
|
const getState = () => ({
|
|
40
56
|
initialized: state.initialized,
|
|
41
57
|
instrumentations: [...state.instrumentations],
|
|
42
58
|
signals: { ...state.signals },
|
|
59
|
+
params: { ...state.params },
|
|
43
60
|
});
|
|
44
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.js
CHANGED
|
@@ -13,7 +13,7 @@ function safely(operation, fallback) {
|
|
|
13
13
|
}
|
|
14
14
|
function whenInitialized(signal, operation) {
|
|
15
15
|
return (...args) => {
|
|
16
|
-
if (
|
|
16
|
+
if ((0, state_js_1.getSignal)(signal) === state_js_1.SignalSources.none)
|
|
17
17
|
return;
|
|
18
18
|
safely(() => operation(...args));
|
|
19
19
|
};
|
package/package.json
CHANGED
package/dist/diagnostics.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { SeverityNumber } from "@opentelemetry/api-logs";
|
|
2
|
-
export declare function configureDiagnostics(): void;
|
|
3
|
-
export declare const info: (message: string) => void;
|
|
4
|
-
export declare const warn: (message: string) => void;
|
|
5
|
-
export declare const error: (message: string) => void;
|
|
6
|
-
export declare function sendStateDiagnosticLog({ name, environment, token, tier, severity, error, }: {
|
|
7
|
-
name: string;
|
|
8
|
-
environment: string;
|
|
9
|
-
token: string;
|
|
10
|
-
tier: "internal" | "external";
|
|
11
|
-
severity: SeverityNumber;
|
|
12
|
-
error?: string;
|
|
13
|
-
}): Promise<void>;
|
package/dist/diagnostics.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.error = exports.warn = exports.info = void 0;
|
|
4
|
-
exports.configureDiagnostics = configureDiagnostics;
|
|
5
|
-
exports.sendStateDiagnosticLog = sendStateDiagnosticLog;
|
|
6
|
-
const api_1 = require("@opentelemetry/api");
|
|
7
|
-
const core_1 = require("@opentelemetry/core");
|
|
8
|
-
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
|
|
9
|
-
const constants_js_1 = require("./constants.js");
|
|
10
|
-
const state_js_1 = require("./state.js");
|
|
11
|
-
const logger = api_1.diag.createComponentLogger({ namespace: constants_js_1.FOAM_IDENTIFIER_NAME });
|
|
12
|
-
function configureDiagnostics() {
|
|
13
|
-
const fromEnv = (0, core_1.diagLogLevelFromString)((0, core_1.getStringFromEnv)(constants_js_1.DIAG_LOG_LEVEL));
|
|
14
|
-
api_1.diag.setLogger(new api_1.DiagConsoleLogger(), fromEnv ?? api_1.DiagLogLevel.INFO);
|
|
15
|
-
}
|
|
16
|
-
const info = (message) => {
|
|
17
|
-
logger.info(message);
|
|
18
|
-
};
|
|
19
|
-
exports.info = info;
|
|
20
|
-
const warn = (message) => {
|
|
21
|
-
logger.warn(message);
|
|
22
|
-
};
|
|
23
|
-
exports.warn = warn;
|
|
24
|
-
const error = (message) => {
|
|
25
|
-
logger.error(message);
|
|
26
|
-
};
|
|
27
|
-
exports.error = error;
|
|
28
|
-
// pcga11: We send the diagnostic log to Foam's OTLP endpoint directly instead of using the logger provider or exporter to deliver diagnostics.
|
|
29
|
-
// We do not want to rely on the logger provider or exporter to deliver diagnostics. As they themselves may not be instrumented.
|
|
30
|
-
async function sendStateDiagnosticLog({ name, environment, token, tier, severity, error, }) {
|
|
31
|
-
if (!name?.trim() || !environment?.trim() || !token) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
await fetch(`${constants_js_1.FOAM_ENDPOINT}${constants_js_1.FOAM_OTLP_LOGS_PATH}`, {
|
|
36
|
-
method: "POST",
|
|
37
|
-
headers: {
|
|
38
|
-
Authorization: `Bearer ${token}`,
|
|
39
|
-
"Content-Type": "application/json",
|
|
40
|
-
},
|
|
41
|
-
body: JSON.stringify({
|
|
42
|
-
resourceLogs: [
|
|
43
|
-
{
|
|
44
|
-
resource: {
|
|
45
|
-
attributes: [
|
|
46
|
-
{ key: semantic_conventions_1.ATTR_SERVICE_NAME, value: { stringValue: name } },
|
|
47
|
-
{
|
|
48
|
-
key: semantic_conventions_1.ATTR_DEPLOYMENT_ENVIRONMENT_NAME,
|
|
49
|
-
value: { stringValue: environment },
|
|
50
|
-
},
|
|
51
|
-
{ key: constants_js_1.FOAM_INGEST_TIER, value: { stringValue: tier } },
|
|
52
|
-
],
|
|
53
|
-
},
|
|
54
|
-
scopeLogs: [
|
|
55
|
-
{
|
|
56
|
-
scope: { name: constants_js_1.FOAM_IDENTIFIER_NAME },
|
|
57
|
-
logRecords: [
|
|
58
|
-
{
|
|
59
|
-
timeUnixNano: String(BigInt(Date.now()) * 1000000n),
|
|
60
|
-
severityNumber: severity,
|
|
61
|
-
severityText: constants_js_1.SEVERITY_TEXT[severity],
|
|
62
|
-
body: {
|
|
63
|
-
stringValue: JSON.stringify({
|
|
64
|
-
...(0, state_js_1.getState)(),
|
|
65
|
-
[constants_js_1.FOAM_INGEST_TIER]: tier,
|
|
66
|
-
...(error === undefined
|
|
67
|
-
? {}
|
|
68
|
-
: { error }),
|
|
69
|
-
}),
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
}),
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
// pcga11: do not remove this catch block. Ingest must never throw into application code.
|
|
82
|
-
}
|
|
83
|
-
}
|