@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
package/README.md CHANGED
@@ -1,123 +1,586 @@
1
- # @foam-ai/node
1
+ # Foam OpenTelemetry SDK for Node.js
2
2
 
3
- Foam observability SDK for Node.js. Sends traces, logs, and metrics to Foam with zero configuration overhead.
3
+ `@foam-ai/node` is Foam's OpenTelemetry distribution for Node.js.
4
+
5
+ ## Requirements
6
+
7
+ - Node.js `^18.19.0 || >=20.6.0`, matching the supported engine range of
8
+ `@opentelemetry/auto-instrumentations-node`
9
+ - Foam ingest token, stored as `FOAM_OTEL_TOKEN`
4
10
 
5
11
  ## Install
6
12
 
7
- ```bash
13
+ ```sh
8
14
  npm install @foam-ai/node
9
15
  ```
10
16
 
11
- ## Quick start
17
+ ## Initialize
18
+
19
+ Call `init()` before your app loads so OpenTelemetry can patch libraries as
20
+ they import. Put `init()` in its own file and preload it with `--import` or
21
+ `--require` (below).
22
+
23
+ Set `enabled: false` to turn Foam off.
24
+
25
+ Store the ingest token as `FOAM_OTEL_TOKEN` and pass
26
+ `process.env.FOAM_OTEL_TOKEN` to `init()`.
27
+
28
+ ### TypeScript
29
+
30
+ Create `instrumentation.ts`:
31
+
32
+ ```ts
33
+ import { init } from "@foam-ai/node";
34
+
35
+ init({
36
+ name: "checkout-api",
37
+ environment: "production",
38
+ enabled: true,
39
+ token: process.env.FOAM_OTEL_TOKEN,
40
+ version: "1.0.0",
41
+ });
42
+ ```
43
+
44
+ Compile it with the app. If TypeScript emits ESM, preload with `--import`
45
+ (same rule as ESM below):
46
+
47
+ ```sh
48
+ node --import ./dist/instrumentation.js ./dist/app.js
49
+ ```
50
+
51
+ If TypeScript emits CommonJS, preload with `--require`:
52
+
53
+ ```sh
54
+ node --require ./dist/instrumentation.js ./dist/app.js
55
+ ```
56
+
57
+ To run TypeScript without compiling first:
58
+
59
+ ```sh
60
+ node --import tsx --import ./instrumentation.ts ./src/app.ts
61
+ ```
62
+
63
+ ### ESM
64
+
65
+ Create `instrumentation.mjs` with the same `init()` call.
66
+
67
+ `import` lines always run first, even if you write `init()` above them.
68
+ Don't import your app from that file. Use `--import`, or
69
+ `await import("./app.js")` after `init()`:
70
+
71
+ ```sh
72
+ node --import ./instrumentation.mjs ./app.js
73
+ ```
74
+
75
+ ### CommonJS
76
+
77
+ Create `instrumentation.cjs`. `require()` runs in order, so `init()` then
78
+ `require("./app")` in this file is fine. `--require` is still the better
79
+ preload:
80
+
81
+ ```js
82
+ const { init } = require("@foam-ai/node");
83
+
84
+ init({
85
+ name: "checkout-api",
86
+ environment: "production",
87
+ enabled: true,
88
+ token: process.env.FOAM_OTEL_TOKEN,
89
+ version: "1.0.0",
90
+ });
91
+ ```
92
+
93
+ ```sh
94
+ node --require ./instrumentation.cjs ./app.js
95
+ ```
96
+
97
+ ## Public API
98
+
99
+ ### `init(options)`
100
+
101
+ Sets up tracing, metrics, logs, auto-instrumentation, OTLP export to Foam,
102
+ and W3C context propagation.
103
+
104
+ Call this once. Use it when Foam should run OpenTelemetry for the process.
105
+
106
+ ```js
107
+ import { init } from "@foam-ai/node";
108
+
109
+ init({
110
+ name: "checkout-api",
111
+ environment: "production",
112
+ enabled: true,
113
+ token: process.env.FOAM_OTEL_TOKEN,
114
+ version: "1.0.0",
115
+ });
116
+ ```
117
+
118
+ ```ts
119
+ init({
120
+ name,
121
+ environment,
122
+ enabled,
123
+ token,
124
+ endpoint?,
125
+ version?,
126
+ sampleRate?,
127
+ additionalInstrumentations?,
128
+ additionalSpanProcessors?,
129
+ additionalLogRecordProcessors?,
130
+ additionalMetricReaders?,
131
+ additionalResourceAttributes?,
132
+ disableLogSending?,
133
+ networkCapture?,
134
+ ignoredOutboundHosts?,
135
+ redact?,
136
+ beforeSend?,
137
+ }): void
138
+ ```
139
+
140
+ `name`, `environment`, and `enabled` are required.
141
+ `token` is required only when Foam is enabled; store it as `FOAM_OTEL_TOKEN`.
142
+
143
+ `endpoint` defaults to Foam's OTLP endpoint and is where all telemetry is
144
+ delivered. Override it only to route through a Foam-compatible OTLP
145
+ gateway, such as an egress proxy or a local collector. It must be an
146
+ `http(s)` URL; its host is always excluded from outbound tracing so
147
+ export calls do not become client spans.
148
+
149
+ `version` sets `service.version`.
150
+
151
+ `sampleRate` is a number from `0` to `1` and defaults to `1`. Rarely if ever updated. Only change it if you are sending telemetry in the petabytes range.
152
+
153
+ `additionalInstrumentations` adds extra OpenTelemetry instrumentations next
154
+ to Foam's Node auto bundle and `ConsoleInstrumentation`.
155
+
156
+ `additionalSpanProcessors`, `additionalLogRecordProcessors`, and
157
+ `additionalMetricReaders` add extra processors/readers from other vendors next to Foam's
158
+ exporters. Note: Never add Foam's own processors/readers helpers.
159
+
160
+ `additionalResourceAttributes` adds extra resource attributes (for example
161
+ `team` or `cloud.region`) to all telemetry.
162
+
163
+ `disableLogSending` defaults to `false`. Set `true` to stop copying logger
164
+ records into Foam (see Loggers).
165
+
166
+ `networkCapture` defaults to `"basic"`. Upgrade to `"advanced"` unless you
167
+ cannot; it gives much more powerful HTTP and Undici capture. See Network capture.
168
+
169
+ `redact` and `beforeSend` are accepted but **not implemented yet**; passing
170
+ them logs a warning and they are ignored. `redact` will add extra
171
+ sensitive keys (strings or RegExps) to the built-in body redaction list.
172
+ `beforeSend` will run on every captured body before it leaves the
173
+ process, returning the (possibly edited) text or `null` to drop the
174
+ capture. Until they ship, rely on the built-in redaction (see Network
175
+ capture).
176
+
177
+ `ignoredOutboundHosts` skips outbound HTTP tracing for those hostnames.
178
+ The configured `endpoint` host is always skipped so export calls do not
179
+ become client spans. Add extra hosts for high-volume clients that would drown traces,
180
+ for example a sidecar health-check host (`localhost`) or another vendor's
181
+ OTLP ingest host you already export to.
182
+
183
+ TODO(pcga11): Add other common Gen AI providers.
184
+
185
+ If another SDK already registered traces, metrics, logs, or the propagator,
186
+ `init()` leaves the signal's slot untouched and only takes what is available. When a slot is taken, use its ingest helper instead.
187
+
188
+ ### `createFoamIngestSpanProcessor(name, environment, token)`
189
+
190
+ Returns a span processor that exports traces to Foam without taking over
191
+ the global TracerProvider.
192
+
193
+ Use this when another OpenTelemetry SDK already owns traces and you only
194
+ need Foam as an extra export destination.
195
+
196
+ ```js
197
+ import { createFoamIngestSpanProcessor } from "@foam-ai/node";
198
+
199
+ const spanProcessor = createFoamIngestSpanProcessor(
200
+ "checkout-api",
201
+ "production",
202
+ process.env.FOAM_OTEL_TOKEN,
203
+ );
204
+ tracerProvider.addSpanProcessor(spanProcessor);
205
+ ```
206
+
207
+ ### `createFoamIngestLogRecordProcessor(name, environment, token)`
208
+
209
+ Returns a log-record processor that exports logs to Foam without taking over
210
+ the global LoggerProvider.
211
+
212
+ Use this when another SDK already owns logs and you want those records in
213
+ Foam.
214
+
215
+ ```js
216
+ import { createFoamIngestLogRecordProcessor } from "@foam-ai/node";
217
+
218
+ const logRecordProcessor = createFoamIngestLogRecordProcessor(
219
+ "checkout-api",
220
+ "production",
221
+ process.env.FOAM_OTEL_TOKEN,
222
+ );
223
+ loggerProvider.addLogRecordProcessor(logRecordProcessor);
224
+ ```
225
+
226
+ ### `createFoamIngestMetricReader(name, environment, token)`
227
+
228
+ Returns a metric reader that exports metrics to Foam without taking over
229
+ the global MeterProvider.
230
+
231
+ Use this when another SDK already owns metrics and you want those meters in
232
+ Foam.
233
+
234
+ ```js
235
+ import { createFoamIngestMetricReader } from "@foam-ai/node";
236
+
237
+ const metricReader = createFoamIngestMetricReader(
238
+ "checkout-api",
239
+ "production",
240
+ process.env.FOAM_OTEL_TOKEN,
241
+ );
242
+ meterProvider.addMetricReader(metricReader);
243
+ ```
244
+
245
+ If the other SDK already set `service.name` or environment,
246
+ Foam's values overwrite them on that copy only. The original spans, logs,
247
+ and metrics are left unchanged. For the Foam SDK, ensure all of the signals (spans, logs and metrics)
248
+ carry identical `service.name`.
249
+
250
+ Don't use `init()` and an ingest helper for the same signal. If another
251
+ SDK already owns traces (or another signal), call `init()` for the rest
252
+ and attach ingest only to the provider you don't own. Don't pass ingest
253
+ helpers as `additionalSpanProcessors` or extra readers: `init()` already
254
+ installs Foam exporters.
12
255
 
13
- Call `init()` as early as possible in your application entry point:
256
+ Ingest helpers do not register instrumentations. If you cannot call
257
+ `init()`, register them with the official OpenTelemetry API
258
+ before the app imports those libraries:
14
259
 
15
- ```typescript
16
- import foam from '@foam-ai/node';
260
+ ```js
261
+ import { registerInstrumentations } from "@opentelemetry/instrumentation";
262
+ import { RedisInstrumentation } from "@opentelemetry/instrumentation-redis";
263
+ import { createFoamIngestSpanProcessor } from "@foam-ai/node";
17
264
 
18
- foam.init({
19
- apiKey: process.env.FOAM_API_KEY,
20
- serviceName: 'my-service',
21
- isProduction: process.env.NODE_ENV === 'production',
265
+ registerInstrumentations({
266
+ instrumentations: [new RedisInstrumentation()],
22
267
  });
23
268
  ```
24
269
 
25
- That's it. In production, Foam will automatically collect:
270
+ Attach the ingest helper to the other SDK's provider as in the examples
271
+ above. If that SDK already instruments the same library, don't register a
272
+ second copy.
273
+
274
+ OpenTelemetry allows only one global propagator per signal. Foam registers W3C Trace
275
+ Context and W3C Baggage (`traceparent`, `tracestate`, `baggage`), not B3,
276
+ Jaeger, or AWS X-Ray. Auto-instrumented HTTP uses whichever propagator
277
+ won. If another SDK already registered a propagator, Foam keeps it and
278
+ `getState().signals.baggage` is `false`. A non-W3C propagator will not
279
+ send `traceparent`, so a Foam peer starts a new trace. Foam does not add
280
+ W3C headers on top.
281
+
282
+ A work around is to initialize Foam first, or include W3C in the other
283
+ SDK's composite, if those headers must be on HTTP.
284
+
285
+ TODO(pcga11): Handle traceparent conflict^
286
+
287
+ ### `injectTraceContext(headers)`
288
+
289
+ Writes W3C `traceparent`, `tracestate`, and `baggage` onto a header map.
290
+
291
+ Use this when you send a message yourself (Kafka, a queue, a custom
292
+ socket). Auto-instrumented HTTP, Express, Undici, and Fetch already inject
293
+ these headers; don't call this on ordinary HTTP. This uses Foam's local
294
+ W3C propagator so it works even if Foam did not get the global one.
295
+
296
+ ```js
297
+ const headers = {};
298
+ injectTraceContext(headers);
299
+ await kafka.send({ value: payload, headers });
300
+ ```
301
+
302
+ ### `extractTraceContext(headers)`
303
+
304
+ Reads W3C trace and baggage headers and returns a parent OpenTelemetry
305
+ context.
306
+
307
+ Use this when you receive a message on a custom transport so the tracing carries over. Missing to extact resets the context on these headers effectively losing the context previously injected.
308
+
309
+ ```js
310
+ import { context } from "@opentelemetry/api";
311
+
312
+ const parent = extractTraceContext(message.headers);
313
+ await context.with(parent, () => handle(message));
314
+ ```
315
+
316
+ ### `setBaggage(key, value)`
317
+
318
+ Stores a small string on the current request context.
319
+
320
+ Use this for request-scoped identity (tenant, user id) that should follow
321
+ the request across `await`s without being passed through every function.
322
+
323
+ Values do not leak to other concurrent requests. Caveat: auto-instrumented HTTP
324
+ does not send these keys until you call `injectTraceContext`.
325
+
326
+ ```js
327
+ setBaggage("tenant.id", "acme");
328
+ ```
329
+
330
+ ### `getBaggage(key)`
331
+
332
+ Returns a baggage value from the current request, or `undefined`.
333
+
334
+ Use this later in the same request to read a value set with `setBaggage`,
335
+ or a key that arrived on an inbound `baggage` header.
336
+
337
+ ```js
338
+ const tenant = getBaggage("tenant.id"); // "acme"
339
+ ```
340
+
341
+ ### `incrementCounter(name, value?, attributes?, options?)`
26
342
 
27
- - **Traces** distributed traces via OpenTelemetry
28
- - **Logs** — console.log/info/error/warn/debug output
29
- - **Metrics** — runtime and custom metrics
30
- - **Uncaught errors** — process-level `uncaughtException` and `unhandledRejection`
343
+ Creates the counter if it does not exist, then increments it.
31
344
 
32
- In non-production environments, `init()` is a silent no-op.
345
+ This counter can only increases. Use this for event counts.
33
346
 
34
- ## Capturing errors
347
+ ```js
348
+ incrementCounter("orders.created", 1, { "cloud.region": "us-west-1" });
349
+ ```
35
350
 
36
- Unhandled exceptions and rejections are captured automatically. For caught errors you want to report, use `captureException`:
351
+ ### `recordHistogram(name, value, attributes?, options?)`
37
352
 
38
- ```typescript
39
- import { captureException } from '@foam-ai/node';
353
+ Creates the histogram if it does not exist, then records a measurement.
354
+
355
+ Use this for values you want percentiles or averages of: latency, duration, payload size.
356
+
357
+ ```js
358
+ recordHistogram("checkout.duration", 0.142, undefined, { unit: "s" });
359
+ ```
360
+
361
+ ### `addUpDownCounter(name, value, attributes?, options?)`
362
+
363
+ Creates the up-down counter if it does not exist, then adds a signed
364
+ delta. The value can go up or down.
365
+
366
+ Use this for occupancy: active jobs, open connections, items in a pool.
367
+
368
+ In other terms, pass +value when something starts occupying a slot, -value when it leaves. The metric is the current state of occupancy.
369
+
370
+ ```js
371
+ addUpDownCounter("jobs.active", -1);
372
+ ```
373
+
374
+ ### `setMetric(name, value, attributes?, options?)`
375
+
376
+ Creates the gauge if it does not exist, then sets its current value.
377
+
378
+ Use this for a point-in-time level: queue depth, heap used, cache size.
379
+
380
+ ```js
381
+ setMetric("queue.depth", 27);
382
+ ```
383
+
384
+ TODO(pcga11): Investigate if it's worth support observable (pull) instruments API.
385
+
386
+ ### `log(body, severity?, attributes?)`
387
+
388
+ Sends a log record to Foam on Foam's own export path. It does not write to the app's logger, and it does not emit on a LoggerProvider Foam does
389
+ not own.
390
+
391
+ Use this to send logs directly. Typically used for Pino 5–6, Winston 1–2 as they do not support sending logs.
392
+
393
+ ```js
394
+ import { log, SeverityNumber } from "@foam-ai/node";
395
+
396
+ log("checkout failed");
397
+ log("checkout failed", SeverityNumber.ERROR);
398
+ ```
399
+
400
+ ### `recordException(error)`
401
+
402
+ Records an exception on the current active span and sets the span status
403
+ to `ERROR`. Accepts an `Error` or any value (stringified). Never throws;
404
+ does nothing when no span is active or the span is not recording.
405
+
406
+ Use this in `catch` blocks inside auto-instrumented handlers (HTTP
407
+ routes, message consumers) where you did not create the span yourself.
408
+ The exception lands on whichever SDK owns the active span, so it works
409
+ even when another SDK owns tracing.
410
+
411
+ ```js
412
+ import { recordException } from "@foam-ai/node";
40
413
 
41
414
  try {
42
- await chargeCustomer(order);
415
+ await chargeCard(order);
43
416
  } catch (err) {
44
- captureException(err);
45
- return res.status(500).json({ error: 'Payment failed' });
417
+ recordException(err);
418
+ throw err;
46
419
  }
47
420
  ```
48
421
 
49
- `captureException` works with or without an active trace span. It always emits an OpenTelemetry log record so errors reach Foam regardless of instrumentation setup.
422
+ ### `getState()`
423
+
424
+ Returns whether Foam initialized, which instrumentations registered, and
425
+ which signals have an export path.
426
+
427
+ Use this in health checks, tests, or diagnostics to confirm Foam is
428
+ running.
429
+
430
+ ```js
431
+ import { getState } from "@foam-ai/node";
432
+
433
+ getState();
434
+ // {
435
+ // initialized: true,
436
+ // instrumentations: ["http", "express", "pg", ...],
437
+ // signals: {
438
+ // traces: "global",
439
+ // metrics: "global",
440
+ // logs: "global",
441
+ // baggage: "global",
442
+ // profile: "none",
443
+ // },
444
+ // }
445
+ ```
50
446
 
51
- ## Auto-instrumentation (optional)
447
+ `instrumentations` lists only the instrumentations registered with a
448
+ successfully started Foam SDK, after upstream defaults and
449
+ `OTEL_NODE_ENABLED_INSTRUMENTATIONS` /
450
+ `OTEL_NODE_DISABLED_INSTRUMENTATIONS` filtering. Registered means the
451
+ instrumentation is ready to patch a supported module when that module
452
+ loads.
52
453
 
53
- By default, no auto-instrumentations are registered. This keeps the SDK webpack/Turbopack-safe with no bundler configuration needed.
454
+ Each `signals` value names the source of the Foam export path for that
455
+ signal:
54
456
 
55
- For plain Node.js servers (Express, Fastify, etc.), you can opt into automatic HTTP, DNS, and framework instrumentation:
457
+ - `"global"` `init()` registered Foam's provider in the global
458
+ OpenTelemetry slot.
459
+ - `"ingest"` — a `createFoamIngest*` processor/reader was constructed for
460
+ another SDK's pipeline. Foam cannot tell whether you actually registered
461
+ the returned object.
462
+ - `"local"` — logs only: another SDK owns the global LoggerProvider, but
463
+ Foam keeps its own local provider so `log()` still delivers to Foam.
464
+ - `"none"` — no Foam export path.
56
465
 
57
- ```typescript
58
- import foam from '@foam-ai/node';
59
- import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
466
+ A value other than `"none"` means telemetry is expected, not that the app
467
+ has already produced or exported it.
60
468
 
61
- foam.init({
62
- apiKey: process.env.FOAM_API_KEY,
63
- serviceName: 'my-api',
64
- isProduction: process.env.NODE_ENV === 'production',
65
- instrumentations: getNodeAutoInstrumentations(),
66
- });
67
- ```
469
+ ## Custom spans and other OpenTelemetry APIs
68
470
 
69
- ```bash
70
- npm install @opentelemetry/auto-instrumentations-node
471
+ Foam does not wrap span creation. To start your own spans, read the
472
+ current span, or work with context, import `@opentelemetry/api` directly.
473
+ `init()` registers Foam's providers in the global OpenTelemetry slots, so
474
+ the official API routes to Foam automatically.
475
+
476
+ Install the API in your app so it resolves one compatible `1.x` copy:
477
+
478
+ ```sh
479
+ npm install @opentelemetry/api
71
480
  ```
72
481
 
73
- ## Next.js
482
+ ```js
483
+ import { trace, SpanStatusCode } from "@opentelemetry/api";
74
484
 
75
- In a Next.js app, create an `instrumentation.ts` file at the project root:
485
+ const tracer = trace.getTracer("checkout");
76
486
 
77
- ```typescript
78
- export async function register() {
79
- if (process.env.NEXT_RUNTIME === 'nodejs') {
80
- const foam = await import('@foam-ai/node');
81
- foam.init({
82
- apiKey: process.env.FOAM_API_KEY!,
83
- serviceName: 'my-nextjs-app',
84
- isProduction: process.env.NODE_ENV === 'production',
85
- });
487
+ await tracer.startActiveSpan("charge-card", async (span) => {
488
+ try {
489
+ await chargeCard(order);
490
+ } catch (err) {
491
+ span.recordException(err);
492
+ span.setStatus({ code: SpanStatusCode.ERROR });
493
+ throw err;
494
+ } finally {
495
+ span.end();
86
496
  }
87
- }
497
+ });
88
498
  ```
89
499
 
90
- No webpack externals or bundler config needed.
500
+ Prefer `startActiveSpan` over `startSpan`: it makes the span current, so
501
+ auto-instrumented HTTP/database spans and nested custom spans parent under
502
+ it. Always call `span.end()` (the `finally` above), or the span never
503
+ exports. When you did not create the span yourself, Foam's
504
+ `recordException(err)` does the exception-plus-ERROR-status pair on the
505
+ current active span for you.
91
506
 
92
- ## Coexistence with other SDKs
507
+ The same applies to the rest of the API: `trace.getActiveSpan()` to tag
508
+ the current request (`span.setAttribute(...)`), and `context.with(...)`
509
+ to run code under a specific context.
93
510
 
94
- Foam detects each OpenTelemetry signal independently. If another SDK (Sentry, @vercel/otel, Datadog, etc.) has already registered a TracerProvider, LoggerProvider, or MeterProvider, Foam attaches its exporters on top rather than replacing them. Both SDKs receive data.
511
+ This is safe in every state: if another SDK owns the global tracer slot,
512
+ these spans go to that SDK; before `init()` (or when Foam is disabled)
513
+ the API returns a no-op tracer and nothing is recorded. Custom spans
514
+ export wherever `getState().signals.traces` points.
95
515
 
96
- If no providers exist, Foam creates its own. This means Foam works in any combination:
516
+ ## Network capture
97
517
 
98
- - Foam alone
99
- - Foam + Sentry
100
- - Foam + @vercel/otel
101
- - Foam + any OpenTelemetry-compatible SDK
518
+ `networkCapture` defaults to `"basic"` and controls extra HTTP detail.
102
519
 
103
- ## API
520
+ - `"off"` keeps standard OpenTelemetry HTTP/Undici telemetry, with no extra
521
+ capture.
522
+ - `"basic"` adds allowlisted `content-type`, `content-length`, and
523
+ `content-encoding` headers.
524
+ - `"advanced"` adds those headers plus full Node `http`/`https` and
525
+ Fetch/Undici request and response bodies as raw wire bytes, up to 1 MiB. Always set to `"advanced"` when security and compliance allows.
104
526
 
105
- ### `init(options)`
106
527
 
107
- | Option | Type | Required | Description |
108
- |---|---|---|---|
109
- | `apiKey` | `string` | Yes | Your Foam API key |
110
- | `serviceName` | `string` | Yes | Name of your service (appears in Foam dashboard) |
111
- | `isProduction` | `boolean` | Yes | Set to `true` to enable telemetry. `false` = no-op. |
112
- | `instrumentations` | `InstrumentationBase[]` | No | OpenTelemetry instrumentations to register |
528
+ Headers are captured from an allowlist only, so sensitive headers such as
529
+ `authorization` and `cookie` never leave the process. Captured JSON and
530
+ form-urlencoded bodies are redacted before emission: values of sensitive
531
+ keys (`password`, `token`, `api_key`, `client_secret`, `ssn`, and similar,
532
+ matched case-insensitively across `-`, `_`, and `.` separators) are
533
+ replaced with `[REDACTED]` on both the span body content and the emitted
534
+ body chunks. When redaction changes a compressed body, the chunks carry
535
+ the redacted text instead of the raw wire bytes. Other text and binary
536
+ media types are captured unmodified.
537
+
538
+ ## Loggers
539
+
540
+ Foam follows the compatibility ranges of its OpenTelemetry logger
541
+ instrumentations:
542
+
543
+ - Bunyan `>=1 <2`
544
+ - Pino `>=5.14 <11`
545
+ - Winston `>=1 <4`
546
+
547
+ Initialize Foam before you import or construct the logger. Registration alone does not mean a logger
548
+ was loaded, patched, or produced records.
549
+
550
+ With `disableLogSending: false` (the default), Foam SDK sends:
551
+
552
+ - Bunyan 1 records through an added OTel stream
553
+ - Pino 7–10 records through an added main-thread `multistream`
554
+ - Winston 3 records through the bundled `OpenTelemetryTransportV3`
555
+
556
+ Pino 5–6 and Winston 1–2 still get correlation. But they have no sending
557
+ path (`pino.multistream` starts at 7; Winston transports start at 3), so Foam never receives a copy. Call `log()` to send a record yourself.
558
+
559
+ Set `disableLogSending: true` when the app already exports logger records
560
+ itself (a Bunyan OTel stream, `pino-opentelemetry-transport`, a Winston
561
+ OTel transport, or another pipeline). That turns off sending for Bunyan
562
+ 1, Pino 7–10, and Winston 3.
563
+
564
+ `ConsoleInstrumentation` turns calls to `console.*` into OTel log records.
565
+ It does not capture arbitrary stdout/stderr writes.
566
+
567
+ ```js
568
+ console.log("hi"); // captured
569
+ process.stdout.write("hi\n"); // not captured
570
+ ```
571
+
572
+ ## OpenTelemetry configuration and compliance
573
+
574
+ Foam uses the official OpenTelemetry JavaScript API, SDK, resource,
575
+ propagation, instrumentation, and OTLP exporter packages.
113
576
 
114
- ### `captureException(error)`
577
+ The [OpenTelemetry JavaScript compliance matrix](https://github.com/open-telemetry/opentelemetry-specification/blob/main/spec-compliance-matrix/js.yaml)
115
578
 
116
- Captures a caught error and sends it to Foam. Works with or without an active span.
579
+ ## TODO(pcga11): OpenTelemetry profiling
117
580
 
118
- ## Design
581
+ The official OpenTelemetry JavaScript SDK has no in-process Profiles provider, processor, or exporter yet. Track upstream progress:
119
582
 
120
- - **HTTP-only exporters** no gRPC, no Node.js built-in dependencies that break bundlers
121
- - **Zero latency** all setup is synchronous; exporting happens in background batch intervals
122
- - **Crash-safe** each signal (traces, logs, metrics, console, process handlers) is independently wrapped in try/catch
123
- - **Idempotent** safe to call `init()` multiple times; console patching and process handlers use Symbols to prevent double-registration
583
+ - [OpenTelemetry JS profiling implementation issue](https://github.com/open-telemetry/opentelemetry-js/issues/6500)
584
+ - [Profiling SIG language SDK support tracker](https://github.com/open-telemetry/sig-profiling/issues/106)
585
+ - [OpenTelemetry Profiles public alpha announcement](https://opentelemetry.io/blog/2026/profiles-alpha/)
586
+ - [OpenTelemetry eBPF profiler](https://github.com/open-telemetry/opentelemetry-ebpf-profiler)