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

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 (46) hide show
  1. package/README.md +424 -77
  2. package/dist/constants.d.ts +42 -0
  3. package/dist/constants.js +54 -0
  4. package/dist/diagnostics.d.ts +13 -0
  5. package/dist/diagnostics.js +83 -0
  6. package/dist/exporters.d.ts +25 -0
  7. package/dist/exporters.js +105 -0
  8. package/dist/index.d.ts +5 -0
  9. package/dist/index.js +21 -0
  10. package/dist/ingest.d.ts +6 -0
  11. package/dist/ingest.js +58 -0
  12. package/dist/init.d.ts +22 -0
  13. package/dist/init.js +121 -0
  14. package/dist/instrumentations.d.ts +3 -0
  15. package/dist/instrumentations.js +79 -0
  16. package/dist/metrics.d.ts +5 -0
  17. package/dist/metrics.js +29 -0
  18. package/dist/network-capture/collector.d.ts +27 -0
  19. package/dist/network-capture/collector.js +312 -0
  20. package/dist/network-capture/http.d.ts +6 -0
  21. package/dist/network-capture/http.js +223 -0
  22. package/dist/network-capture/index.d.ts +2 -0
  23. package/dist/network-capture/index.js +7 -0
  24. package/dist/network-capture/undici.d.ts +9 -0
  25. package/dist/network-capture/undici.js +144 -0
  26. package/dist/propagation.d.ts +5 -0
  27. package/dist/propagation.js +45 -0
  28. package/dist/resource.d.ts +3 -0
  29. package/dist/resource.js +24 -0
  30. package/dist/state.d.ts +19 -0
  31. package/dist/state.js +44 -0
  32. package/dist/utils.d.ts +4 -0
  33. package/dist/utils.js +20 -0
  34. package/package.json +47 -19
  35. package/dist/node/src/capture-exception.d.ts +0 -9
  36. package/dist/node/src/capture-exception.js +0 -31
  37. package/dist/node/src/index.d.ts +0 -13
  38. package/dist/node/src/index.js +0 -19
  39. package/dist/node/src/init.d.ts +0 -27
  40. package/dist/node/src/init.js +0 -218
  41. package/dist/node/src/metrics.d.ts +0 -28
  42. package/dist/node/src/metrics.js +0 -69
  43. package/dist/shared/constants.d.ts +0 -1
  44. package/dist/shared/constants.js +0 -4
  45. package/dist/shared/util.d.ts +0 -9
  46. package/dist/shared/util.js +0 -21
@@ -0,0 +1,83 @@
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
+ }
@@ -0,0 +1,25 @@
1
+ import type { Attributes } from "@opentelemetry/api";
2
+ import type { ExportResult } from "@opentelemetry/core";
3
+ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
4
+ import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
5
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
6
+ import type { ReadableLogRecord } from "@opentelemetry/sdk-logs";
7
+ import type { ResourceMetrics } from "@opentelemetry/sdk-metrics";
8
+ import type { ReadableSpan } from "@opentelemetry/sdk-trace-base";
9
+ type Stamp = Attributes | undefined;
10
+ export declare class FoamTraceExporter extends OTLPTraceExporter {
11
+ private readonly stamp?;
12
+ constructor(token: string, stamp?: Stamp);
13
+ export(spans: ReadableSpan[], callback: (result: ExportResult) => void): void;
14
+ }
15
+ export declare class FoamLogExporter extends OTLPLogExporter {
16
+ private readonly stamp?;
17
+ constructor(token: string, stamp?: Stamp);
18
+ export(records: ReadableLogRecord[], callback: (result: ExportResult) => void): void;
19
+ }
20
+ export declare class FoamMetricExporter extends OTLPMetricExporter {
21
+ private readonly stamp?;
22
+ constructor(token: string, stamp?: Stamp);
23
+ export(resourceMetrics: ResourceMetrics, callback: (result: ExportResult) => void): void;
24
+ }
25
+ export {};
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FoamMetricExporter = exports.FoamLogExporter = exports.FoamTraceExporter = void 0;
4
+ const exporter_logs_otlp_http_1 = require("@opentelemetry/exporter-logs-otlp-http");
5
+ const exporter_metrics_otlp_http_1 = require("@opentelemetry/exporter-metrics-otlp-http");
6
+ const exporter_trace_otlp_http_1 = require("@opentelemetry/exporter-trace-otlp-http");
7
+ const resources_1 = require("@opentelemetry/resources");
8
+ const constants_js_1 = require("./constants.js");
9
+ class FoamTraceExporter extends exporter_trace_otlp_http_1.OTLPTraceExporter {
10
+ stamp;
11
+ constructor(token, stamp) {
12
+ super({
13
+ url: `${constants_js_1.FOAM_ENDPOINT}${constants_js_1.FOAM_OTLP_TRACES_PATH}`,
14
+ headers: { Authorization: `Bearer ${token}` },
15
+ });
16
+ this.stamp = stamp;
17
+ }
18
+ export(spans, callback) {
19
+ const { stamp } = this;
20
+ if (!stamp) {
21
+ super.export(spans, callback);
22
+ return;
23
+ }
24
+ const overlay = (0, resources_1.resourceFromAttributes)(stamp);
25
+ // Stamped copies never mutate the application's own telemetry objects.
26
+ // a shallow copy of each span so Foam can stamp the resource without mutating the original
27
+ const copies = spans.map((span) => ({
28
+ name: span.name,
29
+ kind: span.kind,
30
+ // SpanContext is a function so we need to bind it to the original span so we do not lose the context
31
+ spanContext: span.spanContext.bind(span),
32
+ parentSpanContext: span.parentSpanContext,
33
+ startTime: span.startTime,
34
+ endTime: span.endTime,
35
+ status: span.status,
36
+ attributes: span.attributes,
37
+ links: span.links,
38
+ events: span.events,
39
+ duration: span.duration,
40
+ ended: span.ended,
41
+ resource: span.resource.merge(overlay),
42
+ instrumentationScope: span.instrumentationScope,
43
+ droppedAttributesCount: span.droppedAttributesCount,
44
+ droppedEventsCount: span.droppedEventsCount,
45
+ droppedLinksCount: span.droppedLinksCount,
46
+ }));
47
+ super.export(copies, callback);
48
+ }
49
+ }
50
+ exports.FoamTraceExporter = FoamTraceExporter;
51
+ class FoamLogExporter extends exporter_logs_otlp_http_1.OTLPLogExporter {
52
+ stamp;
53
+ constructor(token, stamp) {
54
+ super({
55
+ url: `${constants_js_1.FOAM_ENDPOINT}${constants_js_1.FOAM_OTLP_LOGS_PATH}`,
56
+ headers: { Authorization: `Bearer ${token}` },
57
+ });
58
+ this.stamp = stamp;
59
+ }
60
+ export(records, callback) {
61
+ const { stamp } = this;
62
+ if (!stamp) {
63
+ super.export(records, callback);
64
+ return;
65
+ }
66
+ const overlay = (0, resources_1.resourceFromAttributes)(stamp);
67
+ const copies = records.map((record) => ({
68
+ hrTime: record.hrTime,
69
+ hrTimeObserved: record.hrTimeObserved,
70
+ spanContext: record.spanContext,
71
+ severityText: record.severityText,
72
+ severityNumber: record.severityNumber,
73
+ body: record.body,
74
+ eventName: record.eventName,
75
+ resource: record.resource.merge(overlay),
76
+ instrumentationScope: record.instrumentationScope,
77
+ attributes: record.attributes,
78
+ droppedAttributesCount: record.droppedAttributesCount,
79
+ }));
80
+ super.export(copies, callback);
81
+ }
82
+ }
83
+ exports.FoamLogExporter = FoamLogExporter;
84
+ class FoamMetricExporter extends exporter_metrics_otlp_http_1.OTLPMetricExporter {
85
+ stamp;
86
+ constructor(token, stamp) {
87
+ super({
88
+ url: `${constants_js_1.FOAM_ENDPOINT}${constants_js_1.FOAM_OTLP_METRICS_PATH}`,
89
+ headers: { Authorization: `Bearer ${token}` },
90
+ });
91
+ this.stamp = stamp;
92
+ }
93
+ export(resourceMetrics, callback) {
94
+ const { stamp } = this;
95
+ if (!stamp) {
96
+ super.export(resourceMetrics, callback);
97
+ return;
98
+ }
99
+ super.export({
100
+ ...resourceMetrics,
101
+ resource: resourceMetrics.resource.merge((0, resources_1.resourceFromAttributes)(stamp)),
102
+ }, callback);
103
+ }
104
+ }
105
+ exports.FoamMetricExporter = FoamMetricExporter;
@@ -0,0 +1,5 @@
1
+ export { init } from "./init.js";
2
+ export { getState } from "./state.js";
3
+ export { createFoamIngestLogRecordProcessor, createFoamIngestMetricReader, createFoamIngestSpanProcessor, } from "./ingest.js";
4
+ export { extractTraceContext, getBaggage, injectTraceContext, setBaggage, } from "./propagation.js";
5
+ export { addUpDownCounter, incrementCounter, recordHistogram, setMetric, } from "./metrics.js";
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setMetric = exports.recordHistogram = exports.incrementCounter = exports.addUpDownCounter = exports.setBaggage = exports.injectTraceContext = exports.getBaggage = exports.extractTraceContext = exports.createFoamIngestSpanProcessor = exports.createFoamIngestMetricReader = exports.createFoamIngestLogRecordProcessor = exports.getState = exports.init = void 0;
4
+ var init_js_1 = require("./init.js");
5
+ Object.defineProperty(exports, "init", { enumerable: true, get: function () { return init_js_1.init; } });
6
+ var state_js_1 = require("./state.js");
7
+ Object.defineProperty(exports, "getState", { enumerable: true, get: function () { return state_js_1.getState; } });
8
+ var ingest_js_1 = require("./ingest.js");
9
+ Object.defineProperty(exports, "createFoamIngestLogRecordProcessor", { enumerable: true, get: function () { return ingest_js_1.createFoamIngestLogRecordProcessor; } });
10
+ Object.defineProperty(exports, "createFoamIngestMetricReader", { enumerable: true, get: function () { return ingest_js_1.createFoamIngestMetricReader; } });
11
+ Object.defineProperty(exports, "createFoamIngestSpanProcessor", { enumerable: true, get: function () { return ingest_js_1.createFoamIngestSpanProcessor; } });
12
+ var propagation_js_1 = require("./propagation.js");
13
+ Object.defineProperty(exports, "extractTraceContext", { enumerable: true, get: function () { return propagation_js_1.extractTraceContext; } });
14
+ Object.defineProperty(exports, "getBaggage", { enumerable: true, get: function () { return propagation_js_1.getBaggage; } });
15
+ Object.defineProperty(exports, "injectTraceContext", { enumerable: true, get: function () { return propagation_js_1.injectTraceContext; } });
16
+ Object.defineProperty(exports, "setBaggage", { enumerable: true, get: function () { return propagation_js_1.setBaggage; } });
17
+ var metrics_js_1 = require("./metrics.js");
18
+ Object.defineProperty(exports, "addUpDownCounter", { enumerable: true, get: function () { return metrics_js_1.addUpDownCounter; } });
19
+ Object.defineProperty(exports, "incrementCounter", { enumerable: true, get: function () { return metrics_js_1.incrementCounter; } });
20
+ Object.defineProperty(exports, "recordHistogram", { enumerable: true, get: function () { return metrics_js_1.recordHistogram; } });
21
+ Object.defineProperty(exports, "setMetric", { enumerable: true, get: function () { return metrics_js_1.setMetric; } });
@@ -0,0 +1,6 @@
1
+ import { type LogRecordProcessor } from "@opentelemetry/sdk-logs";
2
+ import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
3
+ import { type SpanProcessor } from "@opentelemetry/sdk-trace-base";
4
+ export declare function createFoamIngestSpanProcessor(name: string, environment: string, token: string): SpanProcessor;
5
+ export declare function createFoamIngestLogRecordProcessor(name: string, environment: string, token: string): LogRecordProcessor;
6
+ export declare function createFoamIngestMetricReader(name: string, environment: string, token: string): PeriodicExportingMetricReader;
package/dist/ingest.js ADDED
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createFoamIngestSpanProcessor = createFoamIngestSpanProcessor;
4
+ exports.createFoamIngestLogRecordProcessor = createFoamIngestLogRecordProcessor;
5
+ exports.createFoamIngestMetricReader = createFoamIngestMetricReader;
6
+ const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
7
+ const sdk_logs_1 = require("@opentelemetry/sdk-logs");
8
+ const sdk_metrics_1 = require("@opentelemetry/sdk-metrics");
9
+ const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
10
+ const api_logs_1 = require("@opentelemetry/api-logs");
11
+ const constants_js_1 = require("./constants.js");
12
+ const diagnostics_js_1 = require("./diagnostics.js");
13
+ const exporters_js_1 = require("./exporters.js");
14
+ const state_js_1 = require("./state.js");
15
+ function prepareStamp(name, environment, token) {
16
+ if (!name?.trim() || !environment?.trim() || !token) {
17
+ throw new Error(`${constants_js_1.FOAM_IDENTIFIER_NAME}: name, environment, and token are required`);
18
+ }
19
+ return {
20
+ stamp: {
21
+ [semantic_conventions_1.ATTR_SERVICE_NAME]: name,
22
+ [semantic_conventions_1.ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: environment,
23
+ [constants_js_1.FOAM_INGEST_TIER]: "external",
24
+ },
25
+ };
26
+ }
27
+ function claimIngestSignal(signal, name, environment, token) {
28
+ (0, state_js_1.setSignal)(signal, true);
29
+ void (0, diagnostics_js_1.sendStateDiagnosticLog)({
30
+ name,
31
+ environment,
32
+ token,
33
+ tier: "external",
34
+ severity: api_logs_1.SeverityNumber.INFO,
35
+ });
36
+ }
37
+ function createFoamIngestSpanProcessor(name, environment, token) {
38
+ const { stamp } = prepareStamp(name, environment, token);
39
+ const processor = new sdk_trace_base_1.BatchSpanProcessor(new exporters_js_1.FoamTraceExporter(token, stamp));
40
+ claimIngestSignal(state_js_1.Signals.traces, name, environment, token);
41
+ return processor;
42
+ }
43
+ function createFoamIngestLogRecordProcessor(name, environment, token) {
44
+ const { stamp } = prepareStamp(name, environment, token);
45
+ const processor = new sdk_logs_1.BatchLogRecordProcessor({
46
+ exporter: new exporters_js_1.FoamLogExporter(token, stamp),
47
+ });
48
+ claimIngestSignal(state_js_1.Signals.logs, name, environment, token);
49
+ return processor;
50
+ }
51
+ function createFoamIngestMetricReader(name, environment, token) {
52
+ const { stamp } = prepareStamp(name, environment, token);
53
+ const reader = new sdk_metrics_1.PeriodicExportingMetricReader({
54
+ exporter: new exporters_js_1.FoamMetricExporter(token, stamp),
55
+ });
56
+ claimIngestSignal(state_js_1.Signals.metrics, name, environment, token);
57
+ return reader;
58
+ }
package/dist/init.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { type Attributes } from "@opentelemetry/api";
2
+ import { type Instrumentation } from "@opentelemetry/instrumentation";
3
+ import { type LogRecordProcessor } from "@opentelemetry/sdk-logs";
4
+ import { type MetricReader } from "@opentelemetry/sdk-metrics";
5
+ import { type SpanProcessor } from "@opentelemetry/sdk-trace-base";
6
+ export declare function init({ sampleRate, disableLogSending, networkCapture, // pcga11: We do not default to advanced because this is something our clients approve to opt in during onboarding.
7
+ enabled, ...options }: {
8
+ name: string;
9
+ environment: string;
10
+ token?: string;
11
+ version?: string;
12
+ sampleRate?: number;
13
+ disableLogSending?: boolean;
14
+ networkCapture?: "off" | "basic" | "advanced";
15
+ enabled?: boolean;
16
+ additionalInstrumentations?: readonly Instrumentation[];
17
+ additionalSpanProcessors?: readonly SpanProcessor[];
18
+ additionalLogRecordProcessors?: readonly LogRecordProcessor[];
19
+ additionalMetricReaders?: readonly MetricReader[];
20
+ additionalResourceAttributes?: Attributes;
21
+ ignoredOutboundHosts?: readonly string[];
22
+ }): void;
package/dist/init.js ADDED
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.init = init;
4
+ const api_1 = require("@opentelemetry/api");
5
+ const api_logs_1 = require("@opentelemetry/api-logs");
6
+ const context_async_hooks_1 = require("@opentelemetry/context-async-hooks");
7
+ const core_1 = require("@opentelemetry/core");
8
+ const instrumentation_1 = require("@opentelemetry/instrumentation");
9
+ const resource_js_1 = require("./resource.js");
10
+ const sdk_logs_1 = require("@opentelemetry/sdk-logs");
11
+ const sdk_metrics_1 = require("@opentelemetry/sdk-metrics");
12
+ const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
13
+ const constants_js_1 = require("./constants.js");
14
+ const diagnostics_js_1 = require("./diagnostics.js");
15
+ const exporters_js_1 = require("./exporters.js");
16
+ const instrumentations_js_1 = require("./instrumentations.js");
17
+ const state_js_1 = require("./state.js");
18
+ // TODO(pcga11): redact and beforeSend as well as conflicts handling not yet implemented.
19
+ function init({ sampleRate = 1, disableLogSending = false, networkCapture = "basic", // pcga11: We do not default to advanced because this is something our clients approve to opt in during onboarding.
20
+ enabled = true, ...options }) {
21
+ if (!enabled) {
22
+ (0, diagnostics_js_1.info)("Foam SDK for Node.js is disabled");
23
+ return;
24
+ }
25
+ if ((0, state_js_1.getInitialized)()) {
26
+ (0, diagnostics_js_1.warn)("Foam SDK for Node.js is already initialized");
27
+ return;
28
+ }
29
+ (0, diagnostics_js_1.configureDiagnostics)();
30
+ if (!options.name?.trim() || !options.environment?.trim() || !options.token) {
31
+ (0, diagnostics_js_1.error)(`${constants_js_1.FOAM_IDENTIFIER_NAME}: name, environment, and token are required`);
32
+ return;
33
+ }
34
+ try {
35
+ const resource = (0, resource_js_1.createFoamResource)(options.name, options.environment, options.version, options.additionalResourceAttributes);
36
+ // pcga11: Register our context manager and propagator; if another SDK already
37
+ // registered one, keep theirs
38
+ // TODO(pcga11): Implement a way to handle piggy backing the context manager and propagator from another SDK.
39
+ const contextManager = new context_async_hooks_1.AsyncLocalStorageContextManager();
40
+ contextManager.enable();
41
+ if (!api_1.context.setGlobalContextManager(contextManager)) {
42
+ contextManager.disable();
43
+ }
44
+ (0, state_js_1.setSignal)(state_js_1.Signals.baggage, api_1.propagation.setGlobalPropagator(new core_1.CompositePropagator({
45
+ propagators: [
46
+ new core_1.W3CTraceContextPropagator(),
47
+ new core_1.W3CBaggagePropagator(),
48
+ ],
49
+ })));
50
+ // pcga11: The @opentelemetry/api setters return false when another SDK already owns a slot (registration is first-wins). So we try to register our own and if it fails or already taken, nothing happens.
51
+ // TODO(pcga11): Implement a way to handle piggy backing the context and propagation from another SDK.
52
+ const tracerProvider = new sdk_trace_base_1.BasicTracerProvider({
53
+ resource,
54
+ sampler: new sdk_trace_base_1.ParentBasedSampler({
55
+ root: new sdk_trace_base_1.TraceIdRatioBasedSampler(sampleRate),
56
+ }),
57
+ spanProcessors: [
58
+ new sdk_trace_base_1.BatchSpanProcessor(new exporters_js_1.FoamTraceExporter(options.token)),
59
+ ...(options.additionalSpanProcessors ?? []),
60
+ ],
61
+ });
62
+ const meterProvider = new sdk_metrics_1.MeterProvider({
63
+ resource,
64
+ readers: [
65
+ new sdk_metrics_1.PeriodicExportingMetricReader({
66
+ exporter: new exporters_js_1.FoamMetricExporter(options.token),
67
+ }),
68
+ ...(options.additionalMetricReaders ?? []),
69
+ ],
70
+ });
71
+ const loggerProvider = new sdk_logs_1.LoggerProvider({
72
+ resource,
73
+ processors: [
74
+ new sdk_logs_1.BatchLogRecordProcessor({
75
+ exporter: new exporters_js_1.FoamLogExporter(options.token),
76
+ }),
77
+ ...(options.additionalLogRecordProcessors ?? []),
78
+ ],
79
+ });
80
+ // pcga11: trace/metrics/propagation setters return false when the slot is taken by another SDK. The logs setter returns whichever provider ended up registered. We use that to mark ownership.
81
+ (0, state_js_1.setSignal)(state_js_1.Signals.traces, api_1.trace.setGlobalTracerProvider(tracerProvider));
82
+ (0, state_js_1.setSignal)(state_js_1.Signals.metrics, api_1.metrics.setGlobalMeterProvider(meterProvider));
83
+ (0, state_js_1.setSignal)(state_js_1.Signals.logs, api_logs_1.logs.setGlobalLoggerProvider(loggerProvider) === loggerProvider);
84
+ const instrumentations = (0, instrumentations_js_1.buildInstrumentations)(networkCapture, disableLogSending, options.ignoredOutboundHosts, options.additionalInstrumentations ?? []);
85
+ (0, instrumentation_1.registerInstrumentations)({ instrumentations });
86
+ (0, state_js_1.setInstrumentations)(instrumentations.map(instrumentations_js_1.instrumentationName));
87
+ // pcga11: Signals are flushed, remaining batches are sent, and exporters are stopped before the process exits.
88
+ process.once("beforeExit", () => {
89
+ void Promise.all([
90
+ (0, state_js_1.getSignal)(state_js_1.Signals.traces) ? tracerProvider.shutdown() : undefined,
91
+ (0, state_js_1.getSignal)(state_js_1.Signals.metrics) ? meterProvider.shutdown() : undefined,
92
+ (0, state_js_1.getSignal)(state_js_1.Signals.logs) ? loggerProvider.shutdown() : undefined,
93
+ ]).catch(() => undefined);
94
+ });
95
+ (0, state_js_1.setInitialized)(true);
96
+ (0, diagnostics_js_1.info)("Foam SDK for Node.js initialized");
97
+ void (0, diagnostics_js_1.sendStateDiagnosticLog)({
98
+ name: options.name,
99
+ environment: options.environment,
100
+ token: options.token,
101
+ tier: "internal",
102
+ severity: api_logs_1.SeverityNumber.INFO,
103
+ });
104
+ }
105
+ catch (error) {
106
+ const errorMessage = error instanceof Error ? error.message : String(error);
107
+ for (const signal of [state_js_1.Signals.traces, state_js_1.Signals.metrics, state_js_1.Signals.logs, state_js_1.Signals.baggage]) {
108
+ (0, state_js_1.setSignal)(signal, false);
109
+ }
110
+ (0, state_js_1.setInitialized)(false);
111
+ (0, diagnostics_js_1.error)(`Foam SDK for Node.js initialization failed: ${errorMessage}`);
112
+ void (0, diagnostics_js_1.sendStateDiagnosticLog)({
113
+ name: options.name,
114
+ environment: options.environment,
115
+ token: options.token,
116
+ tier: "internal",
117
+ severity: api_logs_1.SeverityNumber.ERROR,
118
+ error: errorMessage,
119
+ });
120
+ }
121
+ }
@@ -0,0 +1,3 @@
1
+ import type { Instrumentation } from "@opentelemetry/instrumentation";
2
+ export declare function buildInstrumentations(networkCapture?: "off" | "basic" | "advanced", disableLogSending?: boolean, ignoredOutboundHosts?: readonly string[], additionalInstrumentations?: readonly Instrumentation[]): Instrumentation[];
3
+ export declare const instrumentationName: (instrumentation: Instrumentation) => string;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.instrumentationName = void 0;
4
+ exports.buildInstrumentations = buildInstrumentations;
5
+ const auto_instrumentations_node_1 = require("@opentelemetry/auto-instrumentations-node");
6
+ const instrumentation_console_1 = require("@opentelemetry/instrumentation-console");
7
+ const index_js_1 = require("./network-capture/index.js");
8
+ const state_js_1 = require("./state.js");
9
+ const constants_js_1 = require("./constants.js");
10
+ function buildInstrumentations(networkCapture = "basic", disableLogSending = false, ignoredOutboundHosts, additionalInstrumentations) {
11
+ const bodyCaptureEnabled = (0, state_js_1.getSignal)(state_js_1.Signals.logs);
12
+ const capturedHeaders = networkCapture === "off" ? undefined : [...constants_js_1.SAFE_NETWORK_HEADERS];
13
+ const bodyHooks = networkCapture === "advanced"
14
+ ? (0, index_js_1.createNetworkCaptureHooks)(bodyCaptureEnabled)
15
+ : undefined;
16
+ const advancedUndiciHooks = networkCapture === "advanced"
17
+ ? (0, index_js_1.createUndiciNetworkCaptureHooks)(bodyCaptureEnabled)
18
+ : undefined;
19
+ const automatic = (0, auto_instrumentations_node_1.getNodeAutoInstrumentations)({
20
+ "@opentelemetry/instrumentation-bunyan": {
21
+ disableLogSending: disableLogSending === true,
22
+ },
23
+ "@opentelemetry/instrumentation-http": {
24
+ ...(capturedHeaders
25
+ ? {
26
+ headersToSpanAttributes: {
27
+ client: {
28
+ requestHeaders: capturedHeaders,
29
+ responseHeaders: capturedHeaders,
30
+ },
31
+ server: {
32
+ requestHeaders: capturedHeaders,
33
+ responseHeaders: capturedHeaders,
34
+ },
35
+ },
36
+ }
37
+ : {}),
38
+ ...(bodyHooks ?? {}),
39
+ ignoreOutgoingRequestHook: ((request) => typeof request.hostname === "string" &&
40
+ (ignoredOutboundHosts ?? []).includes(request.hostname)),
41
+ },
42
+ "@opentelemetry/instrumentation-openai": {
43
+ captureMessageContent: networkCapture === "advanced",
44
+ },
45
+ "@opentelemetry/instrumentation-pino": {
46
+ disableLogSending: disableLogSending === true,
47
+ },
48
+ "@opentelemetry/instrumentation-undici": {
49
+ ...(capturedHeaders
50
+ ? {
51
+ headersToSpanAttributes: {
52
+ requestHeaders: capturedHeaders,
53
+ responseHeaders: capturedHeaders,
54
+ },
55
+ }
56
+ : {}),
57
+ ...(advancedUndiciHooks ?? {}),
58
+ },
59
+ "@opentelemetry/instrumentation-winston": {
60
+ disableLogSending: disableLogSending === true,
61
+ },
62
+ });
63
+ const instrumentations = [
64
+ ...automatic,
65
+ // pcga11: Ignores disableLogSending: console has no in-process transports to double-ship through.
66
+ new instrumentation_console_1.ConsoleInstrumentation(),
67
+ ...(additionalInstrumentations ?? []),
68
+ ];
69
+ // pcga11: Last entry per name wins: user entries win over our defaults, so on a name
70
+ // collision the map holds our instance as they come first in the list and we disable it.
71
+ const unique = new Map();
72
+ for (const entry of instrumentations) {
73
+ unique.get(entry.instrumentationName)?.disable();
74
+ unique.set(entry.instrumentationName, entry);
75
+ }
76
+ return [...unique.values()];
77
+ }
78
+ const instrumentationName = (instrumentation) => instrumentation.instrumentationName.replace(/^@opentelemetry\/instrumentation-/, "");
79
+ exports.instrumentationName = instrumentationName;
@@ -0,0 +1,5 @@
1
+ import { type Attributes, type MetricOptions } from "@opentelemetry/api";
2
+ export declare const incrementCounter: (name: string, n?: number | undefined, attributes?: Attributes | undefined, options?: MetricOptions | undefined) => void;
3
+ export declare const recordHistogram: (name: string, value: number, attributes?: Attributes | undefined, options?: MetricOptions | undefined) => void;
4
+ export declare const addUpDownCounter: (name: string, n: number, attributes?: Attributes | undefined, options?: MetricOptions | undefined) => void;
5
+ export declare const setMetric: (name: string, value: number, attributes?: Attributes | undefined, options?: MetricOptions | undefined) => void;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setMetric = exports.addUpDownCounter = exports.recordHistogram = exports.incrementCounter = void 0;
4
+ const api_1 = require("@opentelemetry/api");
5
+ const sdk_metrics_1 = require("@opentelemetry/sdk-metrics");
6
+ const constants_js_1 = require("./constants.js");
7
+ const utils_js_1 = require("./utils.js");
8
+ const state_js_1 = require("./state.js");
9
+ const instruments = new Map();
10
+ const meter = () => api_1.metrics.getMeter(constants_js_1.FOAM_DISTRO_NAME);
11
+ // pcga11: We use cache to avoid wasting time searching for the meter every time we need to create an instrument.
12
+ function cachedInstrument(type, name, create) {
13
+ const key = `${type}:${name}`;
14
+ if (!instruments.has(key))
15
+ instruments.set(key, create());
16
+ return instruments.get(key);
17
+ }
18
+ exports.incrementCounter = (0, utils_js_1.whenInitialized)(state_js_1.Signals.metrics, (name, n = 1, attributes, options = {}) => {
19
+ cachedInstrument(sdk_metrics_1.InstrumentType.COUNTER, name, () => meter().createCounter(name, options)).add(n, attributes);
20
+ });
21
+ exports.recordHistogram = (0, utils_js_1.whenInitialized)(state_js_1.Signals.metrics, (name, value, attributes, options = {}) => {
22
+ cachedInstrument(sdk_metrics_1.InstrumentType.HISTOGRAM, name, () => meter().createHistogram(name, options)).record(value, attributes);
23
+ });
24
+ exports.addUpDownCounter = (0, utils_js_1.whenInitialized)(state_js_1.Signals.metrics, (name, n, attributes, options = {}) => {
25
+ cachedInstrument(sdk_metrics_1.InstrumentType.UP_DOWN_COUNTER, name, () => meter().createUpDownCounter(name, options)).add(n, attributes);
26
+ });
27
+ exports.setMetric = (0, utils_js_1.whenInitialized)(state_js_1.Signals.metrics, (name, value, attributes, options = {}) => {
28
+ cachedInstrument(sdk_metrics_1.InstrumentType.GAUGE, name, () => meter().createGauge(name, options)).record(value, attributes);
29
+ });
@@ -0,0 +1,27 @@
1
+ import { type Context, type Span } from "@opentelemetry/api";
2
+ export type CaptureDirection = "request" | "response";
3
+ export type CaptureSide = "client" | "server";
4
+ export type HeaderSnapshot = Readonly<Record<string, unknown>>;
5
+ export declare function setHeaderAttributes(span: Span, direction: CaptureDirection, headers: HeaderSnapshot): void;
6
+ export declare class BodyCollector {
7
+ private readonly span;
8
+ private readonly context;
9
+ private readonly side;
10
+ private readonly direction;
11
+ private readonly headers;
12
+ private readonly id;
13
+ private readonly attr;
14
+ private readonly chunks;
15
+ private readonly cleanups;
16
+ private readonly deadline;
17
+ private observedBytes;
18
+ private capturedBytes;
19
+ private truncated;
20
+ private finalized;
21
+ constructor(span: Span, context: Context, side: CaptureSide, direction: CaptureDirection, headers: () => HeaderSnapshot);
22
+ static create(span: Span, side: CaptureSide, direction: CaptureDirection, headers: () => HeaderSnapshot): BodyCollector | undefined;
23
+ addCleanup(cleanup: () => void): void;
24
+ observe(value: unknown, encoding?: unknown): void;
25
+ finalize(complete: boolean): void;
26
+ private emit;
27
+ }