@foam-ai/node-cliengo 0.1.0-alpha.2 → 0.1.0-alpha.20

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.
@@ -8,8 +8,7 @@
8
8
  * through individual files.
9
9
  *
10
10
  * Edge cases covered:
11
- * - FOAM_OTEL_ENDPOINT is hardcoded (not read from env) to prevent
12
- * misconfiguration; services override via `options.endpoint` only.
11
+ * - FOAM_OTEL_ENDPOINT is hardcoded not configurable via env or options.
13
12
  * - REDACTED_KEYS uses lowercase comparison so "Authorization", "PASSWORD",
14
13
  * "Token" all match. Includes both "creditcard" and "credit_card" variants.
15
14
  * - HEALTH_ROUTES covers the four most common patterns across all 5 Cliengo
@@ -9,8 +9,7 @@
9
9
  * through individual files.
10
10
  *
11
11
  * Edge cases covered:
12
- * - FOAM_OTEL_ENDPOINT is hardcoded (not read from env) to prevent
13
- * misconfiguration; services override via `options.endpoint` only.
12
+ * - FOAM_OTEL_ENDPOINT is hardcoded not configurable via env or options.
14
13
  * - REDACTED_KEYS uses lowercase comparison so "Authorization", "PASSWORD",
15
14
  * "Token" all match. Includes both "creditcard" and "credit_card" variants.
16
15
  * - HEALTH_ROUTES covers the four most common patterns across all 5 Cliengo
@@ -31,6 +30,10 @@ exports.REDACTED_KEYS = new Set([
31
30
  'secret',
32
31
  'creditcard',
33
32
  'credit_card',
33
+ 'api_key',
34
+ 'apikey',
35
+ 'access_token',
36
+ 'accesstoken',
34
37
  ]);
35
38
  exports.HEALTH_ROUTES = new Set(['/health', '/healthz', '/ready', '/status']);
36
39
  exports.REQUEST_CONTEXT_FIELDS = [
@@ -36,6 +36,27 @@
36
36
  import { type Tracer } from '@opentelemetry/api';
37
37
  import type { ExpressErrorHandler, ExpressRequestHandler } from '../types';
38
38
  export declare function createExpressMiddleware(tracer: Tracer): ExpressRequestHandler;
39
+ /**
40
+ * Reads the trace context that was captured on the request at middleware time.
41
+ * Use this in pino-http's `customProps` to get trace IDs even after NR's
42
+ * transaction has ended:
43
+ *
44
+ * pinoHttp({ customProps: (req) => getRequestTraceContext(req) })
45
+ */
46
+ export declare function getRequestTraceContext(req: any): {
47
+ traceId: string;
48
+ spanId: string;
49
+ traceparent: string;
50
+ };
51
+ /**
52
+ * Returns pino-http options with customProps pre-wired to read the trace
53
+ * context stored on req by createExpressMiddleware(). Zero config for services:
54
+ *
55
+ * app.use(pinoHttp({ logger, ...foam.createPinoHttpOptions() }));
56
+ */
57
+ export declare function createPinoHttpOptions(): {
58
+ customProps: (req: any) => Record<string, string>;
59
+ };
39
60
  export declare function createExpressErrorHandler(middlewareRef?: ExpressRequestHandler & {
40
61
  _markErrorHandler?: () => void;
41
62
  }): ExpressErrorHandler;
@@ -36,11 +36,15 @@
36
36
  */
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
38
  exports.createExpressMiddleware = createExpressMiddleware;
39
+ exports.getRequestTraceContext = getRequestTraceContext;
40
+ exports.createPinoHttpOptions = createPinoHttpOptions;
39
41
  exports.createExpressErrorHandler = createExpressErrorHandler;
40
42
  const api_1 = require("@opentelemetry/api");
41
43
  const trace_bridge_1 = require("../trace-bridge");
42
44
  const request_context_1 = require("./request-context");
43
45
  const SPAN_KEY = Symbol.for('foam.shadow.span');
46
+ const TRACE_CTX_KEY = Symbol.for('foam.trace.context');
47
+ const ERROR_RECORDED_KEY = Symbol.for('foam.shadow.errorRecorded');
44
48
  /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */
45
49
  function createExpressMiddleware(tracer) {
46
50
  let errorHandlerRegistered = false;
@@ -52,15 +56,35 @@ function createExpressMiddleware(tracer) {
52
56
  const method = req.method ?? 'UNKNOWN';
53
57
  const target = req.originalUrl ?? req.url ?? '/';
54
58
  const { traceId, spanId } = (0, trace_bridge_1.getTraceContext)();
59
+ // Build a parent context so the shadow span shares the same OTel traceId
60
+ // as NR's transaction (or the active OTel span). Without this, startSpan()
61
+ // creates a root span with a separate traceId, breaking end-to-end
62
+ // traceability between HTTP spans, consumer spans, and logs.
63
+ let parentCtx;
64
+ if (traceId && spanId) {
65
+ parentCtx = api_1.trace.setSpanContext(api_1.ROOT_CONTEXT, {
66
+ traceId,
67
+ spanId,
68
+ traceFlags: 1,
69
+ isRemote: true,
70
+ });
71
+ }
55
72
  const span = tracer.startSpan(`${method} ${target}`, {
56
73
  attributes: {
57
74
  'http.method': method,
58
75
  'http.target': target,
59
- ...(traceId ? { 'nr.traceId': traceId } : {}),
60
- ...(spanId ? { 'nr.spanId': spanId } : {}),
61
76
  },
62
- });
77
+ }, parentCtx);
63
78
  req[SPAN_KEY] = span;
79
+ // Read trace context from the span we just created — not from NR.
80
+ // The span's spanContext() always has the correct traceId, whether it
81
+ // inherited from NR or generated a fresh root.
82
+ const sc = span.spanContext();
83
+ req[TRACE_CTX_KEY] = {
84
+ traceId: sc.traceId,
85
+ spanId: sc.spanId,
86
+ traceparent: `00-${sc.traceId}-${sc.spanId}-01`,
87
+ };
64
88
  const startTime = Date.now();
65
89
  const onFinish = () => {
66
90
  try {
@@ -71,7 +95,10 @@ function createExpressMiddleware(tracer) {
71
95
  span.setAttribute('http.route', route);
72
96
  span.setAttribute('http.response_content_length', String(contentLength));
73
97
  span.setAttribute('http.duration_ms', Date.now() - startTime);
74
- if (statusCode >= 500) {
98
+ if (req[ERROR_RECORDED_KEY]) {
99
+ // Error handler already set status — don't overwrite
100
+ }
101
+ else if (statusCode >= 500) {
75
102
  span.setStatus({ code: api_1.SpanStatusCode.ERROR });
76
103
  if (!errorHandlerRegistered && !warnedMissingErrorHandler) {
77
104
  warnedMissingErrorHandler = true;
@@ -94,11 +121,47 @@ function createExpressMiddleware(tracer) {
94
121
  }
95
122
  };
96
123
  res.on('finish', onFinish);
97
- next();
124
+ // Set the shadow span as the active context for the duration of the
125
+ // request. This makes it discoverable via trace.getActiveSpan() so
126
+ // buildTraceparent() (used by injectJobData/injectSnsAttributes) returns
127
+ // the same traceId — even without NR.
128
+ const activeCtx = api_1.trace.setSpan(parentCtx ?? api_1.ROOT_CONTEXT, span);
129
+ api_1.context.with(activeCtx, () => next());
98
130
  };
99
131
  middleware._markErrorHandler = markErrorHandlerRegistered;
100
132
  return middleware;
101
133
  }
134
+ /**
135
+ * Reads the trace context that was captured on the request at middleware time.
136
+ * Use this in pino-http's `customProps` to get trace IDs even after NR's
137
+ * transaction has ended:
138
+ *
139
+ * pinoHttp({ customProps: (req) => getRequestTraceContext(req) })
140
+ */
141
+ function getRequestTraceContext(req) {
142
+ try {
143
+ const ctx = req[TRACE_CTX_KEY];
144
+ return {
145
+ traceId: ctx?.traceId ?? '',
146
+ spanId: ctx?.spanId ?? '',
147
+ traceparent: ctx?.traceparent ?? '',
148
+ };
149
+ }
150
+ catch {
151
+ return { traceId: '', spanId: '', traceparent: '' };
152
+ }
153
+ }
154
+ /**
155
+ * Returns pino-http options with customProps pre-wired to read the trace
156
+ * context stored on req by createExpressMiddleware(). Zero config for services:
157
+ *
158
+ * app.use(pinoHttp({ logger, ...foam.createPinoHttpOptions() }));
159
+ */
160
+ function createPinoHttpOptions() {
161
+ return {
162
+ customProps: (req) => getRequestTraceContext(req),
163
+ };
164
+ }
102
165
  function createExpressErrorHandler(middlewareRef) {
103
166
  middlewareRef?._markErrorHandler?.();
104
167
  return (err, req, _res, next) => {
@@ -112,6 +175,7 @@ function createExpressErrorHandler(middlewareRef) {
112
175
  stack: error.stack,
113
176
  });
114
177
  span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error.message });
178
+ req[ERROR_RECORDED_KEY] = true;
115
179
  }
116
180
  }
117
181
  catch {
@@ -5,7 +5,8 @@
5
5
  * Why this file exists:
6
6
  * Same rationale as the Express middleware (see express.ts). Fastify's hook
7
7
  * system is different — we use three hooks instead of middleware + error handler:
8
- * - onRequest: creates the shadow span, reads NR's traceId
8
+ * - onRequest: creates the shadow span, reads NR's traceId, sets the span as
9
+ * the active OTel context so route handlers inherit it via AsyncLocalStorage
9
10
  * - onError: records exception details (fires BEFORE errorHandler, so we see
10
11
  * every error including ones mapped to non-500 responses like AppError.badRequest)
11
12
  * - onResponse: records status/route/duration, captures request context, ends span
@@ -15,14 +16,27 @@
15
16
  * Edge cases covered:
16
17
  * - Fastify's onError fires for ALL errors including 4xx AppError variants.
17
18
  * We record the exception on the span regardless — Foam sees the full error
18
- * even for 400s. The span status is only set to ERROR by onError (not
19
- * overwritten to OK by onResponse for <500 codes, since onError already set it).
19
+ * even for 400s. Once onError sets ERROR status, onResponse never overwrites it.
20
20
  * - request.routeOptions?.url may not exist in older Fastify versions — falls
21
21
  * back to request.url.
22
22
  * - Health routes skipped for request.context capture (same as Express).
23
23
  * - All hooks wrapped in try/catch — never crashes the request pipeline.
24
- * - done() always called even if our code throws — Fastify hook contract.
24
+ * - onError/onResponse use async hooks (Fastify 5 forward-compat).
25
+ * onRequest uses callback style intentionally for context.with() propagation.
26
+ * - Plugin sets Symbol.for('skip-override') (same as fastify-plugin) so hooks
27
+ * fire globally across all encapsulated contexts. Without this, hooks only
28
+ * fire for routes in the same plugin scope — a silent production footgun.
25
29
  */
26
30
  import { type Tracer } from '@opentelemetry/api';
27
31
  import type { FastifyPluginAsync } from '../types';
32
+ /**
33
+ * Reads the trace context that was captured on the request at hook time.
34
+ * Use this in Fastify's logger mixin or serializers to get trace IDs even
35
+ * after NR's transaction has ended.
36
+ */
37
+ export declare function getRequestTraceContext(request: any): {
38
+ traceId: string;
39
+ spanId: string;
40
+ traceparent: string;
41
+ };
28
42
  export declare function createFastifyPlugin(tracer: Tracer): FastifyPluginAsync;
@@ -6,7 +6,8 @@
6
6
  * Why this file exists:
7
7
  * Same rationale as the Express middleware (see express.ts). Fastify's hook
8
8
  * system is different — we use three hooks instead of middleware + error handler:
9
- * - onRequest: creates the shadow span, reads NR's traceId
9
+ * - onRequest: creates the shadow span, reads NR's traceId, sets the span as
10
+ * the active OTel context so route handlers inherit it via AsyncLocalStorage
10
11
  * - onError: records exception details (fires BEFORE errorHandler, so we see
11
12
  * every error including ones mapped to non-500 responses like AppError.badRequest)
12
13
  * - onResponse: records status/route/duration, captures request context, ends span
@@ -16,46 +17,90 @@
16
17
  * Edge cases covered:
17
18
  * - Fastify's onError fires for ALL errors including 4xx AppError variants.
18
19
  * We record the exception on the span regardless — Foam sees the full error
19
- * even for 400s. The span status is only set to ERROR by onError (not
20
- * overwritten to OK by onResponse for <500 codes, since onError already set it).
20
+ * even for 400s. Once onError sets ERROR status, onResponse never overwrites it.
21
21
  * - request.routeOptions?.url may not exist in older Fastify versions — falls
22
22
  * back to request.url.
23
23
  * - Health routes skipped for request.context capture (same as Express).
24
24
  * - All hooks wrapped in try/catch — never crashes the request pipeline.
25
- * - done() always called even if our code throws — Fastify hook contract.
25
+ * - onError/onResponse use async hooks (Fastify 5 forward-compat).
26
+ * onRequest uses callback style intentionally for context.with() propagation.
27
+ * - Plugin sets Symbol.for('skip-override') (same as fastify-plugin) so hooks
28
+ * fire globally across all encapsulated contexts. Without this, hooks only
29
+ * fire for routes in the same plugin scope — a silent production footgun.
26
30
  */
27
31
  Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.getRequestTraceContext = getRequestTraceContext;
28
33
  exports.createFastifyPlugin = createFastifyPlugin;
29
34
  const api_1 = require("@opentelemetry/api");
30
35
  const trace_bridge_1 = require("../trace-bridge");
31
36
  const request_context_1 = require("./request-context");
32
37
  const SPAN_KEY = Symbol.for('foam.shadow.span');
33
38
  const START_KEY = Symbol.for('foam.shadow.start');
39
+ const TRACE_CTX_KEY = Symbol.for('foam.trace.context');
40
+ const ERROR_RECORDED_KEY = Symbol.for('foam.shadow.errorRecorded');
41
+ /**
42
+ * Reads the trace context that was captured on the request at hook time.
43
+ * Use this in Fastify's logger mixin or serializers to get trace IDs even
44
+ * after NR's transaction has ended.
45
+ */
46
+ function getRequestTraceContext(request) {
47
+ try {
48
+ const ctx = request[TRACE_CTX_KEY];
49
+ return {
50
+ traceId: ctx?.traceId ?? '',
51
+ spanId: ctx?.spanId ?? '',
52
+ traceparent: ctx?.traceparent ?? '',
53
+ };
54
+ }
55
+ catch {
56
+ return { traceId: '', spanId: '', traceparent: '' };
57
+ }
58
+ }
34
59
  /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */
35
60
  function createFastifyPlugin(tracer) {
36
- return async (fastify) => {
61
+ const plugin = async (fastify) => {
62
+ // onRequest uses callback style intentionally — otelContext.with(ctx, done)
63
+ // propagates the shadow span as the active OTel context via AsyncLocalStorage
64
+ // to all downstream hooks and the route handler. Async hooks would lose the
65
+ // context after the awaited promise resolves.
37
66
  fastify.addHook('onRequest', (request, _reply, done) => {
38
67
  try {
39
68
  const method = request.method ?? 'UNKNOWN';
40
69
  const url = request.url ?? '/';
41
70
  const { traceId, spanId } = (0, trace_bridge_1.getTraceContext)();
71
+ let parentCtx;
72
+ if (traceId && spanId) {
73
+ parentCtx = api_1.trace.setSpanContext(api_1.ROOT_CONTEXT, {
74
+ traceId,
75
+ spanId,
76
+ traceFlags: 1,
77
+ isRemote: true,
78
+ });
79
+ }
42
80
  const span = tracer.startSpan(`${method} ${url}`, {
43
81
  attributes: {
44
82
  'http.method': method,
45
83
  'http.target': url,
46
- ...(traceId ? { 'nr.traceId': traceId } : {}),
47
- ...(spanId ? { 'nr.spanId': spanId } : {}),
48
84
  },
49
- });
85
+ }, parentCtx);
50
86
  request[SPAN_KEY] = span;
51
87
  request[START_KEY] = Date.now();
88
+ const sc = span.spanContext();
89
+ request[TRACE_CTX_KEY] = {
90
+ traceId: sc.traceId,
91
+ spanId: sc.spanId,
92
+ traceparent: `00-${sc.traceId}-${sc.spanId}-01`,
93
+ };
94
+ const activeCtx = api_1.trace.setSpan(parentCtx ?? api_1.ROOT_CONTEXT, span);
95
+ api_1.context.with(activeCtx, () => done());
96
+ return;
52
97
  }
53
98
  catch {
54
99
  /* never crash */
55
100
  }
56
101
  done();
57
102
  });
58
- fastify.addHook('onError', (request, _reply, error, done) => {
103
+ fastify.addHook('onError', async (request, _reply, error) => {
59
104
  try {
60
105
  const span = request[SPAN_KEY];
61
106
  if (span) {
@@ -65,14 +110,14 @@ function createFastifyPlugin(tracer) {
65
110
  stack: error.stack,
66
111
  });
67
112
  span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error.message });
113
+ request[ERROR_RECORDED_KEY] = true;
68
114
  }
69
115
  }
70
116
  catch {
71
117
  /* never crash */
72
118
  }
73
- done();
74
119
  });
75
- fastify.addHook('onResponse', (request, reply, done) => {
120
+ fastify.addHook('onResponse', async (request, reply) => {
76
121
  try {
77
122
  const span = request[SPAN_KEY];
78
123
  if (span) {
@@ -82,8 +127,13 @@ function createFastifyPlugin(tracer) {
82
127
  span.setAttribute('http.status_code', statusCode);
83
128
  span.setAttribute('http.route', route);
84
129
  span.setAttribute('http.duration_ms', Date.now() - start);
85
- if (statusCode < 500) {
86
- span.setStatus({ code: api_1.SpanStatusCode.OK });
130
+ if (!request[ERROR_RECORDED_KEY]) {
131
+ if (statusCode >= 500) {
132
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR });
133
+ }
134
+ else {
135
+ span.setStatus({ code: api_1.SpanStatusCode.OK });
136
+ }
87
137
  }
88
138
  if (!(0, request_context_1.isHealthRoute)(route) && !(0, request_context_1.isHealthRoute)(request.url ?? '/')) {
89
139
  (0, request_context_1.captureRequestContext)(span, request);
@@ -94,8 +144,13 @@ function createFastifyPlugin(tracer) {
94
144
  catch {
95
145
  /* never crash */
96
146
  }
97
- done();
98
147
  });
99
148
  };
149
+ // Equivalent to wrapping with fastify-plugin — breaks Fastify's encapsulation
150
+ // so hooks fire for ALL routes, not just those registered in the same scope.
151
+ // Without this, hooks silently no-op for routes in child contexts.
152
+ plugin[Symbol.for('skip-override')] = true;
153
+ plugin[Symbol.for('fastify.display-name')] = '@foam-ai/node-cliengo';
154
+ return plugin;
100
155
  }
101
156
  /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */
@@ -11,16 +11,19 @@
11
11
  *
12
12
  * The individual function exports exist for tree-shaking and for cases where
13
13
  * a service needs to call e.g. injectJobData() from a file that doesn't have
14
- * access to the foam instance.
14
+ * access to the foam instance. createFastifyPlugin is NOT exported standalone
15
+ * because the raw function takes a tracer argument — use foam.createFastifyPlugin()
16
+ * instead.
15
17
  */
16
- export { init } from './init';
18
+ export { init, getFoam, createFastifyLoggerConfig } from './init';
17
19
  export { buildTraceparent, extractParentContext, getTraceContext } from './trace-bridge';
18
20
  export { injectSnsAttributes } from './sns';
19
21
  export { injectJobData, wrapJobConsumer } from './job';
20
22
  export { wrapSqsConsumer } from './sqs';
21
- export { createExpressMiddleware, createExpressErrorHandler } from './http/express';
22
- export { createFastifyPlugin } from './http/fastify';
23
+ export { createExpressMiddleware, createExpressErrorHandler, getRequestTraceContext, createPinoHttpOptions } from './http/express';
24
+ export { getRequestTraceContext as getFastifyRequestTraceContext } from './http/fastify';
23
25
  export { createWinstonFormat } from './logs/winston-format';
24
26
  export { createPinoMixin } from './logs/pino-mixin';
27
+ export { loadNewRelic } from './nr';
25
28
  export { FOAM_OTEL_ENDPOINT } from './constants';
26
29
  export type { FoamMetrics, Counter, ExpressErrorHandler, ExpressRequestHandler, FastifyPluginAsync, Histogram, InitOptions, NewRelicAgent, ObservableGauge, FoamInstance, SnsMessageAttributeValue, SqsMessage, TraceContext, WinstonFormat, WinstonLogger, WinstonTransport, } from './types';
@@ -12,12 +12,16 @@
12
12
  *
13
13
  * The individual function exports exist for tree-shaking and for cases where
14
14
  * a service needs to call e.g. injectJobData() from a file that doesn't have
15
- * access to the foam instance.
15
+ * access to the foam instance. createFastifyPlugin is NOT exported standalone
16
+ * because the raw function takes a tracer argument — use foam.createFastifyPlugin()
17
+ * instead.
16
18
  */
17
19
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.FOAM_OTEL_ENDPOINT = exports.createPinoMixin = exports.createWinstonFormat = exports.createFastifyPlugin = exports.createExpressErrorHandler = exports.createExpressMiddleware = exports.wrapSqsConsumer = exports.wrapJobConsumer = exports.injectJobData = exports.injectSnsAttributes = exports.getTraceContext = exports.extractParentContext = exports.buildTraceparent = exports.init = void 0;
20
+ exports.FOAM_OTEL_ENDPOINT = exports.loadNewRelic = exports.createPinoMixin = exports.createWinstonFormat = exports.getFastifyRequestTraceContext = exports.createPinoHttpOptions = exports.getRequestTraceContext = exports.createExpressErrorHandler = exports.createExpressMiddleware = exports.wrapSqsConsumer = exports.wrapJobConsumer = exports.injectJobData = exports.injectSnsAttributes = exports.getTraceContext = exports.extractParentContext = exports.buildTraceparent = exports.createFastifyLoggerConfig = exports.getFoam = exports.init = void 0;
19
21
  var init_1 = require("./init");
20
22
  Object.defineProperty(exports, "init", { enumerable: true, get: function () { return init_1.init; } });
23
+ Object.defineProperty(exports, "getFoam", { enumerable: true, get: function () { return init_1.getFoam; } });
24
+ Object.defineProperty(exports, "createFastifyLoggerConfig", { enumerable: true, get: function () { return init_1.createFastifyLoggerConfig; } });
21
25
  var trace_bridge_1 = require("./trace-bridge");
22
26
  Object.defineProperty(exports, "buildTraceparent", { enumerable: true, get: function () { return trace_bridge_1.buildTraceparent; } });
23
27
  Object.defineProperty(exports, "extractParentContext", { enumerable: true, get: function () { return trace_bridge_1.extractParentContext; } });
@@ -32,11 +36,15 @@ Object.defineProperty(exports, "wrapSqsConsumer", { enumerable: true, get: funct
32
36
  var express_1 = require("./http/express");
33
37
  Object.defineProperty(exports, "createExpressMiddleware", { enumerable: true, get: function () { return express_1.createExpressMiddleware; } });
34
38
  Object.defineProperty(exports, "createExpressErrorHandler", { enumerable: true, get: function () { return express_1.createExpressErrorHandler; } });
39
+ Object.defineProperty(exports, "getRequestTraceContext", { enumerable: true, get: function () { return express_1.getRequestTraceContext; } });
40
+ Object.defineProperty(exports, "createPinoHttpOptions", { enumerable: true, get: function () { return express_1.createPinoHttpOptions; } });
35
41
  var fastify_1 = require("./http/fastify");
36
- Object.defineProperty(exports, "createFastifyPlugin", { enumerable: true, get: function () { return fastify_1.createFastifyPlugin; } });
42
+ Object.defineProperty(exports, "getFastifyRequestTraceContext", { enumerable: true, get: function () { return fastify_1.getRequestTraceContext; } });
37
43
  var winston_format_1 = require("./logs/winston-format");
38
44
  Object.defineProperty(exports, "createWinstonFormat", { enumerable: true, get: function () { return winston_format_1.createWinstonFormat; } });
39
45
  var pino_mixin_1 = require("./logs/pino-mixin");
40
46
  Object.defineProperty(exports, "createPinoMixin", { enumerable: true, get: function () { return pino_mixin_1.createPinoMixin; } });
47
+ var nr_1 = require("./nr");
48
+ Object.defineProperty(exports, "loadNewRelic", { enumerable: true, get: function () { return nr_1.loadNewRelic; } });
41
49
  var constants_1 = require("./constants");
42
50
  Object.defineProperty(exports, "FOAM_OTEL_ENDPOINT", { enumerable: true, get: function () { return constants_1.FOAM_OTEL_ENDPOINT; } });
@@ -24,6 +24,7 @@
24
24
  * features (trace propagation, log enrichment, NR bridging) still work.
25
25
  * - Token undefined + forceExport: throws (fail-fast — you asked to force
26
26
  * export but provided no credentials).
27
+ * - Endpoint is always the production Foam endpoint — not configurable.
27
28
  * - Auto-shutdown (SIGTERM/SIGINT): flushes all providers with 5s timeout,
28
29
  * then calls process.exit(0). Without the explicit exit, registering
29
30
  * process.on('SIGTERM') replaces Node's default terminate behavior — services
@@ -48,4 +49,26 @@
48
49
  * global registrations.
49
50
  */
50
51
  import type { InitOptions, FoamInstance } from './types';
51
- export declare function init(serviceName: string, token?: string, options?: InitOptions): FoamInstance;
52
+ export declare function init(serviceName: string, options?: InitOptions): FoamInstance;
53
+ /**
54
+ * Returns the Foam instance for the given service name (or the only instance
55
+ * if there's just one). Call this from any module after init() has run —
56
+ * no circular dependency, no holder pattern needed.
57
+ */
58
+ export declare function getFoam(serviceName?: string): FoamInstance | undefined;
59
+ /**
60
+ * Standalone Fastify logger config that handles the getFoam() lookup internally.
61
+ * If Foam is initialized, returns { mixin, stream (multistream with OTLP), ...opts }.
62
+ * If Foam is not initialized (disabled, not yet called), returns opts as-is —
63
+ * Fastify gets a plain Pino logger with no OTLP destination.
64
+ *
65
+ * // No getFoam() check or ternary needed:
66
+ * Fastify({ logger: createFastifyLoggerConfig({ level: 'info' }) })
67
+ *
68
+ * // Dev with pino-pretty:
69
+ * Fastify({ logger: createFastifyLoggerConfig({
70
+ * level: 'debug',
71
+ * streams: [{ stream: pinoPretty() }],
72
+ * })})
73
+ */
74
+ export declare function createFastifyLoggerConfig(opts?: Record<string, any>): Record<string, any>;