@foam-ai/node 0.1.0-alpha.3 → 0.1.0-alpha.5

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.
Files changed (56) hide show
  1. package/README.md +538 -75
  2. package/dist/constants.d.ts +43 -0
  3. package/dist/constants.js +97 -0
  4. package/dist/endpoint.d.ts +2 -0
  5. package/dist/endpoint.js +11 -0
  6. package/dist/exporters.d.ts +25 -0
  7. package/dist/exporters.js +103 -0
  8. package/dist/index.d.ts +7 -0
  9. package/dist/index.js +26 -0
  10. package/dist/ingest.d.ts +6 -0
  11. package/dist/ingest.js +61 -0
  12. package/dist/init.d.ts +22 -0
  13. package/dist/init.js +150 -0
  14. package/dist/instrumentations.d.ts +3 -0
  15. package/dist/instrumentations.js +101 -0
  16. package/dist/logs.d.ts +9 -0
  17. package/dist/logs.js +61 -0
  18. package/dist/metrics.d.ts +5 -0
  19. package/dist/metrics.js +29 -0
  20. package/dist/network-capture/collector.d.ts +27 -0
  21. package/dist/network-capture/collector.js +356 -0
  22. package/dist/network-capture/http.d.ts +5 -0
  23. package/dist/network-capture/http.js +221 -0
  24. package/dist/network-capture/index.d.ts +2 -0
  25. package/dist/network-capture/index.js +17 -0
  26. package/dist/network-capture/redact.d.ts +6 -0
  27. package/dist/network-capture/redact.js +87 -0
  28. package/dist/network-capture/undici.d.ts +5 -0
  29. package/dist/network-capture/undici.js +177 -0
  30. package/dist/otlp.d.ts +8 -0
  31. package/dist/otlp.js +46 -0
  32. package/dist/propagation.d.ts +5 -0
  33. package/dist/propagation.js +45 -0
  34. package/dist/report.d.ts +9 -0
  35. package/dist/report.js +56 -0
  36. package/dist/resource.d.ts +3 -0
  37. package/dist/resource.js +24 -0
  38. package/dist/state.d.ts +26 -0
  39. package/dist/state.js +61 -0
  40. package/dist/traces.d.ts +1 -0
  41. package/dist/traces.js +21 -0
  42. package/dist/utils.d.ts +4 -0
  43. package/dist/utils.js +20 -0
  44. package/package.json +47 -19
  45. package/dist/node/src/capture-exception.d.ts +0 -9
  46. package/dist/node/src/capture-exception.js +0 -31
  47. package/dist/node/src/index.d.ts +0 -13
  48. package/dist/node/src/index.js +0 -19
  49. package/dist/node/src/init.d.ts +0 -27
  50. package/dist/node/src/init.js +0 -218
  51. package/dist/node/src/metrics.d.ts +0 -28
  52. package/dist/node/src/metrics.js +0 -69
  53. package/dist/shared/constants.d.ts +0 -1
  54. package/dist/shared/constants.js +0 -4
  55. package/dist/shared/util.d.ts +0 -9
  56. package/dist/shared/util.js +0 -21
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createNetworkCaptureHooks = createNetworkCaptureHooks;
4
+ const node_events_1 = require("node:events");
5
+ const utils_js_1 = require("../utils.js");
6
+ const collector_js_1 = require("./collector.js");
7
+ function incomingHeaders(message) {
8
+ return message.headers;
9
+ }
10
+ function outgoingHeaders(message, direct) {
11
+ return {
12
+ ...Object.fromEntries(direct),
13
+ ...message.getHeaders(),
14
+ };
15
+ }
16
+ function observeDirectHeaders(args, headers) {
17
+ const append = (rawKey, value) => {
18
+ const key = String(rawKey).toLowerCase();
19
+ const existing = headers.get(key);
20
+ if (existing === undefined)
21
+ headers.set(key, value);
22
+ else if (Array.isArray(existing))
23
+ existing.push(value);
24
+ else
25
+ headers.set(key, [existing, value]);
26
+ };
27
+ const candidate = args.length >= 3 && typeof args[1] === "string" ? args[2] : args[1];
28
+ if (!candidate || typeof candidate !== "object")
29
+ return;
30
+ if (Array.isArray(candidate)) {
31
+ if (candidate.every(Array.isArray)) {
32
+ for (const entry of candidate) {
33
+ if (entry.length >= 2) {
34
+ append(entry[0], entry[1]);
35
+ }
36
+ }
37
+ }
38
+ else {
39
+ for (let index = 0; index + 1 < candidate.length; index += 2) {
40
+ append(candidate[index], candidate[index + 1]);
41
+ }
42
+ }
43
+ return;
44
+ }
45
+ for (const [rawKey, value] of Object.entries(candidate)) {
46
+ append(rawKey, value);
47
+ }
48
+ }
49
+ // pcga11:Replaces a method with a wrapped version; returns an undo that restores it unless someone else re-patched it.
50
+ function swapMethod(target, key, wrap) {
51
+ const original = target[key];
52
+ const wrapped = wrap(original);
53
+ target[key] = wrapped;
54
+ return () => {
55
+ if (target[key] === wrapped)
56
+ target[key] = original;
57
+ };
58
+ }
59
+ // Wraps writeHead (when present) to record inline headers; returns an undo.
60
+ function swapWriteHead(target, directHeaders, afterWriteHead) {
61
+ const original = target.writeHead;
62
+ if (!original)
63
+ return () => { };
64
+ const wrapped = function (...args) {
65
+ const result = Reflect.apply(original, this, args);
66
+ (0, utils_js_1.safely)(() => {
67
+ observeDirectHeaders(args, directHeaders);
68
+ afterWriteHead?.();
69
+ });
70
+ return result;
71
+ };
72
+ target.writeHead = wrapped;
73
+ return () => {
74
+ if (target.writeHead === wrapped)
75
+ target.writeHead = original;
76
+ };
77
+ }
78
+ // Attaches one-shot listeners; returns an undo that removes all of them.
79
+ function listenOnce(target, listeners) {
80
+ for (const [event, listener] of listeners)
81
+ target.once(event, listener);
82
+ return () => {
83
+ for (const [event, listener] of listeners) {
84
+ target.removeListener(event, listener);
85
+ }
86
+ };
87
+ }
88
+ function observeWritableHeaders(message, span, direction, directHeaders) {
89
+ const target = message;
90
+ const applyHeaders = () => {
91
+ (0, collector_js_1.setHeaderAttributes)(span, direction, outgoingHeaders(message, directHeaders));
92
+ };
93
+ const restoreEnd = swapMethod(target, "end", (originalEnd) => function (...args) {
94
+ const result = Reflect.apply(originalEnd, this, args);
95
+ (0, utils_js_1.safely)(applyHeaders);
96
+ return result;
97
+ });
98
+ const restoreWriteHead = swapWriteHead(target, directHeaders, applyHeaders);
99
+ let removeListeners = () => { };
100
+ // Runs inside the customer's EventEmitter emit; a throw here would surface
101
+ // as an uncaughtException in their app.
102
+ const cleanup = () => (0, utils_js_1.safely)(() => {
103
+ restoreEnd();
104
+ restoreWriteHead();
105
+ removeListeners();
106
+ });
107
+ removeListeners = listenOnce(target, [
108
+ ["finish", cleanup],
109
+ ["close", cleanup],
110
+ ]);
111
+ }
112
+ function observeReadable(message, collector) {
113
+ const target = message;
114
+ const restorePush = swapMethod(target, "push", (originalPush) => function (...args) {
115
+ // The parser's delivery path runs first and its result is returned
116
+ // unconditionally; observation failures must never disturb it.
117
+ const result = Reflect.apply(originalPush, this, args);
118
+ (0, utils_js_1.safely)(() => {
119
+ if (args[0] !== null)
120
+ collector.observe(args[0], args[1]);
121
+ else
122
+ collector.finalize(true);
123
+ });
124
+ return result;
125
+ });
126
+ const onComplete = () => (0, utils_js_1.safely)(() => collector.finalize(true));
127
+ const onIncomplete = () => (0, utils_js_1.safely)(() => collector.finalize(false));
128
+ const removeListeners = listenOnce(target, [
129
+ ["end", onComplete],
130
+ ["aborted", onIncomplete],
131
+ ["close", onIncomplete],
132
+ [node_events_1.errorMonitor, onIncomplete],
133
+ ]);
134
+ collector.addCleanup(() => {
135
+ restorePush();
136
+ removeListeners();
137
+ });
138
+ }
139
+ function observeWritable(message, collector, directHeaders) {
140
+ const target = message;
141
+ // Prevents double-observing a final chunk that end() forwards to write() internally.
142
+ let ending = false;
143
+ const restoreWrite = swapMethod(target, "write", (originalWrite) => function (...args) {
144
+ const result = Reflect.apply(originalWrite, this, args);
145
+ if (!ending)
146
+ (0, utils_js_1.safely)(() => collector.observe(args[0], args[1]));
147
+ return result;
148
+ });
149
+ const restoreEnd = swapMethod(target, "end", (originalEnd) => function (...args) {
150
+ ending = true;
151
+ try {
152
+ const result = Reflect.apply(originalEnd, this, args);
153
+ (0, utils_js_1.safely)(() => collector.observe(args[0], args[1]));
154
+ return result;
155
+ }
156
+ finally {
157
+ ending = false;
158
+ }
159
+ });
160
+ const restoreWriteHead = swapWriteHead(target, directHeaders);
161
+ const onComplete = () => (0, utils_js_1.safely)(() => collector.finalize(true));
162
+ const onIncomplete = () => (0, utils_js_1.safely)(() => collector.finalize(false));
163
+ const removeListeners = listenOnce(target, [
164
+ ["finish", onComplete],
165
+ ["close", onIncomplete],
166
+ [node_events_1.errorMonitor, onIncomplete],
167
+ ]);
168
+ collector.addCleanup(() => {
169
+ restoreWrite();
170
+ restoreEnd();
171
+ restoreWriteHead();
172
+ removeListeners();
173
+ });
174
+ }
175
+ // pcga11: Instruments one HTTP message on a span:
176
+ // - records headers as `http.<direction>.header.*` attributes
177
+ // - captures the body via a BodyCollector when enabled.
178
+ function hookMessage(span, message, direction, bodyCaptureEnabled) {
179
+ // pcga11: Duck-type: only outgoing messages (ClientRequest/ServerResponse) have write/end.
180
+ if ("write" in message && "end" in message) {
181
+ const side = direction === "request" ? "client" : "server";
182
+ const directHeaders = new Map();
183
+ (0, collector_js_1.setHeaderAttributes)(span, direction, outgoingHeaders(message, directHeaders));
184
+ const collector = bodyCaptureEnabled
185
+ ? // Headers are passed as a lazy callback so late-set headers are seen at finalize time.
186
+ collector_js_1.BodyCollector.create(span, side, direction, () => outgoingHeaders(message, directHeaders))
187
+ : undefined;
188
+ // With a collector, patch write/end/writeHead to copy body chunks as the app sends them.
189
+ if (collector)
190
+ observeWritable(message, collector, directHeaders);
191
+ else if (span.isRecording()) {
192
+ observeWritableHeaders(message, span, direction, directHeaders);
193
+ }
194
+ }
195
+ else {
196
+ const side = direction === "request" ? "server" : "client";
197
+ const headers = incomingHeaders(message);
198
+ (0, collector_js_1.setHeaderAttributes)(span, direction, headers);
199
+ const collector = bodyCaptureEnabled
200
+ ? collector_js_1.BodyCollector.create(span, side, direction, () => headers)
201
+ : undefined;
202
+ if (collector)
203
+ observeReadable(message, collector);
204
+ }
205
+ }
206
+ // pcga11: Creates network capture hooks for node:http / node:https. Wraps instance
207
+ // methods: push(), write(), and end() on IncomingMessage / ClientRequest /
208
+ // ServerResponse. This works for all Node versions Foam supports.
209
+ function createNetworkCaptureHooks(bodyCaptureEnabled = true) {
210
+ // OTel guards hook errors today, but Foam's no-crash guarantee must not
211
+ // depend on that: any failure here is swallowed and the request proceeds
212
+ // as if Foam were not installed.
213
+ return {
214
+ requestHook(span, request) {
215
+ (0, utils_js_1.safely)(() => hookMessage(span, request, "request", bodyCaptureEnabled));
216
+ },
217
+ responseHook(span, response) {
218
+ (0, utils_js_1.safely)(() => hookMessage(span, response, "response", bodyCaptureEnabled));
219
+ },
220
+ };
221
+ }
@@ -0,0 +1,2 @@
1
+ export { createNetworkCaptureHooks } from "./http.js";
2
+ export { createUndiciNetworkCaptureHooks } from "./undici.js";
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createUndiciNetworkCaptureHooks = exports.createNetworkCaptureHooks = void 0;
4
+ //
5
+ // Advanced body capture uses three adapters across Foam's Node range:
6
+ // 1. node:http / node:https — instance push(), write(), and end() wrapping
7
+ // (every supported Node version).
8
+ // 2. Undici legacy — request:create plus instance onBodySent / onData
9
+ // (bundled fetch on Node 18.19, 20, 22, and 24.0–24.3, whose Undici is
10
+ // older than 7.11).
11
+ // 3. Undici modern — bodyChunkSent / bodyChunkReceived channels (Undici
12
+ // 7.11+, including Node 24.4+ and a separately installed undici). Future
13
+ // Node is chosen by capability: modern channels win when they fire.
14
+ var http_js_1 = require("./http.js");
15
+ Object.defineProperty(exports, "createNetworkCaptureHooks", { enumerable: true, get: function () { return http_js_1.createNetworkCaptureHooks; } });
16
+ var undici_js_1 = require("./undici.js");
17
+ Object.defineProperty(exports, "createUndiciNetworkCaptureHooks", { enumerable: true, get: function () { return undici_js_1.createUndiciNetworkCaptureHooks; } });
@@ -0,0 +1,6 @@
1
+ export interface RedactionResult {
2
+ readonly text: string;
3
+ readonly changed: boolean;
4
+ }
5
+ export declare function isSensitiveBodyKey(key: string): boolean;
6
+ export declare function redactBodyText(text: string, contentType: unknown): RedactionResult;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isSensitiveBodyKey = isSensitiveBodyKey;
4
+ exports.redactBodyText = redactBodyText;
5
+ const constants_js_1 = require("../constants.js");
6
+ const EXACT_KEYS = new Set(constants_js_1.SENSITIVE_BODY_KEYS_EXACT);
7
+ function normalizeKey(key) {
8
+ return key.toLowerCase().replace(/[-_.\s]/g, "");
9
+ }
10
+ function isSensitiveBodyKey(key) {
11
+ const normalized = normalizeKey(key);
12
+ return (EXACT_KEYS.has(normalized) ||
13
+ constants_js_1.SENSITIVE_BODY_KEY_PATTERNS.some((pattern) => normalized.includes(pattern)));
14
+ }
15
+ function mediaType(contentType) {
16
+ const raw = Array.isArray(contentType) ? contentType[0] : contentType;
17
+ if (typeof raw !== "string")
18
+ return "";
19
+ return (raw.split(";")[0] ?? "").trim().toLowerCase();
20
+ }
21
+ function redactNode(node, state) {
22
+ if (Array.isArray(node)) {
23
+ return node.map((item) => redactNode(item, state));
24
+ }
25
+ if (node !== null && typeof node === "object") {
26
+ const result = {};
27
+ for (const [key, value] of Object.entries(node)) {
28
+ if (isSensitiveBodyKey(key) && value !== null && value !== undefined) {
29
+ result[key] = constants_js_1.REDACTED_VALUE;
30
+ state.changed = true;
31
+ }
32
+ else {
33
+ result[key] = redactNode(value, state);
34
+ }
35
+ }
36
+ return result;
37
+ }
38
+ return node;
39
+ }
40
+ // pcga11: Captures a "key": value pair with a string (possibly unterminated
41
+ // at a truncation point), number, or literal value. Used only when the body
42
+ // does not parse as JSON, e.g. because the 1 MiB cap cut it mid-document.
43
+ const JSON_PAIR_PATTERN = /"((?:[^"\\]|\\.)*)"(\s*:\s*)("(?:[^"\\]|\\.)*"?|-?\d[\d.eE+-]*|true|false|null)/g;
44
+ function redactJsonFallback(text) {
45
+ let changed = false;
46
+ const redacted = text.replace(JSON_PAIR_PATTERN, (match, key, separator) => {
47
+ if (!isSensitiveBodyKey(key))
48
+ return match;
49
+ changed = true;
50
+ return `"${key}"${separator}"${constants_js_1.REDACTED_VALUE}"`;
51
+ });
52
+ return changed ? { text: redacted, changed } : { text, changed: false };
53
+ }
54
+ function redactJson(text) {
55
+ let parsed;
56
+ try {
57
+ parsed = JSON.parse(text);
58
+ }
59
+ catch {
60
+ return redactJsonFallback(text);
61
+ }
62
+ const state = { changed: false };
63
+ const redacted = redactNode(parsed, state);
64
+ if (!state.changed)
65
+ return { text, changed: false };
66
+ return { text: JSON.stringify(redacted), changed: true };
67
+ }
68
+ function redactForm(text) {
69
+ const params = new URLSearchParams(text);
70
+ let changed = false;
71
+ for (const key of new Set(params.keys())) {
72
+ if (!isSensitiveBodyKey(key))
73
+ continue;
74
+ params.set(key, constants_js_1.REDACTED_VALUE);
75
+ changed = true;
76
+ }
77
+ return changed ? { text: params.toString(), changed } : { text, changed: false };
78
+ }
79
+ function redactBodyText(text, contentType) {
80
+ const media = mediaType(contentType);
81
+ if (media === "application/x-www-form-urlencoded")
82
+ return redactForm(text);
83
+ if (media.endsWith("/json") || media.endsWith("+json")) {
84
+ return redactJson(text);
85
+ }
86
+ return { text, changed: false };
87
+ }
@@ -0,0 +1,5 @@
1
+ import type { RequestHookFunction, ResponseHookFunction } from "@opentelemetry/instrumentation-undici";
2
+ export declare function createUndiciNetworkCaptureHooks(bodyCaptureEnabled?: boolean): {
3
+ requestHook: RequestHookFunction;
4
+ responseHook: ResponseHookFunction;
5
+ };
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createUndiciNetworkCaptureHooks = createUndiciNetworkCaptureHooks;
4
+ const api_1 = require("@opentelemetry/api");
5
+ const node_diagnostics_channel_1 = require("node:diagnostics_channel");
6
+ const utils_js_1 = require("../utils.js");
7
+ const collector_js_1 = require("./collector.js");
8
+ const undiciSessions = new WeakMap();
9
+ const legacyWrappedRequests = new WeakSet();
10
+ let undiciBodyChannelsBound = false;
11
+ function appendHeader(headers, rawName, value) {
12
+ const name = String(rawName).toLowerCase();
13
+ const existing = headers[name];
14
+ if (existing === undefined)
15
+ headers[name] = value;
16
+ else if (Array.isArray(existing))
17
+ existing.push(value);
18
+ else
19
+ headers[name] = [existing, value];
20
+ }
21
+ function undiciRequestHeaders(value) {
22
+ const headers = {};
23
+ if (typeof value === "string") {
24
+ for (const line of value.split("\r\n")) {
25
+ const separator = line.indexOf(":");
26
+ if (separator <= 0)
27
+ continue;
28
+ appendHeader(headers, line.slice(0, separator).trim(), line.slice(separator + 1).trim());
29
+ }
30
+ }
31
+ else if (Array.isArray(value)) {
32
+ for (let index = 0; index + 1 < value.length; index += 2) {
33
+ appendHeader(headers, value[index], value[index + 1]);
34
+ }
35
+ }
36
+ return headers;
37
+ }
38
+ function undiciResponseHeaders(value) {
39
+ if (!Array.isArray(value))
40
+ return {};
41
+ const headers = {};
42
+ for (let index = 0; index + 1 < value.length; index += 2) {
43
+ const name = value[index];
44
+ const headerValue = value[index + 1];
45
+ appendHeader(headers, Buffer.isBuffer(name) ? name.toString("latin1") : name, Buffer.isBuffer(headerValue)
46
+ ? headerValue.toString("latin1")
47
+ : headerValue);
48
+ }
49
+ return headers;
50
+ }
51
+ function undiciRequest(message) {
52
+ if (!message || typeof message !== "object" || !("request" in message)) {
53
+ return undefined;
54
+ }
55
+ const request = message.request;
56
+ return request && typeof request === "object"
57
+ ? request
58
+ : undefined;
59
+ }
60
+ function undiciCollector(session, direction) {
61
+ const field = direction === "request" ? "requestBody" : "responseBody";
62
+ const existing = session[field];
63
+ if (existing)
64
+ return existing;
65
+ const headers = direction === "request" ? session.requestHeaders : session.responseHeaders;
66
+ if (!headers)
67
+ return undefined;
68
+ const created = collector_js_1.BodyCollector.create(session.span, "client", direction, () => headers);
69
+ if (created)
70
+ session[field] = created;
71
+ return created;
72
+ }
73
+ function finishUndici(request, complete) {
74
+ const session = undiciSessions.get(request);
75
+ if (!session)
76
+ return;
77
+ undiciSessions.delete(request);
78
+ session.requestBody?.finalize(complete);
79
+ session.responseBody?.finalize(complete);
80
+ }
81
+ function bindUndiciChannel(name, handle) {
82
+ (0, node_diagnostics_channel_1.subscribe)(name, (message) => {
83
+ (0, utils_js_1.safely)(() => {
84
+ const request = undiciRequest(message);
85
+ if (!request)
86
+ return;
87
+ const session = undiciSessions.get(request);
88
+ if (!session)
89
+ return;
90
+ handle(message, request, session);
91
+ });
92
+ });
93
+ }
94
+ function wrapLegacyBodyMethod(request, name, direction) {
95
+ const target = request;
96
+ const original = target[name];
97
+ if (typeof original !== "function")
98
+ return;
99
+ target[name] = function (chunk, ...rest) {
100
+ const result = original.call(this, chunk, ...rest);
101
+ (0, utils_js_1.safely)(() => {
102
+ const session = undiciSessions.get(request);
103
+ if (!session || session.modernBodyChannels)
104
+ return;
105
+ undiciCollector(session, direction)?.observe(chunk);
106
+ });
107
+ return result;
108
+ };
109
+ }
110
+ function bindLegacyUndiciBodyMethods(request) {
111
+ if (legacyWrappedRequests.has(request))
112
+ return;
113
+ legacyWrappedRequests.add(request);
114
+ wrapLegacyBodyMethod(request, "onBodySent", "request");
115
+ wrapLegacyBodyMethod(request, "onData", "response");
116
+ wrapLegacyBodyMethod(request, "onResponseData", "response");
117
+ }
118
+ function bindUndiciBodyChannels() {
119
+ if (undiciBodyChannelsBound)
120
+ return;
121
+ undiciBodyChannelsBound = true;
122
+ (0, node_diagnostics_channel_1.subscribe)("undici:request:create", (message) => {
123
+ (0, utils_js_1.safely)(() => {
124
+ const request = undiciRequest(message);
125
+ if (request)
126
+ bindLegacyUndiciBodyMethods(request);
127
+ });
128
+ });
129
+ bindUndiciChannel("undici:request:bodyChunkSent", (message, _request, session) => {
130
+ session.modernBodyChannels = true;
131
+ undiciCollector(session, "request")?.observe(message.chunk);
132
+ });
133
+ bindUndiciChannel("undici:request:bodySent", (_message, _request, session) => {
134
+ session.requestBody?.finalize(true);
135
+ });
136
+ bindUndiciChannel("undici:request:bodyChunkReceived", (message, _request, session) => {
137
+ session.modernBodyChannels = true;
138
+ undiciCollector(session, "response")?.observe(message.chunk);
139
+ });
140
+ bindUndiciChannel("undici:request:trailers", (_message, request) => {
141
+ finishUndici(request, true);
142
+ });
143
+ bindUndiciChannel("undici:request:error", (_message, request) => {
144
+ finishUndici(request, false);
145
+ });
146
+ }
147
+ function createUndiciNetworkCaptureHooks(bodyCaptureEnabled = true) {
148
+ if (bodyCaptureEnabled)
149
+ bindUndiciBodyChannels();
150
+ return {
151
+ requestHook(span, request) {
152
+ (0, utils_js_1.safely)(() => {
153
+ const headers = undiciRequestHeaders(request.headers);
154
+ (0, collector_js_1.setHeaderAttributes)(span, "request", headers);
155
+ if (!bodyCaptureEnabled ||
156
+ !span.isRecording() ||
157
+ (span.spanContext().traceFlags & api_1.TraceFlags.SAMPLED) !==
158
+ api_1.TraceFlags.SAMPLED) {
159
+ return;
160
+ }
161
+ undiciSessions.set(request, { span, requestHeaders: headers });
162
+ bindLegacyUndiciBodyMethods(request);
163
+ });
164
+ },
165
+ responseHook(span, info) {
166
+ (0, utils_js_1.safely)(() => {
167
+ const headers = undiciResponseHeaders(info.response.headers);
168
+ (0, collector_js_1.setHeaderAttributes)(span, "response", headers);
169
+ if (!bodyCaptureEnabled || info.request === undefined)
170
+ return;
171
+ const session = undiciSessions.get(info.request);
172
+ if (session)
173
+ session.responseHeaders = headers;
174
+ });
175
+ },
176
+ };
177
+ }
package/dist/otlp.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { SeverityNumber } from "@opentelemetry/api-logs";
2
+ export declare function sendOtlpLog({ token, resourceAttributes, scopeName, severityNumber, body, }: {
3
+ token: string;
4
+ resourceAttributes: Record<string, string>;
5
+ scopeName: string;
6
+ severityNumber: SeverityNumber;
7
+ body: string;
8
+ }): Promise<void>;
package/dist/otlp.js ADDED
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendOtlpLog = sendOtlpLog;
4
+ const constants_js_1 = require("./constants.js");
5
+ const endpoint_js_1 = require("./endpoint.js");
6
+ // pcga11: Pipeline-independent OTLP log delivery.
7
+ // Use when the SDK's LoggerProvider/exporter never got set up or is
8
+ // broken (diagnostics). Not a client surface: no batching, no resource
9
+ // detectors, no trace correlation, one fetch per record. Client logs go
10
+ // through the Foam LoggerProvider in logs.ts.
11
+ async function sendOtlpLog({ token, resourceAttributes, scopeName, severityNumber, body, }) {
12
+ try {
13
+ await fetch(`${endpoint_js_1.endpoint}${constants_js_1.FOAM_OTLP_LOGS_PATH}`, {
14
+ method: "POST",
15
+ headers: {
16
+ Authorization: `Bearer ${token}`,
17
+ "Content-Type": "application/json",
18
+ },
19
+ body: JSON.stringify({
20
+ resourceLogs: [
21
+ {
22
+ resource: {
23
+ attributes: Object.entries(resourceAttributes).map(([key, value]) => ({ key, value: { stringValue: value } })),
24
+ },
25
+ scopeLogs: [
26
+ {
27
+ scope: { name: scopeName },
28
+ logRecords: [
29
+ {
30
+ timeUnixNano: String(BigInt(Date.now()) * 1000000n),
31
+ severityNumber,
32
+ severityText: constants_js_1.SEVERITY_TEXT[severityNumber],
33
+ body: { stringValue: body },
34
+ },
35
+ ],
36
+ },
37
+ ],
38
+ },
39
+ ],
40
+ }),
41
+ });
42
+ }
43
+ catch {
44
+ // pcga11: do not remove this catch block. Ingest must never throw into application code.
45
+ }
46
+ }
@@ -0,0 +1,5 @@
1
+ import { type Context } from "@opentelemetry/api";
2
+ export declare function injectTraceContext(carrier: Record<string, string>): void;
3
+ export declare function extractTraceContext(carrier: Record<string, string>): Context;
4
+ export declare const setBaggage: (key: string, value: string) => void;
5
+ export declare function getBaggage(key: string): string | undefined;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setBaggage = void 0;
4
+ exports.injectTraceContext = injectTraceContext;
5
+ exports.extractTraceContext = extractTraceContext;
6
+ exports.getBaggage = getBaggage;
7
+ const node_async_hooks_1 = require("node:async_hooks");
8
+ const api_1 = require("@opentelemetry/api");
9
+ const core_1 = require("@opentelemetry/core");
10
+ const utils_js_1 = require("./utils.js");
11
+ const state_js_1 = require("./state.js");
12
+ // pcga11: Below are some definitios you may find helpful to understand this file.
13
+ // CompositePropagator is a local propagator used on custom transports (Kafka, SQS, Redis, WebSocket)
14
+ // An "injector/extractor" turns the context into headers and vice versa for distributed tracing
15
+ // Carrier is the headers object can be kafka message, queue payload or any other transport headers
16
+ // Context.active() returns the process' context. Think of it like a local RAM for this request in this Node process. Another service, process or even another Kafka in the same service will have a different context.active().
17
+ const propagator = new core_1.CompositePropagator({
18
+ propagators: [new core_1.W3CTraceContextPropagator(), new core_1.W3CBaggagePropagator()],
19
+ });
20
+ const baggageStorage = new node_async_hooks_1.AsyncLocalStorage();
21
+ function contextWithBaggage() {
22
+ const entries = Object.fromEntries(api_1.propagation.getBaggage(api_1.context.active())?.getAllEntries() ?? []);
23
+ for (const [key, value] of baggageStorage.getStore() ?? []) {
24
+ entries[key] = { value };
25
+ }
26
+ const baggage = api_1.propagation.createBaggage(entries);
27
+ return api_1.propagation.setBaggage(api_1.context.active(), baggage);
28
+ }
29
+ function injectTraceContext(carrier) {
30
+ (0, utils_js_1.safely)(() => {
31
+ propagator.inject(contextWithBaggage(), carrier, api_1.defaultTextMapSetter);
32
+ });
33
+ }
34
+ function extractTraceContext(carrier) {
35
+ return (0, utils_js_1.safely)(() => propagator.extract(api_1.context.active(), carrier, api_1.defaultTextMapGetter), api_1.context.active());
36
+ }
37
+ exports.setBaggage = (0, utils_js_1.whenInitialized)(state_js_1.Signals.baggage, (key, value) => {
38
+ const next = new Map(baggageStorage.getStore() ?? []);
39
+ next.set(key, value);
40
+ baggageStorage.enterWith(next);
41
+ });
42
+ function getBaggage(key) {
43
+ return (0, utils_js_1.safely)(() => baggageStorage.getStore()?.get(key) ??
44
+ api_1.propagation.getBaggage(api_1.context.active())?.getEntry(key)?.value, undefined);
45
+ }
@@ -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>;