@foam-ai/node 0.1.0-alpha.4 → 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.
@@ -1,13 +1,15 @@
1
+ import { SeverityNumber } from "@opentelemetry/api-logs";
1
2
  export declare const FOAM_ENDPOINT = "https://otel.api.foam.ai";
2
- export declare const FOAM_IDENTIFIER_NAME = "foam-otel";
3
+ export declare const FOAM_INGEST_HOST: string;
4
+ export declare const FOAM_IDENTIFIER_NAME = "[foam-otel]";
3
5
  export declare const FOAM_DISTRO_NAME = "@foam-ai/node";
4
- export declare const FOAM_DISTRO_VERSION = "0.1.0-alpha.4";
5
- export declare const FOAM_INGEST_TIER = "foam.ingest.tier";
6
+ export declare const FOAM_DISTRO_VERSION = "0.1.0-alpha.7";
6
7
  export declare const FOAM_OTLP_TRACES_PATH = "/v1/traces";
7
8
  export declare const FOAM_OTLP_LOGS_PATH = "/v1/logs";
8
9
  export declare const FOAM_OTLP_METRICS_PATH = "/v1/metrics";
9
10
  export declare const NETWORK_BODY_CHUNK_BYTES: number;
10
11
  export declare const NETWORK_BODY_MAX_CAPTURE_BYTES: number;
12
+ export declare const NETWORK_BODY_MAX_DECODE_BYTES: number;
11
13
  export declare const NETWORK_CAPTURE_EVENT = "foam.http.body.chunk";
12
14
  export declare const NETWORK_CAPTURE_EVENT_BODY = "HTTP body chunk";
13
15
  export declare const NETWORK_CAPTURE_DEADLINE_MS = 60000;
@@ -34,9 +36,8 @@ export declare const ATTR_FOAM_HTTP_RESPONSE_BODY_COMPLETE = "foam.http.response
34
36
  export declare const ATTR_FOAM_HTTP_REQUEST_BODY_TRUNCATED = "foam.http.request.body.truncated";
35
37
  export declare const ATTR_FOAM_HTTP_RESPONSE_BODY_TRUNCATED = "foam.http.response.body.truncated";
36
38
  export declare const SAFE_NETWORK_HEADERS: readonly ["content-type", "content-length", "content-encoding"];
39
+ export declare const REDACTED_VALUE = "[REDACTED]";
40
+ export declare const SENSITIVE_BODY_KEY_PATTERNS: readonly ["password", "passwd", "passphrase", "secret", "token", "apikey", "authorization", "credential", "privatekey", "sessionid", "creditcard", "cardnumber", "accountnumber", "routingnumber"];
41
+ export declare const SENSITIVE_BODY_KEYS_EXACT: readonly ["auth", "pwd", "pin", "otp", "cvv", "cvc", "ssn", "key", "cookie", "setcookie", "session"];
37
42
  export declare const DIAG_LOG_LEVEL = "OTEL_LOG_LEVEL";
38
- export declare const SEVERITY_TEXT: {
39
- readonly 9: "INFO";
40
- readonly 13: "WARN";
41
- readonly 17: "ERROR";
42
- };
43
+ export declare const SEVERITY_TEXT: Partial<Record<SeverityNumber, string>>;
package/dist/constants.js CHANGED
@@ -1,17 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SEVERITY_TEXT = exports.DIAG_LOG_LEVEL = exports.SAFE_NETWORK_HEADERS = exports.ATTR_FOAM_HTTP_RESPONSE_BODY_TRUNCATED = exports.ATTR_FOAM_HTTP_REQUEST_BODY_TRUNCATED = exports.ATTR_FOAM_HTTP_RESPONSE_BODY_COMPLETE = exports.ATTR_FOAM_HTTP_REQUEST_BODY_COMPLETE = exports.ATTR_FOAM_HTTP_RESPONSE_BODY_CAPTURE_ID = exports.ATTR_FOAM_HTTP_REQUEST_BODY_CAPTURE_ID = exports.ATTR_FOAM_HTTP_BODY_CHUNK_CONTENT = exports.ATTR_FOAM_HTTP_BODY_CHUNK_BYTES = exports.ATTR_FOAM_HTTP_BODY_CHUNK_INDEX = exports.ATTR_FOAM_HTTP_BODY_CHUNK_COUNT = exports.ATTR_FOAM_HTTP_BODY_CAPTURED_BYTES = exports.ATTR_FOAM_HTTP_BODY_OUTCOME = exports.ATTR_FOAM_HTTP_BODY_TRUNCATED = exports.ATTR_FOAM_HTTP_BODY_COMPLETE = exports.ATTR_FOAM_HTTP_BODY_DIRECTION = exports.ATTR_FOAM_HTTP_BODY_SIDE = exports.ATTR_FOAM_HTTP_BODY_ID = exports.ATTR_HTTP_RESPONSE_BODY_CONTENT = exports.ATTR_HTTP_REQUEST_BODY_CONTENT = exports.ATTR_HTTP_RESPONSE_BODY_SIZE = exports.ATTR_HTTP_REQUEST_BODY_SIZE = exports.MAX_ACTIVE_CAPTURES = exports.NETWORK_CAPTURE_DEADLINE_MS = exports.NETWORK_CAPTURE_EVENT_BODY = exports.NETWORK_CAPTURE_EVENT = exports.NETWORK_BODY_MAX_CAPTURE_BYTES = exports.NETWORK_BODY_CHUNK_BYTES = exports.FOAM_OTLP_METRICS_PATH = exports.FOAM_OTLP_LOGS_PATH = exports.FOAM_OTLP_TRACES_PATH = exports.FOAM_INGEST_TIER = exports.FOAM_DISTRO_VERSION = exports.FOAM_DISTRO_NAME = exports.FOAM_IDENTIFIER_NAME = exports.FOAM_ENDPOINT = void 0;
3
+ exports.SEVERITY_TEXT = exports.DIAG_LOG_LEVEL = exports.SENSITIVE_BODY_KEYS_EXACT = exports.SENSITIVE_BODY_KEY_PATTERNS = exports.REDACTED_VALUE = exports.SAFE_NETWORK_HEADERS = exports.ATTR_FOAM_HTTP_RESPONSE_BODY_TRUNCATED = exports.ATTR_FOAM_HTTP_REQUEST_BODY_TRUNCATED = exports.ATTR_FOAM_HTTP_RESPONSE_BODY_COMPLETE = exports.ATTR_FOAM_HTTP_REQUEST_BODY_COMPLETE = exports.ATTR_FOAM_HTTP_RESPONSE_BODY_CAPTURE_ID = exports.ATTR_FOAM_HTTP_REQUEST_BODY_CAPTURE_ID = exports.ATTR_FOAM_HTTP_BODY_CHUNK_CONTENT = exports.ATTR_FOAM_HTTP_BODY_CHUNK_BYTES = exports.ATTR_FOAM_HTTP_BODY_CHUNK_INDEX = exports.ATTR_FOAM_HTTP_BODY_CHUNK_COUNT = exports.ATTR_FOAM_HTTP_BODY_CAPTURED_BYTES = exports.ATTR_FOAM_HTTP_BODY_OUTCOME = exports.ATTR_FOAM_HTTP_BODY_TRUNCATED = exports.ATTR_FOAM_HTTP_BODY_COMPLETE = exports.ATTR_FOAM_HTTP_BODY_DIRECTION = exports.ATTR_FOAM_HTTP_BODY_SIDE = exports.ATTR_FOAM_HTTP_BODY_ID = exports.ATTR_HTTP_RESPONSE_BODY_CONTENT = exports.ATTR_HTTP_REQUEST_BODY_CONTENT = exports.ATTR_HTTP_RESPONSE_BODY_SIZE = exports.ATTR_HTTP_REQUEST_BODY_SIZE = exports.MAX_ACTIVE_CAPTURES = exports.NETWORK_CAPTURE_DEADLINE_MS = exports.NETWORK_CAPTURE_EVENT_BODY = exports.NETWORK_CAPTURE_EVENT = exports.NETWORK_BODY_MAX_DECODE_BYTES = exports.NETWORK_BODY_MAX_CAPTURE_BYTES = exports.NETWORK_BODY_CHUNK_BYTES = exports.FOAM_OTLP_METRICS_PATH = exports.FOAM_OTLP_LOGS_PATH = exports.FOAM_OTLP_TRACES_PATH = exports.FOAM_DISTRO_VERSION = exports.FOAM_DISTRO_NAME = exports.FOAM_IDENTIFIER_NAME = exports.FOAM_INGEST_HOST = exports.FOAM_ENDPOINT = void 0;
4
4
  const api_logs_1 = require("@opentelemetry/api-logs");
5
5
  exports.FOAM_ENDPOINT = "https://otel.api.foam.ai";
6
- exports.FOAM_IDENTIFIER_NAME = "foam-otel";
6
+ exports.FOAM_INGEST_HOST = new URL(exports.FOAM_ENDPOINT).hostname;
7
+ exports.FOAM_IDENTIFIER_NAME = "[foam-otel]";
7
8
  exports.FOAM_DISTRO_NAME = "@foam-ai/node";
8
- exports.FOAM_DISTRO_VERSION = "0.1.0-alpha.4";
9
- exports.FOAM_INGEST_TIER = "foam.ingest.tier";
9
+ exports.FOAM_DISTRO_VERSION = "0.1.0-alpha.7";
10
10
  exports.FOAM_OTLP_TRACES_PATH = "/v1/traces";
11
11
  exports.FOAM_OTLP_LOGS_PATH = "/v1/logs";
12
12
  exports.FOAM_OTLP_METRICS_PATH = "/v1/metrics";
13
- exports.NETWORK_BODY_CHUNK_BYTES = 64 * 1024; // 64Kib
14
- exports.NETWORK_BODY_MAX_CAPTURE_BYTES = 1024 * 1024; // 1Mib
13
+ exports.NETWORK_BODY_CHUNK_BYTES = 64 * 1024; // pcga11: 64Kib
14
+ exports.NETWORK_BODY_MAX_CAPTURE_BYTES = 1024 * 1024; // pcga11: 1Mib
15
+ // Bounds sync zlib output when decoding captured bodies. 1 MiB of captured
16
+ // gzip can legally inflate to ~1 GiB (worst case ~1032:1), which would be
17
+ // allocated synchronously on the event loop and could OOM-abort the host
18
+ // app. Past this bound zlib throws instead, and the capture falls back to
19
+ // the compressed wire bytes. 16x covers typical text compression ratios.
20
+ exports.NETWORK_BODY_MAX_DECODE_BYTES = 16 * exports.NETWORK_BODY_MAX_CAPTURE_BYTES;
15
21
  exports.NETWORK_CAPTURE_EVENT = "foam.http.body.chunk";
16
22
  exports.NETWORK_CAPTURE_EVENT_BODY = "HTTP body chunk";
17
23
  exports.NETWORK_CAPTURE_DEADLINE_MS = 60_000;
@@ -46,9 +52,46 @@ exports.SAFE_NETWORK_HEADERS = [
46
52
  "content-length",
47
53
  "content-encoding",
48
54
  ];
55
+ exports.REDACTED_VALUE = "[REDACTED]";
56
+ // pcga11: Matched against body keys after lowercasing and stripping the
57
+ // -_. separators, so "api-key", "apiKey", and "API_KEY" all normalize to
58
+ // "apikey". Patterns match anywhere in the key ("user_password" is caught);
59
+ // exact keys are too short or too common to substring-match safely.
60
+ exports.SENSITIVE_BODY_KEY_PATTERNS = [
61
+ "password",
62
+ "passwd",
63
+ "passphrase",
64
+ "secret",
65
+ "token",
66
+ "apikey",
67
+ "authorization",
68
+ "credential",
69
+ "privatekey",
70
+ "sessionid",
71
+ "creditcard",
72
+ "cardnumber",
73
+ "accountnumber",
74
+ "routingnumber",
75
+ ];
76
+ exports.SENSITIVE_BODY_KEYS_EXACT = [
77
+ "auth",
78
+ "pwd",
79
+ "pin",
80
+ "otp",
81
+ "cvv",
82
+ "cvc",
83
+ "ssn",
84
+ "key",
85
+ "cookie",
86
+ "setcookie",
87
+ "session",
88
+ ];
49
89
  exports.DIAG_LOG_LEVEL = "OTEL_LOG_LEVEL";
50
90
  exports.SEVERITY_TEXT = {
91
+ [api_logs_1.SeverityNumber.TRACE]: "TRACE",
92
+ [api_logs_1.SeverityNumber.DEBUG]: "DEBUG",
51
93
  [api_logs_1.SeverityNumber.INFO]: "INFO",
52
94
  [api_logs_1.SeverityNumber.WARN]: "WARN",
53
95
  [api_logs_1.SeverityNumber.ERROR]: "ERROR",
96
+ [api_logs_1.SeverityNumber.FATAL]: "FATAL",
54
97
  };
@@ -0,0 +1,2 @@
1
+ export declare let endpoint: string;
2
+ export declare const setEndpoint: (value: string) => void;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setEndpoint = exports.endpoint = void 0;
4
+ const constants_js_1 = require("./constants.js");
5
+ // pcga11: Defaults to Foam's endpoint and changes only on an explicit setEndpoint
6
+ // call. Exported as a live binding so importers always read the current value.
7
+ exports.endpoint = constants_js_1.FOAM_ENDPOINT;
8
+ const setEndpoint = (value) => {
9
+ exports.endpoint = value;
10
+ };
11
+ exports.setEndpoint = setEndpoint;
package/dist/exporters.js CHANGED
@@ -6,11 +6,12 @@ const exporter_metrics_otlp_http_1 = require("@opentelemetry/exporter-metrics-ot
6
6
  const exporter_trace_otlp_http_1 = require("@opentelemetry/exporter-trace-otlp-http");
7
7
  const resources_1 = require("@opentelemetry/resources");
8
8
  const constants_js_1 = require("./constants.js");
9
+ const endpoint_js_1 = require("./endpoint.js");
9
10
  class FoamTraceExporter extends exporter_trace_otlp_http_1.OTLPTraceExporter {
10
11
  stamp;
11
12
  constructor(token, stamp) {
12
13
  super({
13
- url: `${constants_js_1.FOAM_ENDPOINT}${constants_js_1.FOAM_OTLP_TRACES_PATH}`,
14
+ url: `${endpoint_js_1.endpoint}${constants_js_1.FOAM_OTLP_TRACES_PATH}`,
14
15
  headers: { Authorization: `Bearer ${token}` },
15
16
  });
16
17
  this.stamp = stamp;
@@ -22,12 +23,9 @@ class FoamTraceExporter extends exporter_trace_otlp_http_1.OTLPTraceExporter {
22
23
  return;
23
24
  }
24
25
  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
26
  const copies = spans.map((span) => ({
28
27
  name: span.name,
29
28
  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
29
  spanContext: span.spanContext.bind(span),
32
30
  parentSpanContext: span.parentSpanContext,
33
31
  startTime: span.startTime,
@@ -52,7 +50,7 @@ class FoamLogExporter extends exporter_logs_otlp_http_1.OTLPLogExporter {
52
50
  stamp;
53
51
  constructor(token, stamp) {
54
52
  super({
55
- url: `${constants_js_1.FOAM_ENDPOINT}${constants_js_1.FOAM_OTLP_LOGS_PATH}`,
53
+ url: `${endpoint_js_1.endpoint}${constants_js_1.FOAM_OTLP_LOGS_PATH}`,
56
54
  headers: { Authorization: `Bearer ${token}` },
57
55
  });
58
56
  this.stamp = stamp;
@@ -85,7 +83,7 @@ class FoamMetricExporter extends exporter_metrics_otlp_http_1.OTLPMetricExporter
85
83
  stamp;
86
84
  constructor(token, stamp) {
87
85
  super({
88
- url: `${constants_js_1.FOAM_ENDPOINT}${constants_js_1.FOAM_OTLP_METRICS_PATH}`,
86
+ url: `${endpoint_js_1.endpoint}${constants_js_1.FOAM_OTLP_METRICS_PATH}`,
89
87
  headers: { Authorization: `Bearer ${token}` },
90
88
  });
91
89
  this.stamp = stamp;
package/dist/index.d.ts CHANGED
@@ -3,3 +3,5 @@ export { getState } from "./state.js";
3
3
  export { createFoamIngestLogRecordProcessor, createFoamIngestMetricReader, createFoamIngestSpanProcessor, } from "./ingest.js";
4
4
  export { extractTraceContext, getBaggage, injectTraceContext, setBaggage, } from "./propagation.js";
5
5
  export { addUpDownCounter, incrementCounter, recordHistogram, setMetric, } from "./metrics.js";
6
+ export { log, SeverityNumber } from "./logs.js";
7
+ export { recordException } from "./traces.js";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
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;
3
+ exports.recordException = exports.SeverityNumber = exports.log = 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
4
  var init_js_1 = require("./init.js");
5
5
  Object.defineProperty(exports, "init", { enumerable: true, get: function () { return init_js_1.init; } });
6
6
  var state_js_1 = require("./state.js");
@@ -19,3 +19,8 @@ Object.defineProperty(exports, "addUpDownCounter", { enumerable: true, get: func
19
19
  Object.defineProperty(exports, "incrementCounter", { enumerable: true, get: function () { return metrics_js_1.incrementCounter; } });
20
20
  Object.defineProperty(exports, "recordHistogram", { enumerable: true, get: function () { return metrics_js_1.recordHistogram; } });
21
21
  Object.defineProperty(exports, "setMetric", { enumerable: true, get: function () { return metrics_js_1.setMetric; } });
22
+ var logs_js_1 = require("./logs.js");
23
+ Object.defineProperty(exports, "log", { enumerable: true, get: function () { return logs_js_1.log; } });
24
+ Object.defineProperty(exports, "SeverityNumber", { enumerable: true, get: function () { return logs_js_1.SeverityNumber; } });
25
+ var traces_js_1 = require("./traces.js");
26
+ Object.defineProperty(exports, "recordException", { enumerable: true, get: function () { return traces_js_1.recordException; } });
package/dist/ingest.js CHANGED
@@ -9,8 +9,9 @@ const sdk_metrics_1 = require("@opentelemetry/sdk-metrics");
9
9
  const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
10
10
  const api_logs_1 = require("@opentelemetry/api-logs");
11
11
  const constants_js_1 = require("./constants.js");
12
- const diagnostics_js_1 = require("./diagnostics.js");
12
+ const report_js_1 = require("./report.js");
13
13
  const exporters_js_1 = require("./exporters.js");
14
+ const logs_js_1 = require("./logs.js");
14
15
  const state_js_1 = require("./state.js");
15
16
  function prepareStamp(name, environment, token) {
16
17
  if (!name?.trim() || !environment?.trim() || !token) {
@@ -20,24 +21,25 @@ function prepareStamp(name, environment, token) {
20
21
  stamp: {
21
22
  [semantic_conventions_1.ATTR_SERVICE_NAME]: name,
22
23
  [semantic_conventions_1.ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: environment,
23
- [constants_js_1.FOAM_INGEST_TIER]: "external",
24
+ [constants_js_1.FOAM_INGEST_HOST]: constants_js_1.FOAM_INGEST_HOST,
24
25
  },
25
26
  };
26
27
  }
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",
28
+ function reportIngestSignal(signal, name, environment, token) {
29
+ (0, state_js_1.setSignal)(signal, state_js_1.SignalSources.ingest);
30
+ // pcga11: report() prefixes the identifier itself; adding it here would double it.
31
+ (0, report_js_1.report)({
32
+ token: token,
33
+ name: name,
34
+ environment: environment,
34
35
  severity: api_logs_1.SeverityNumber.INFO,
36
+ message: `ingest ${signal} processor attached`,
35
37
  });
36
38
  }
37
39
  function createFoamIngestSpanProcessor(name, environment, token) {
38
40
  const { stamp } = prepareStamp(name, environment, token);
39
41
  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);
42
+ reportIngestSignal(state_js_1.Signals.traces, name, environment, token);
41
43
  return processor;
42
44
  }
43
45
  function createFoamIngestLogRecordProcessor(name, environment, token) {
@@ -45,7 +47,8 @@ function createFoamIngestLogRecordProcessor(name, environment, token) {
45
47
  const processor = new sdk_logs_1.BatchLogRecordProcessor({
46
48
  exporter: new exporters_js_1.FoamLogExporter(token, stamp),
47
49
  });
48
- claimIngestSignal(state_js_1.Signals.logs, name, environment, token);
50
+ (0, logs_js_1.ensureFoamLoggerProvider)(name, environment, token, stamp);
51
+ reportIngestSignal(state_js_1.Signals.logs, name, environment, token);
49
52
  return processor;
50
53
  }
51
54
  function createFoamIngestMetricReader(name, environment, token) {
@@ -53,6 +56,6 @@ function createFoamIngestMetricReader(name, environment, token) {
53
56
  const reader = new sdk_metrics_1.PeriodicExportingMetricReader({
54
57
  exporter: new exporters_js_1.FoamMetricExporter(token, stamp),
55
58
  });
56
- claimIngestSignal(state_js_1.Signals.metrics, name, environment, token);
59
+ reportIngestSignal(state_js_1.Signals.metrics, name, environment, token);
57
60
  return reader;
58
61
  }
package/dist/init.d.ts CHANGED
@@ -3,8 +3,7 @@ import { type Instrumentation } from "@opentelemetry/instrumentation";
3
3
  import { type LogRecordProcessor } from "@opentelemetry/sdk-logs";
4
4
  import { type MetricReader } from "@opentelemetry/sdk-metrics";
5
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 }: {
6
+ export interface InitOptions {
8
7
  name: string;
9
8
  environment: string;
10
9
  token?: string;
@@ -19,4 +18,5 @@ enabled, ...options }: {
19
18
  additionalMetricReaders?: readonly MetricReader[];
20
19
  additionalResourceAttributes?: Attributes;
21
20
  ignoredOutboundHosts?: readonly string[];
22
- }): void;
21
+ }
22
+ export declare function init(options: InitOptions): void;
package/dist/init.js CHANGED
@@ -10,32 +10,60 @@ const resource_js_1 = require("./resource.js");
10
10
  const sdk_logs_1 = require("@opentelemetry/sdk-logs");
11
11
  const sdk_metrics_1 = require("@opentelemetry/sdk-metrics");
12
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");
13
+ const report_js_1 = require("./report.js");
15
14
  const exporters_js_1 = require("./exporters.js");
16
15
  const instrumentations_js_1 = require("./instrumentations.js");
16
+ const logs_js_1 = require("./logs.js");
17
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 }) {
18
+ // TODO(pcga11): conflicts, redact and beforeSend handling pending.
19
+ function init(options) {
20
+ const { sampleRate = 1, disableLogSending = false, networkCapture = "basic", enabled = true, } = options;
21
+ // pcga11: Set params before any early return so every state is reported, including failed or disabled attempts.
22
+ (0, state_js_1.setParams)({ name: options.name, environment: options.environment });
23
+ // pcga11: We keep this option because companies often want to turn the integration off in specific envs
21
24
  if (!enabled) {
22
- (0, diagnostics_js_1.info)("Foam SDK for Node.js is disabled");
25
+ (0, report_js_1.report)({
26
+ token: options.token,
27
+ severity: api_logs_1.SeverityNumber.INFO,
28
+ message: 'SDK disabled',
29
+ });
30
+ return;
31
+ }
32
+ if (!options.token) {
33
+ // pcga11: Nothing can be sent without a token; report() still logs locally so this is not silent.
34
+ (0, report_js_1.report)({
35
+ severity: api_logs_1.SeverityNumber.ERROR,
36
+ message: 'Parameter token not provided',
37
+ });
23
38
  return;
24
39
  }
40
+ // pcga11: Re-initializing could register duplicate providers/instrumentations, so we track the initialization state and bail out on repeat calls
25
41
  if ((0, state_js_1.getInitialized)()) {
26
- (0, diagnostics_js_1.warn)("Foam SDK for Node.js is already initialized");
42
+ (0, report_js_1.report)({
43
+ token: options.token,
44
+ severity: api_logs_1.SeverityNumber.WARN,
45
+ message: 'SDK already initialized',
46
+ });
27
47
  return;
28
48
  }
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`);
49
+ if (!options.name?.trim() || !options.environment?.trim()) {
50
+ (0, report_js_1.report)({
51
+ token: options.token,
52
+ severity: api_logs_1.SeverityNumber.ERROR,
53
+ message: 'Parameters name and environment not provided',
54
+ });
32
55
  return;
33
56
  }
57
+ // pcga11: Hoisted so the catch block can shut down providers whose batch/periodic
58
+ // export timers started in their constructors, even when init fails midway.
59
+ let tracerProvider;
60
+ let meterProvider;
34
61
  try {
35
62
  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.
63
+ // pcga11: The context manager is what makes the "current" context (active span, baggage)
64
+ // follow execution across await/callback boundaries, via Node's AsyncLocalStorage. Without
65
+ // one registered, spans would not nest and trace/log correlation would break. Registration
66
+ // is first-wins: if another SDK already installed one, we keep theirs and disable ours.
39
67
  const contextManager = new context_async_hooks_1.AsyncLocalStorageContextManager();
40
68
  contextManager.enable();
41
69
  if (!api_1.context.setGlobalContextManager(contextManager)) {
@@ -46,10 +74,10 @@ enabled = true, ...options }) {
46
74
  new core_1.W3CTraceContextPropagator(),
47
75
  new core_1.W3CBaggagePropagator(),
48
76
  ],
49
- })));
77
+ })) ? state_js_1.SignalSources.global : state_js_1.SignalSources.none);
50
78
  // 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
79
  // 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({
80
+ tracerProvider = new sdk_trace_base_1.BasicTracerProvider({
53
81
  resource,
54
82
  sampler: new sdk_trace_base_1.ParentBasedSampler({
55
83
  root: new sdk_trace_base_1.TraceIdRatioBasedSampler(sampleRate),
@@ -59,7 +87,7 @@ enabled = true, ...options }) {
59
87
  ...(options.additionalSpanProcessors ?? []),
60
88
  ],
61
89
  });
62
- const meterProvider = new sdk_metrics_1.MeterProvider({
90
+ meterProvider = new sdk_metrics_1.MeterProvider({
63
91
  resource,
64
92
  readers: [
65
93
  new sdk_metrics_1.PeriodicExportingMetricReader({
@@ -77,44 +105,45 @@ enabled = true, ...options }) {
77
105
  ...(options.additionalLogRecordProcessors ?? []),
78
106
  ],
79
107
  });
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);
108
+ (0, logs_js_1.setFoamLoggerProvider)(loggerProvider);
109
+ // 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 record the source: "global" when Foam owns the slot, "none" when another SDK does. Logs fall back to "local" because the Foam-only provider behind log() exists either way.
110
+ (0, state_js_1.setSignal)(state_js_1.Signals.traces, api_1.trace.setGlobalTracerProvider(tracerProvider) ? state_js_1.SignalSources.global : state_js_1.SignalSources.none);
111
+ (0, state_js_1.setSignal)(state_js_1.Signals.metrics, api_1.metrics.setGlobalMeterProvider(meterProvider) ? state_js_1.SignalSources.global : state_js_1.SignalSources.none);
112
+ (0, state_js_1.setSignal)(state_js_1.Signals.logs, api_logs_1.logs.setGlobalLoggerProvider(loggerProvider) === loggerProvider ? state_js_1.SignalSources.global : state_js_1.SignalSources.local);
84
113
  const instrumentations = (0, instrumentations_js_1.buildInstrumentations)(networkCapture, disableLogSending, options.ignoredOutboundHosts, options.additionalInstrumentations ?? []);
114
+ // pcga11: registerInstrumentations turns on instrumentation "hooks". OpenTelemetry wraps supported libraries when the app imports them.
85
115
  (0, instrumentation_1.registerInstrumentations)({ instrumentations });
86
116
  (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
117
  process.once("beforeExit", () => {
89
118
  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,
119
+ (0, state_js_1.getSignal)(state_js_1.Signals.traces) === state_js_1.SignalSources.global ? tracerProvider?.shutdown() : undefined,
120
+ (0, state_js_1.getSignal)(state_js_1.Signals.metrics) === state_js_1.SignalSources.global ? meterProvider?.shutdown() : undefined,
121
+ loggerProvider.shutdown(),
93
122
  ]).catch(() => undefined);
94
123
  });
95
124
  (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,
125
+ (0, report_js_1.report)({
100
126
  token: options.token,
101
- tier: "internal",
102
127
  severity: api_logs_1.SeverityNumber.INFO,
128
+ message: 'SDK initialized',
103
129
  });
104
130
  }
105
131
  catch (error) {
106
132
  const errorMessage = error instanceof Error ? error.message : String(error);
107
133
  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);
134
+ (0, state_js_1.setSignal)(signal, state_js_1.SignalSources.none);
109
135
  }
136
+ void Promise.all([
137
+ tracerProvider?.shutdown(),
138
+ meterProvider?.shutdown(),
139
+ (0, logs_js_1.getFoamLoggerProvider)()?.shutdown(),
140
+ ]).catch(() => undefined);
141
+ (0, logs_js_1.clearFoamLoggerProvider)();
110
142
  (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,
143
+ (0, report_js_1.report)({
115
144
  token: options.token,
116
- tier: "internal",
117
145
  severity: api_logs_1.SeverityNumber.ERROR,
146
+ message: `SDK initialization failed: ${errorMessage}`,
118
147
  error: errorMessage,
119
148
  });
120
149
  }
@@ -5,11 +5,33 @@ exports.buildInstrumentations = buildInstrumentations;
5
5
  const auto_instrumentations_node_1 = require("@opentelemetry/auto-instrumentations-node");
6
6
  const instrumentation_console_1 = require("@opentelemetry/instrumentation-console");
7
7
  const index_js_1 = require("./network-capture/index.js");
8
+ const endpoint_js_1 = require("./endpoint.js");
8
9
  const state_js_1 = require("./state.js");
9
10
  const constants_js_1 = require("./constants.js");
10
11
  function buildInstrumentations(networkCapture = "basic", disableLogSending = false, ignoredOutboundHosts, additionalInstrumentations) {
11
- const bodyCaptureEnabled = (0, state_js_1.getSignal)(state_js_1.Signals.logs);
12
+ // pcga11: Captured bodies are emitted through the global logs pipeline. Only enable
13
+ // capture when that pipeline delivers to Foam ("global"/"ingest").
14
+ // If another SDK owns the slot (aka we have "local" for logs), capturing would be done for other vendors instead of Foam which might be non-compliant.
15
+ // If it's "none", capturing does not work at all.
16
+ const logsSource = (0, state_js_1.getSignal)(state_js_1.Signals.logs);
17
+ const bodyCaptureEnabled = logsSource === state_js_1.SignalSources.global || logsSource === state_js_1.SignalSources.ingest;
12
18
  const capturedHeaders = networkCapture === "off" ? undefined : [...constants_js_1.SAFE_NETWORK_HEADERS];
19
+ // pcga11: Skip Foam's OTLP export HTTP calls so they do not become client spans
20
+ // TODO(pcga11): This could get real interesting: could we use these calls to verify coverage? IDK maybe tarpid
21
+ const ignoredHosts = new Set([
22
+ new URL(endpoint_js_1.endpoint).hostname,
23
+ ...(ignoredOutboundHosts ?? []),
24
+ ]);
25
+ const ignoreHttpOutgoingRequest = ((request) => typeof request.hostname === "string" &&
26
+ ignoredHosts.has(request.hostname));
27
+ const ignoreUndiciRequest = ((request) => {
28
+ try {
29
+ return ignoredHosts.has(new URL(request.origin).hostname);
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ });
13
35
  const bodyHooks = networkCapture === "advanced"
14
36
  ? (0, index_js_1.createNetworkCaptureHooks)(bodyCaptureEnabled)
15
37
  : undefined;
@@ -36,8 +58,7 @@ function buildInstrumentations(networkCapture = "basic", disableLogSending = fal
36
58
  }
37
59
  : {}),
38
60
  ...(bodyHooks ?? {}),
39
- ignoreOutgoingRequestHook: ((request) => typeof request.hostname === "string" &&
40
- (ignoredOutboundHosts ?? []).includes(request.hostname)),
61
+ ignoreOutgoingRequestHook: ignoreHttpOutgoingRequest,
41
62
  },
42
63
  "@opentelemetry/instrumentation-openai": {
43
64
  captureMessageContent: networkCapture === "advanced",
@@ -55,6 +76,7 @@ function buildInstrumentations(networkCapture = "basic", disableLogSending = fal
55
76
  }
56
77
  : {}),
57
78
  ...(advancedUndiciHooks ?? {}),
79
+ ignoreRequestHook: ignoreUndiciRequest,
58
80
  },
59
81
  "@opentelemetry/instrumentation-winston": {
60
82
  disableLogSending: disableLogSending === true,
package/dist/logs.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { Attributes } from "@opentelemetry/api";
2
+ import { SeverityNumber } from "@opentelemetry/api-logs";
3
+ import { LoggerProvider } from "@opentelemetry/sdk-logs";
4
+ export declare function setFoamLoggerProvider(provider: LoggerProvider): void;
5
+ export declare function getFoamLoggerProvider(): LoggerProvider | undefined;
6
+ export declare function clearFoamLoggerProvider(): void;
7
+ export declare function ensureFoamLoggerProvider(name: string, environment: string, token: string, stamp?: Attributes): LoggerProvider;
8
+ export declare const log: (body: string, severityNumber?: SeverityNumber, attributes?: Attributes) => void;
9
+ export { SeverityNumber };
package/dist/logs.js ADDED
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SeverityNumber = exports.log = void 0;
4
+ exports.setFoamLoggerProvider = setFoamLoggerProvider;
5
+ exports.getFoamLoggerProvider = getFoamLoggerProvider;
6
+ exports.clearFoamLoggerProvider = clearFoamLoggerProvider;
7
+ exports.ensureFoamLoggerProvider = ensureFoamLoggerProvider;
8
+ const api_logs_1 = require("@opentelemetry/api-logs");
9
+ Object.defineProperty(exports, "SeverityNumber", { enumerable: true, get: function () { return api_logs_1.SeverityNumber; } });
10
+ const sdk_logs_1 = require("@opentelemetry/sdk-logs");
11
+ const constants_js_1 = require("./constants.js");
12
+ const exporters_js_1 = require("./exporters.js");
13
+ const resource_js_1 = require("./resource.js");
14
+ const utils_js_1 = require("./utils.js");
15
+ let foamLoggerProvider;
16
+ function setFoamLoggerProvider(provider) {
17
+ if (foamLoggerProvider && foamLoggerProvider !== provider) {
18
+ void foamLoggerProvider.shutdown().catch(() => undefined);
19
+ }
20
+ foamLoggerProvider = provider;
21
+ }
22
+ function getFoamLoggerProvider() {
23
+ return foamLoggerProvider;
24
+ }
25
+ function clearFoamLoggerProvider() {
26
+ foamLoggerProvider = undefined;
27
+ }
28
+ function ensureFoamLoggerProvider(name, environment, token, stamp) {
29
+ const existing = foamLoggerProvider;
30
+ if (existing) {
31
+ return existing;
32
+ }
33
+ const provider = new sdk_logs_1.LoggerProvider({
34
+ resource: (0, resource_js_1.createFoamResource)(name, environment),
35
+ processors: [
36
+ new sdk_logs_1.BatchLogRecordProcessor({
37
+ exporter: new exporters_js_1.FoamLogExporter(token, stamp),
38
+ }),
39
+ ],
40
+ });
41
+ setFoamLoggerProvider(provider);
42
+ process.once("beforeExit", () => {
43
+ void provider.shutdown().catch(() => undefined);
44
+ });
45
+ return provider;
46
+ }
47
+ const log = (body, severityNumber = api_logs_1.SeverityNumber.INFO, attributes) => {
48
+ const provider = foamLoggerProvider;
49
+ if (!provider) {
50
+ return;
51
+ }
52
+ (0, utils_js_1.safely)(() => {
53
+ provider.getLogger(constants_js_1.FOAM_DISTRO_NAME, constants_js_1.FOAM_DISTRO_VERSION).emit({
54
+ body,
55
+ severityNumber,
56
+ severityText: constants_js_1.SEVERITY_TEXT[severityNumber],
57
+ attributes,
58
+ });
59
+ });
60
+ };
61
+ exports.log = log;