@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.
- package/dist/node-cliengo/src/constants.d.ts +1 -2
- package/dist/node-cliengo/src/constants.js +5 -2
- package/dist/node-cliengo/src/http/express.d.ts +21 -0
- package/dist/node-cliengo/src/http/express.js +69 -5
- package/dist/node-cliengo/src/http/fastify.d.ts +18 -4
- package/dist/node-cliengo/src/http/fastify.js +69 -14
- package/dist/node-cliengo/src/index.d.ts +7 -4
- package/dist/node-cliengo/src/index.js +11 -3
- package/dist/node-cliengo/src/init.d.ts +24 -1
- package/dist/node-cliengo/src/init.js +150 -53
- package/dist/node-cliengo/src/job.d.ts +1 -1
- package/dist/node-cliengo/src/job.js +1 -5
- package/dist/node-cliengo/src/logs/pino-destination.d.ts +7 -2
- package/dist/node-cliengo/src/logs/pino-destination.js +34 -9
- package/dist/node-cliengo/src/logs/pino-mixin.d.ts +5 -4
- package/dist/node-cliengo/src/logs/pino-mixin.js +9 -5
- package/dist/node-cliengo/src/logs/winston-transport.js +11 -0
- package/dist/node-cliengo/src/nr.d.ts +17 -0
- package/dist/node-cliengo/src/nr.js +29 -0
- package/dist/node-cliengo/src/sns.d.ts +1 -4
- package/dist/node-cliengo/src/sns.js +2 -9
- package/dist/node-cliengo/src/trace-bridge.d.ts +30 -26
- package/dist/node-cliengo/src/trace-bridge.js +66 -35
- package/dist/node-cliengo/src/types.d.ts +37 -4
- package/package.json +11 -9
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
* through individual files.
|
|
9
9
|
*
|
|
10
10
|
* Edge cases covered:
|
|
11
|
-
* - FOAM_OTEL_ENDPOINT is hardcoded
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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
|
-
* -
|
|
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.
|
|
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
|
-
* -
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
86
|
-
|
|
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 {
|
|
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.
|
|
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, "
|
|
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,
|
|
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>;
|