@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.
- package/README.md +299 -183
- package/dist/constants.d.ts +9 -8
- package/dist/constants.js +49 -6
- package/dist/endpoint.d.ts +2 -0
- package/dist/endpoint.js +11 -0
- package/dist/exporters.js +4 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -1
- package/dist/ingest.js +15 -12
- package/dist/init.d.ts +3 -3
- package/dist/init.js +64 -35
- package/dist/instrumentations.js +25 -3
- package/dist/logs.d.ts +9 -0
- package/dist/logs.js +61 -0
- package/dist/network-capture/collector.js +96 -52
- package/dist/network-capture/http.d.ts +0 -1
- package/dist/network-capture/http.js +23 -25
- package/dist/network-capture/index.js +10 -0
- package/dist/network-capture/redact.d.ts +6 -0
- package/dist/network-capture/redact.js +87 -0
- package/dist/network-capture/undici.d.ts +0 -4
- package/dist/network-capture/undici.js +55 -22
- package/dist/otlp.d.ts +8 -0
- package/dist/otlp.js +46 -0
- package/dist/report.d.ts +9 -0
- package/dist/report.js +56 -0
- package/dist/state.d.ts +11 -4
- package/dist/state.js +26 -9
- package/dist/traces.d.ts +1 -0
- package/dist/traces.js +21 -0
- package/dist/utils.js +1 -1
- package/package.json +1 -1
- package/dist/diagnostics.d.ts +0 -13
- package/dist/diagnostics.js +0 -83
package/README.md
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# Foam OpenTelemetry SDK for Node.js
|
|
2
2
|
|
|
3
|
-
`@foam-ai/node` is Foam's OpenTelemetry distribution for Node.js.
|
|
4
|
-
the official OpenTelemetry JavaScript SDK, node automatic instrumentations, and
|
|
5
|
-
OTLP/HTTP exporters with one `init()` call.
|
|
3
|
+
`@foam-ai/node` is Foam's OpenTelemetry distribution for Node.js.
|
|
6
4
|
|
|
7
5
|
## Requirements
|
|
8
6
|
|
|
9
7
|
- Node.js `^18.19.0 || >=20.6.0`, matching the supported engine range of
|
|
10
|
-
|
|
11
|
-
-
|
|
8
|
+
`@opentelemetry/auto-instrumentations-node`
|
|
9
|
+
- Foam ingest token, stored as `FOAM_OTEL_TOKEN`
|
|
12
10
|
|
|
13
11
|
## Install
|
|
14
12
|
|
|
@@ -18,11 +16,14 @@ npm install @foam-ai/node
|
|
|
18
16
|
|
|
19
17
|
## Initialize
|
|
20
18
|
|
|
21
|
-
Call `init()` before
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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()`.
|
|
26
27
|
|
|
27
28
|
### TypeScript
|
|
28
29
|
|
|
@@ -40,13 +41,14 @@ init({
|
|
|
40
41
|
});
|
|
41
42
|
```
|
|
42
43
|
|
|
43
|
-
Compile it with the app. If TypeScript emits ESM
|
|
44
|
+
Compile it with the app. If TypeScript emits ESM, preload with `--import`
|
|
45
|
+
(same rule as ESM below):
|
|
44
46
|
|
|
45
47
|
```sh
|
|
46
48
|
node --import ./dist/instrumentation.js ./dist/app.js
|
|
47
49
|
```
|
|
48
50
|
|
|
49
|
-
If TypeScript emits CommonJS
|
|
51
|
+
If TypeScript emits CommonJS, preload with `--require`:
|
|
50
52
|
|
|
51
53
|
```sh
|
|
52
54
|
node --require ./dist/instrumentation.js ./dist/app.js
|
|
@@ -60,7 +62,11 @@ node --import tsx --import ./instrumentation.ts ./src/app.ts
|
|
|
60
62
|
|
|
61
63
|
### ESM
|
|
62
64
|
|
|
63
|
-
Create `instrumentation.mjs` with the same `init()` call
|
|
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()`:
|
|
64
70
|
|
|
65
71
|
```sh
|
|
66
72
|
node --import ./instrumentation.mjs ./app.js
|
|
@@ -68,7 +74,9 @@ node --import ./instrumentation.mjs ./app.js
|
|
|
68
74
|
|
|
69
75
|
### CommonJS
|
|
70
76
|
|
|
71
|
-
Create `instrumentation.cjs
|
|
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:
|
|
72
80
|
|
|
73
81
|
```js
|
|
74
82
|
const { init } = require("@foam-ai/node");
|
|
@@ -90,10 +98,10 @@ node --require ./instrumentation.cjs ./app.js
|
|
|
90
98
|
|
|
91
99
|
### `init(options)`
|
|
92
100
|
|
|
93
|
-
|
|
94
|
-
and
|
|
101
|
+
Sets up tracing, metrics, logs, auto-instrumentation, OTLP export to Foam,
|
|
102
|
+
and W3C context propagation.
|
|
95
103
|
|
|
96
|
-
Call this once when Foam
|
|
104
|
+
Call this once. Use it when Foam should run OpenTelemetry for the process.
|
|
97
105
|
|
|
98
106
|
```js
|
|
99
107
|
import { init } from "@foam-ai/node";
|
|
@@ -113,6 +121,7 @@ init({
|
|
|
113
121
|
environment,
|
|
114
122
|
enabled,
|
|
115
123
|
token,
|
|
124
|
+
endpoint?,
|
|
116
125
|
version?,
|
|
117
126
|
sampleRate?,
|
|
118
127
|
additionalInstrumentations?,
|
|
@@ -123,49 +132,66 @@ init({
|
|
|
123
132
|
disableLogSending?,
|
|
124
133
|
networkCapture?,
|
|
125
134
|
ignoredOutboundHosts?,
|
|
126
|
-
|
|
135
|
+
redact?,
|
|
136
|
+
beforeSend?,
|
|
127
137
|
}): void
|
|
128
138
|
```
|
|
129
139
|
|
|
130
140
|
`name`, `environment`, and `enabled` are required.
|
|
131
|
-
`token` is required only when enabled; store it as `FOAM_OTEL_TOKEN`.
|
|
132
|
-
|
|
133
|
-
`
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
`
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
Foam
|
|
145
|
-
|
|
146
|
-
`
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
`
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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.
|
|
161
187
|
|
|
162
188
|
### `createFoamIngestSpanProcessor(name, environment, token)`
|
|
163
189
|
|
|
164
|
-
Returns a span processor that exports traces to Foam without taking over
|
|
165
|
-
global TracerProvider.
|
|
190
|
+
Returns a span processor that exports traces to Foam without taking over
|
|
191
|
+
the global TracerProvider.
|
|
166
192
|
|
|
167
|
-
Use this when another OpenTelemetry SDK already owns traces and you only
|
|
168
|
-
Foam as an extra export destination.
|
|
193
|
+
Use this when another OpenTelemetry SDK already owns traces and you only
|
|
194
|
+
need Foam as an extra export destination.
|
|
169
195
|
|
|
170
196
|
```js
|
|
171
197
|
import { createFoamIngestSpanProcessor } from "@foam-ai/node";
|
|
@@ -183,7 +209,8 @@ tracerProvider.addSpanProcessor(spanProcessor);
|
|
|
183
209
|
Returns a log-record processor that exports logs to Foam without taking over
|
|
184
210
|
the global LoggerProvider.
|
|
185
211
|
|
|
186
|
-
Use this when another SDK already owns logs and you want those records in
|
|
212
|
+
Use this when another SDK already owns logs and you want those records in
|
|
213
|
+
Foam.
|
|
187
214
|
|
|
188
215
|
```js
|
|
189
216
|
import { createFoamIngestLogRecordProcessor } from "@foam-ai/node";
|
|
@@ -198,8 +225,8 @@ loggerProvider.addLogRecordProcessor(logRecordProcessor);
|
|
|
198
225
|
|
|
199
226
|
### `createFoamIngestMetricReader(name, environment, token)`
|
|
200
227
|
|
|
201
|
-
Returns a metric reader that exports metrics to Foam without taking over
|
|
202
|
-
global MeterProvider.
|
|
228
|
+
Returns a metric reader that exports metrics to Foam without taking over
|
|
229
|
+
the global MeterProvider.
|
|
203
230
|
|
|
204
231
|
Use this when another SDK already owns metrics and you want those meters in
|
|
205
232
|
Foam.
|
|
@@ -215,39 +242,56 @@ const metricReader = createFoamIngestMetricReader(
|
|
|
215
242
|
meterProvider.addMetricReader(metricReader);
|
|
216
243
|
```
|
|
217
244
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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.
|
|
255
|
+
|
|
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:
|
|
259
|
+
|
|
260
|
+
```js
|
|
261
|
+
import { registerInstrumentations } from "@opentelemetry/instrumentation";
|
|
262
|
+
import { RedisInstrumentation } from "@opentelemetry/instrumentation-redis";
|
|
263
|
+
import { createFoamIngestSpanProcessor } from "@foam-ai/node";
|
|
264
|
+
|
|
265
|
+
registerInstrumentations({
|
|
266
|
+
instrumentations: [new RedisInstrumentation()],
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
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^
|
|
242
286
|
|
|
243
287
|
### `injectTraceContext(headers)`
|
|
244
288
|
|
|
245
289
|
Writes W3C `traceparent`, `tracestate`, and `baggage` onto a header map.
|
|
246
290
|
|
|
247
|
-
Use this when you send a message yourself (Kafka, a queue, a custom
|
|
248
|
-
Auto-instrumented HTTP, Express, Undici, and Fetch already inject
|
|
249
|
-
headers;
|
|
250
|
-
propagator even if Foam
|
|
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.
|
|
251
295
|
|
|
252
296
|
```js
|
|
253
297
|
const headers = {};
|
|
@@ -257,10 +301,10 @@ await kafka.send({ value: payload, headers });
|
|
|
257
301
|
|
|
258
302
|
### `extractTraceContext(headers)`
|
|
259
303
|
|
|
260
|
-
Reads W3C trace and baggage headers and returns a parent OpenTelemetry
|
|
304
|
+
Reads W3C trace and baggage headers and returns a parent OpenTelemetry
|
|
305
|
+
context.
|
|
261
306
|
|
|
262
|
-
Use this when you receive a message on a custom transport so the
|
|
263
|
-
the same trace. Ignoring those headers starts a new one.
|
|
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.
|
|
264
308
|
|
|
265
309
|
```js
|
|
266
310
|
import { context } from "@opentelemetry/api";
|
|
@@ -273,10 +317,11 @@ await context.with(parent, () => handle(message));
|
|
|
273
317
|
|
|
274
318
|
Stores a small string on the current request context.
|
|
275
319
|
|
|
276
|
-
Use this for request-scoped identity (tenant, user id) that should follow
|
|
277
|
-
request across `await`s without being passed through every function.
|
|
278
|
-
|
|
279
|
-
|
|
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`.
|
|
280
325
|
|
|
281
326
|
```js
|
|
282
327
|
setBaggage("tenant.id", "acme");
|
|
@@ -286,8 +331,8 @@ setBaggage("tenant.id", "acme");
|
|
|
286
331
|
|
|
287
332
|
Returns a baggage value from the current request, or `undefined`.
|
|
288
333
|
|
|
289
|
-
Use this later in the same request to read a value set with `setBaggage`,
|
|
290
|
-
key that arrived on an inbound `baggage` header.
|
|
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.
|
|
291
336
|
|
|
292
337
|
```js
|
|
293
338
|
const tenant = getBaggage("tenant.id"); // "acme"
|
|
@@ -295,9 +340,9 @@ const tenant = getBaggage("tenant.id"); // "acme"
|
|
|
295
340
|
|
|
296
341
|
### `incrementCounter(name, value?, attributes?, options?)`
|
|
297
342
|
|
|
298
|
-
|
|
343
|
+
Creates the counter if it does not exist, then increments it.
|
|
299
344
|
|
|
300
|
-
Use this for event counts
|
|
345
|
+
This counter can only increases. Use this for event counts.
|
|
301
346
|
|
|
302
347
|
```js
|
|
303
348
|
incrementCounter("orders.created", 1, { "cloud.region": "us-west-1" });
|
|
@@ -305,10 +350,9 @@ incrementCounter("orders.created", 1, { "cloud.region": "us-west-1" });
|
|
|
305
350
|
|
|
306
351
|
### `recordHistogram(name, value, attributes?, options?)`
|
|
307
352
|
|
|
308
|
-
|
|
353
|
+
Creates the histogram if it does not exist, then records a measurement.
|
|
309
354
|
|
|
310
|
-
Use this for values you want percentiles or averages of: latency, duration,
|
|
311
|
-
payload size.
|
|
355
|
+
Use this for values you want percentiles or averages of: latency, duration, payload size.
|
|
312
356
|
|
|
313
357
|
```js
|
|
314
358
|
recordHistogram("checkout.duration", 0.142, undefined, { unit: "s" });
|
|
@@ -316,17 +360,20 @@ recordHistogram("checkout.duration", 0.142, undefined, { unit: "s" });
|
|
|
316
360
|
|
|
317
361
|
### `addUpDownCounter(name, value, attributes?, options?)`
|
|
318
362
|
|
|
319
|
-
|
|
363
|
+
Creates the up-down counter if it does not exist, then adds a signed
|
|
364
|
+
delta. The value can go up or down.
|
|
320
365
|
|
|
321
366
|
Use this for occupancy: active jobs, open connections, items in a pool.
|
|
322
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
|
+
|
|
323
370
|
```js
|
|
324
371
|
addUpDownCounter("jobs.active", -1);
|
|
325
372
|
```
|
|
326
373
|
|
|
327
374
|
### `setMetric(name, value, attributes?, options?)`
|
|
328
375
|
|
|
329
|
-
|
|
376
|
+
Creates the gauge if it does not exist, then sets its current value.
|
|
330
377
|
|
|
331
378
|
Use this for a point-in-time level: queue depth, heap used, cache size.
|
|
332
379
|
|
|
@@ -334,17 +381,51 @@ Use this for a point-in-time level: queue depth, heap used, cache size.
|
|
|
334
381
|
setMetric("queue.depth", 27);
|
|
335
382
|
```
|
|
336
383
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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";
|
|
413
|
+
|
|
414
|
+
try {
|
|
415
|
+
await chargeCard(order);
|
|
416
|
+
} catch (err) {
|
|
417
|
+
recordException(err);
|
|
418
|
+
throw err;
|
|
419
|
+
}
|
|
420
|
+
```
|
|
341
421
|
|
|
342
422
|
### `getState()`
|
|
343
423
|
|
|
344
|
-
Returns whether Foam initialized, which instrumentations registered, and
|
|
345
|
-
signals have an export path.
|
|
424
|
+
Returns whether Foam initialized, which instrumentations registered, and
|
|
425
|
+
which signals have an export path.
|
|
346
426
|
|
|
347
|
-
Use this in health checks, tests, or diagnostics to confirm Foam is
|
|
427
|
+
Use this in health checks, tests, or diagnostics to confirm Foam is
|
|
428
|
+
running.
|
|
348
429
|
|
|
349
430
|
```js
|
|
350
431
|
import { getState } from "@foam-ai/node";
|
|
@@ -354,47 +435,107 @@ getState();
|
|
|
354
435
|
// initialized: true,
|
|
355
436
|
// instrumentations: ["http", "express", "pg", ...],
|
|
356
437
|
// signals: {
|
|
357
|
-
// traces:
|
|
358
|
-
// metrics:
|
|
359
|
-
// logs:
|
|
360
|
-
// baggage:
|
|
361
|
-
// profile:
|
|
438
|
+
// traces: "global",
|
|
439
|
+
// metrics: "global",
|
|
440
|
+
// logs: "global",
|
|
441
|
+
// baggage: "global",
|
|
442
|
+
// profile: "none",
|
|
362
443
|
// },
|
|
363
444
|
// }
|
|
364
445
|
```
|
|
365
446
|
|
|
366
|
-
`instrumentations`
|
|
447
|
+
`instrumentations` lists only the instrumentations registered with a
|
|
367
448
|
successfully started Foam SDK, after upstream defaults and
|
|
368
449
|
`OTEL_NODE_ENABLED_INSTRUMENTATIONS` /
|
|
369
|
-
`OTEL_NODE_DISABLED_INSTRUMENTATIONS` filtering.
|
|
370
|
-
instrumentation is ready to patch a supported module when that module
|
|
371
|
-
|
|
450
|
+
`OTEL_NODE_DISABLED_INSTRUMENTATIONS` filtering. Registered means the
|
|
451
|
+
instrumentation is ready to patch a supported module when that module
|
|
452
|
+
loads.
|
|
453
|
+
|
|
454
|
+
Each `signals` value names the source of the Foam export path for that
|
|
455
|
+
signal:
|
|
456
|
+
|
|
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.
|
|
465
|
+
|
|
466
|
+
A value other than `"none"` means telemetry is expected, not that the app
|
|
467
|
+
has already produced or exported it.
|
|
468
|
+
|
|
469
|
+
## Custom spans and other OpenTelemetry APIs
|
|
470
|
+
|
|
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
|
|
480
|
+
```
|
|
372
481
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
482
|
+
```js
|
|
483
|
+
import { trace, SpanStatusCode } from "@opentelemetry/api";
|
|
484
|
+
|
|
485
|
+
const tracer = trace.getTracer("checkout");
|
|
486
|
+
|
|
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();
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
```
|
|
499
|
+
|
|
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.
|
|
506
|
+
|
|
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.
|
|
510
|
+
|
|
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.
|
|
379
515
|
|
|
380
516
|
## Network capture
|
|
381
517
|
|
|
382
|
-
`networkCapture` defaults to `"basic"` and controls HTTP
|
|
383
|
-
|
|
384
|
-
`"
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
518
|
+
`networkCapture` defaults to `"basic"` and controls extra HTTP detail.
|
|
519
|
+
|
|
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.
|
|
526
|
+
|
|
527
|
+
|
|
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
|
|
398
539
|
|
|
399
540
|
Foam follows the compatibility ranges of its OpenTelemetry logger
|
|
400
541
|
instrumentations:
|
|
@@ -403,66 +544,41 @@ instrumentations:
|
|
|
403
544
|
- Pino `>=5.14 <11`
|
|
404
545
|
- Winston `>=1 <4`
|
|
405
546
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
constructing the logger; registration alone does not prove that a logger was
|
|
409
|
-
loaded, patched, or produced records.
|
|
547
|
+
Initialize Foam before you import or construct the logger. Registration alone does not mean a logger
|
|
548
|
+
was loaded, patched, or produced records.
|
|
410
549
|
|
|
411
|
-
|
|
412
|
-
`disableLogSending: false` (the default), OpenTelemetry automatically sends:
|
|
550
|
+
With `disableLogSending: false` (the default), Foam SDK sends:
|
|
413
551
|
|
|
414
|
-
- Bunyan records through
|
|
415
|
-
- Pino 7
|
|
552
|
+
- Bunyan 1 records through an added OTel stream
|
|
553
|
+
- Pino 7–10 records through an added main-thread `multistream`
|
|
416
554
|
- Winston 3 records through the bundled `OpenTelemetryTransportV3`
|
|
417
555
|
|
|
418
|
-
Pino 5
|
|
419
|
-
|
|
420
|
-
stream, configures `pino-opentelemetry-transport`, installs a Winston OTel
|
|
421
|
-
transport, or otherwise gives another pipeline ownership of logger records.
|
|
422
|
-
This disables automatic sending for Bunyan, Pino, and Winston while preserving
|
|
423
|
-
trace correlation.
|
|
424
|
-
|
|
425
|
-
OpenTelemetry cannot reliably deduplicate manually configured streams and
|
|
426
|
-
transports; explicit `disableLogSending` ownership is the correctness
|
|
427
|
-
mechanism.
|
|
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.
|
|
428
558
|
|
|
429
|
-
`
|
|
430
|
-
|
|
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.
|
|
431
563
|
|
|
432
|
-
|
|
564
|
+
`ConsoleInstrumentation` turns calls to `console.*` into OTel log records.
|
|
565
|
+
It does not capture arbitrary stdout/stderr writes.
|
|
433
566
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
567
|
+
```js
|
|
568
|
+
console.log("hi"); // captured
|
|
569
|
+
process.stdout.write("hi\n"); // not captured
|
|
570
|
+
```
|
|
437
571
|
|
|
438
|
-
|
|
572
|
+
## OpenTelemetry configuration and compliance
|
|
439
573
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
- `OTEL_NODE_ENABLED_INSTRUMENTATIONS` and
|
|
443
|
-
`OTEL_NODE_DISABLED_INSTRUMENTATIONS`
|
|
444
|
-
- `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT`
|
|
445
|
-
- `OTEL_TRACES_SAMPLER`
|
|
574
|
+
Foam uses the official OpenTelemetry JavaScript API, SDK, resource,
|
|
575
|
+
propagation, instrumentation, and OTLP exporter packages.
|
|
446
576
|
|
|
447
577
|
The [OpenTelemetry JavaScript compliance matrix](https://github.com/open-telemetry/opentelemetry-specification/blob/main/spec-compliance-matrix/js.yaml)
|
|
448
|
-
describes `opentelemetry-js`, not this distro. It uses `+` (implemented),
|
|
449
|
-
`-` (not implemented), `?` (unknown), and `N/A`. Foam does not reimplement
|
|
450
|
-
those SDK features and does not inherit every `+` row: `init()` only
|
|
451
|
-
constructs the packages and options this distro actually wires (W3C
|
|
452
|
-
propagation, OTLP/HTTP to Foam, a parent-based ratio sampler). Rows the
|
|
453
|
-
matrix marks `+` for JavaScript but Foam never installs stay unavailable
|
|
454
|
-
here, including OTLP/gRPC, Zipkin, Prometheus, Jaeger remote sampling,
|
|
455
|
-
B3/Jaeger propagators, metric views, and `OTEL_TRACES_SAMPLER`. Foam also
|
|
456
|
-
does not implement rows marked `-` or `?`.
|
|
457
578
|
|
|
458
579
|
## TODO(pcga11): OpenTelemetry profiling
|
|
459
580
|
|
|
460
|
-
|
|
461
|
-
in-process Profiles provider, processor, or exporter yet.
|
|
462
|
-
|
|
463
|
-
Add profiling and report `getState().signals.profile: true` after that ships.
|
|
464
|
-
|
|
465
|
-
Track upstream progress:
|
|
581
|
+
The official OpenTelemetry JavaScript SDK has no in-process Profiles provider, processor, or exporter yet. Track upstream progress:
|
|
466
582
|
|
|
467
583
|
- [OpenTelemetry JS profiling implementation issue](https://github.com/open-telemetry/opentelemetry-js/issues/6500)
|
|
468
584
|
- [Profiling SIG language SDK support tracker](https://github.com/open-telemetry/sig-profiling/issues/106)
|