@foam-ai/node-cliengo 0.1.0-alpha.6 → 0.1.0-alpha.9
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/http/express.d.ts +21 -0
- package/dist/node-cliengo/src/http/express.js +34 -0
- package/dist/node-cliengo/src/http/fastify.d.ts +10 -0
- package/dist/node-cliengo/src/http/fastify.js +20 -0
- package/dist/node-cliengo/src/index.d.ts +2 -2
- package/dist/node-cliengo/src/index.js +4 -1
- package/dist/node-cliengo/src/init.js +2 -0
- package/dist/node-cliengo/src/logs/pino-destination.js +14 -0
- 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/trace-bridge.d.ts +24 -22
- package/dist/node-cliengo/src/trace-bridge.js +46 -34
- package/dist/node-cliengo/src/types.d.ts +3 -0
- package/package.json +1 -1
|
@@ -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,14 @@
|
|
|
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');
|
|
44
47
|
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */
|
|
45
48
|
function createExpressMiddleware(tracer) {
|
|
46
49
|
let errorHandlerRegistered = false;
|
|
@@ -61,6 +64,7 @@ function createExpressMiddleware(tracer) {
|
|
|
61
64
|
},
|
|
62
65
|
});
|
|
63
66
|
req[SPAN_KEY] = span;
|
|
67
|
+
req[TRACE_CTX_KEY] = { traceId, spanId };
|
|
64
68
|
const startTime = Date.now();
|
|
65
69
|
const onFinish = () => {
|
|
66
70
|
try {
|
|
@@ -99,6 +103,36 @@ function createExpressMiddleware(tracer) {
|
|
|
99
103
|
middleware._markErrorHandler = markErrorHandlerRegistered;
|
|
100
104
|
return middleware;
|
|
101
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Reads the trace context that was captured on the request at middleware time.
|
|
108
|
+
* Use this in pino-http's `customProps` to get trace IDs even after NR's
|
|
109
|
+
* transaction has ended:
|
|
110
|
+
*
|
|
111
|
+
* pinoHttp({ customProps: (req) => getRequestTraceContext(req) })
|
|
112
|
+
*/
|
|
113
|
+
function getRequestTraceContext(req) {
|
|
114
|
+
try {
|
|
115
|
+
const ctx = req[TRACE_CTX_KEY];
|
|
116
|
+
const traceId = ctx?.traceId ?? '';
|
|
117
|
+
const spanId = ctx?.spanId ?? '';
|
|
118
|
+
const traceparent = traceId && spanId ? `00-${traceId}-${spanId}-01` : '';
|
|
119
|
+
return { traceId, spanId, traceparent };
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return { traceId: '', spanId: '', traceparent: '' };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Returns pino-http options with customProps pre-wired to read the trace
|
|
127
|
+
* context stored on req by createExpressMiddleware(). Zero config for services:
|
|
128
|
+
*
|
|
129
|
+
* app.use(pinoHttp({ logger, ...foam.createPinoHttpOptions() }));
|
|
130
|
+
*/
|
|
131
|
+
function createPinoHttpOptions() {
|
|
132
|
+
return {
|
|
133
|
+
customProps: (req) => getRequestTraceContext(req),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
102
136
|
function createExpressErrorHandler(middlewareRef) {
|
|
103
137
|
middlewareRef?._markErrorHandler?.();
|
|
104
138
|
return (err, req, _res, next) => {
|
|
@@ -25,4 +25,14 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { type Tracer } from '@opentelemetry/api';
|
|
27
27
|
import type { FastifyPluginAsync } from '../types';
|
|
28
|
+
/**
|
|
29
|
+
* Reads the trace context that was captured on the request at hook time.
|
|
30
|
+
* Use this in Fastify's logger mixin or serializers to get trace IDs even
|
|
31
|
+
* after NR's transaction has ended.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getRequestTraceContext(request: any): {
|
|
34
|
+
traceId: string;
|
|
35
|
+
spanId: string;
|
|
36
|
+
traceparent: string;
|
|
37
|
+
};
|
|
28
38
|
export declare function createFastifyPlugin(tracer: Tracer): FastifyPluginAsync;
|
|
@@ -25,12 +25,31 @@
|
|
|
25
25
|
* - done() always called even if our code throws — Fastify hook contract.
|
|
26
26
|
*/
|
|
27
27
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.getRequestTraceContext = getRequestTraceContext;
|
|
28
29
|
exports.createFastifyPlugin = createFastifyPlugin;
|
|
29
30
|
const api_1 = require("@opentelemetry/api");
|
|
30
31
|
const trace_bridge_1 = require("../trace-bridge");
|
|
31
32
|
const request_context_1 = require("./request-context");
|
|
32
33
|
const SPAN_KEY = Symbol.for('foam.shadow.span');
|
|
33
34
|
const START_KEY = Symbol.for('foam.shadow.start');
|
|
35
|
+
const TRACE_CTX_KEY = Symbol.for('foam.trace.context');
|
|
36
|
+
/**
|
|
37
|
+
* Reads the trace context that was captured on the request at hook time.
|
|
38
|
+
* Use this in Fastify's logger mixin or serializers to get trace IDs even
|
|
39
|
+
* after NR's transaction has ended.
|
|
40
|
+
*/
|
|
41
|
+
function getRequestTraceContext(request) {
|
|
42
|
+
try {
|
|
43
|
+
const ctx = request[TRACE_CTX_KEY];
|
|
44
|
+
const traceId = ctx?.traceId ?? '';
|
|
45
|
+
const spanId = ctx?.spanId ?? '';
|
|
46
|
+
const traceparent = traceId && spanId ? `00-${traceId}-${spanId}-01` : '';
|
|
47
|
+
return { traceId, spanId, traceparent };
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return { traceId: '', spanId: '', traceparent: '' };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
34
53
|
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */
|
|
35
54
|
function createFastifyPlugin(tracer) {
|
|
36
55
|
return async (fastify) => {
|
|
@@ -49,6 +68,7 @@ function createFastifyPlugin(tracer) {
|
|
|
49
68
|
});
|
|
50
69
|
request[SPAN_KEY] = span;
|
|
51
70
|
request[START_KEY] = Date.now();
|
|
71
|
+
request[TRACE_CTX_KEY] = { traceId, spanId };
|
|
52
72
|
}
|
|
53
73
|
catch {
|
|
54
74
|
/* never crash */
|
|
@@ -18,8 +18,8 @@ export { buildTraceparent, extractParentContext, getTraceContext } from './trace
|
|
|
18
18
|
export { injectSnsAttributes } from './sns';
|
|
19
19
|
export { injectJobData, wrapJobConsumer } from './job';
|
|
20
20
|
export { wrapSqsConsumer } from './sqs';
|
|
21
|
-
export { createExpressMiddleware, createExpressErrorHandler } from './http/express';
|
|
22
|
-
export { createFastifyPlugin } from './http/fastify';
|
|
21
|
+
export { createExpressMiddleware, createExpressErrorHandler, getRequestTraceContext, createPinoHttpOptions } from './http/express';
|
|
22
|
+
export { createFastifyPlugin, getRequestTraceContext as getFastifyRequestTraceContext } from './http/fastify';
|
|
23
23
|
export { createWinstonFormat } from './logs/winston-format';
|
|
24
24
|
export { createPinoMixin } from './logs/pino-mixin';
|
|
25
25
|
export { FOAM_OTEL_ENDPOINT } from './constants';
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* access to the foam instance.
|
|
16
16
|
*/
|
|
17
17
|
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;
|
|
18
|
+
exports.FOAM_OTEL_ENDPOINT = exports.createPinoMixin = exports.createWinstonFormat = exports.getFastifyRequestTraceContext = exports.createFastifyPlugin = exports.createPinoHttpOptions = exports.getRequestTraceContext = exports.createExpressErrorHandler = exports.createExpressMiddleware = exports.wrapSqsConsumer = exports.wrapJobConsumer = exports.injectJobData = exports.injectSnsAttributes = exports.getTraceContext = exports.extractParentContext = exports.buildTraceparent = exports.init = void 0;
|
|
19
19
|
var init_1 = require("./init");
|
|
20
20
|
Object.defineProperty(exports, "init", { enumerable: true, get: function () { return init_1.init; } });
|
|
21
21
|
var trace_bridge_1 = require("./trace-bridge");
|
|
@@ -32,8 +32,11 @@ Object.defineProperty(exports, "wrapSqsConsumer", { enumerable: true, get: funct
|
|
|
32
32
|
var express_1 = require("./http/express");
|
|
33
33
|
Object.defineProperty(exports, "createExpressMiddleware", { enumerable: true, get: function () { return express_1.createExpressMiddleware; } });
|
|
34
34
|
Object.defineProperty(exports, "createExpressErrorHandler", { enumerable: true, get: function () { return express_1.createExpressErrorHandler; } });
|
|
35
|
+
Object.defineProperty(exports, "getRequestTraceContext", { enumerable: true, get: function () { return express_1.getRequestTraceContext; } });
|
|
36
|
+
Object.defineProperty(exports, "createPinoHttpOptions", { enumerable: true, get: function () { return express_1.createPinoHttpOptions; } });
|
|
35
37
|
var fastify_1 = require("./http/fastify");
|
|
36
38
|
Object.defineProperty(exports, "createFastifyPlugin", { enumerable: true, get: function () { return fastify_1.createFastifyPlugin; } });
|
|
39
|
+
Object.defineProperty(exports, "getFastifyRequestTraceContext", { enumerable: true, get: function () { return fastify_1.getRequestTraceContext; } });
|
|
37
40
|
var winston_format_1 = require("./logs/winston-format");
|
|
38
41
|
Object.defineProperty(exports, "createWinstonFormat", { enumerable: true, get: function () { return winston_format_1.createWinstonFormat; } });
|
|
39
42
|
var pino_mixin_1 = require("./logs/pino-mixin");
|
|
@@ -100,6 +100,7 @@ function createInertInstance(serviceName) {
|
|
|
100
100
|
}),
|
|
101
101
|
createWinstonTransport: () => ({}),
|
|
102
102
|
createPinoMixin: () => () => ({}),
|
|
103
|
+
createPinoHttpOptions: () => ({ customProps: () => ({}) }),
|
|
103
104
|
createPinoDestination: () => new node_stream_1.Writable({
|
|
104
105
|
write: (_c, _e, cb) => cb(),
|
|
105
106
|
}),
|
|
@@ -339,6 +340,7 @@ function init(serviceName, token, options = {}) {
|
|
|
339
340
|
createWinstonFormat: () => (0, winston_format_1.createWinstonFormat)(),
|
|
340
341
|
createWinstonTransport: () => (0, winston_transport_1.createWinstonTransport)(loggerProvider, serviceName),
|
|
341
342
|
createPinoMixin: () => (0, pino_mixin_1.createPinoMixin)(),
|
|
343
|
+
createPinoHttpOptions: () => (0, express_1.createPinoHttpOptions)(),
|
|
342
344
|
createPinoDestination: () => (0, pino_destination_1.createPinoDestination)(loggerProvider, serviceName),
|
|
343
345
|
buildTraceparent: trace_bridge_1.buildTraceparent,
|
|
344
346
|
injectSnsAttributes: sns_1.injectSnsAttributes,
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
*/
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
37
|
exports.createPinoDestination = createPinoDestination;
|
|
38
|
+
const api_1 = require("@opentelemetry/api");
|
|
38
39
|
const api_logs_1 = require("@opentelemetry/api-logs");
|
|
39
40
|
const node_stream_1 = require("node:stream");
|
|
40
41
|
const PINO_LEVEL_MAP = {
|
|
@@ -95,11 +96,24 @@ function createPinoDestination(loggerProvider, serviceName) {
|
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
}
|
|
99
|
+
// Build an OTel Context carrying the SpanContext so the SDK stamps the
|
|
100
|
+
// native traceId/spanId fields on the exported OTLP LogRecord. Without
|
|
101
|
+
// this, backends can't correlate logs to traces.
|
|
102
|
+
let logContext;
|
|
103
|
+
if (traceId && spanId) {
|
|
104
|
+
logContext = api_1.trace.setSpanContext(api_1.ROOT_CONTEXT, {
|
|
105
|
+
traceId,
|
|
106
|
+
spanId,
|
|
107
|
+
traceFlags: 1,
|
|
108
|
+
isRemote: true,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
98
111
|
otelLogger.emit({
|
|
99
112
|
body: msg,
|
|
100
113
|
severityNumber: severity,
|
|
101
114
|
severityText,
|
|
102
115
|
attributes,
|
|
116
|
+
...(logContext && { context: logContext }),
|
|
103
117
|
});
|
|
104
118
|
}
|
|
105
119
|
catch {
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
* Used by kori (Fastify built-in Pino) and cb-proxy (Pino + pino-http).
|
|
12
12
|
*
|
|
13
13
|
* Edge cases covered:
|
|
14
|
-
* - NR not loaded or no active transaction: returns {
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* - NR not loaded or no active transaction: returns {} so it doesn't
|
|
15
|
+
* overwrite valid trace context injected by pino-http's customProps
|
|
16
|
+
* (Pino serializes mixin fields after customProps — duplicate keys
|
|
17
|
+
* in JSON take the last value).
|
|
18
|
+
* - getTraceContext throws: catch returns {}. Never crashes logging.
|
|
18
19
|
* - Lazy evaluation: the mixin is called at log-write time, not at Pino
|
|
19
20
|
* construction time, so trace context is always current.
|
|
20
21
|
* - Works with pino-pretty in development — fields appear in pretty output.
|
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
* Used by kori (Fastify built-in Pino) and cb-proxy (Pino + pino-http).
|
|
13
13
|
*
|
|
14
14
|
* Edge cases covered:
|
|
15
|
-
* - NR not loaded or no active transaction: returns {
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* - NR not loaded or no active transaction: returns {} so it doesn't
|
|
16
|
+
* overwrite valid trace context injected by pino-http's customProps
|
|
17
|
+
* (Pino serializes mixin fields after customProps — duplicate keys
|
|
18
|
+
* in JSON take the last value).
|
|
19
|
+
* - getTraceContext throws: catch returns {}. Never crashes logging.
|
|
19
20
|
* - Lazy evaluation: the mixin is called at log-write time, not at Pino
|
|
20
21
|
* construction time, so trace context is always current.
|
|
21
22
|
* - Works with pino-pretty in development — fields appear in pretty output.
|
|
@@ -27,6 +28,9 @@ function createPinoMixin() {
|
|
|
27
28
|
return () => {
|
|
28
29
|
try {
|
|
29
30
|
const ctx = (0, trace_bridge_1.getTraceContext)();
|
|
31
|
+
if (!ctx.traceId) {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
30
34
|
return {
|
|
31
35
|
traceId: ctx.traceId,
|
|
32
36
|
spanId: ctx.spanId,
|
|
@@ -34,7 +38,7 @@ function createPinoMixin() {
|
|
|
34
38
|
};
|
|
35
39
|
}
|
|
36
40
|
catch {
|
|
37
|
-
return {
|
|
41
|
+
return {};
|
|
38
42
|
}
|
|
39
43
|
};
|
|
40
44
|
}
|
|
@@ -7,35 +7,37 @@
|
|
|
7
7
|
* propagation works whether NR is installed, partially loaded, or completely
|
|
8
8
|
* removed.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Two resolution modes:
|
|
11
|
+
* resolveActiveTraceIds() — NR → OTel active span → empty strings.
|
|
12
|
+
* Used by getTraceContext() (log enrichment) and createPinoMixin().
|
|
13
|
+
* Never generates IDs — returns empty when no real source exists, so
|
|
14
|
+
* it doesn't overwrite valid IDs injected by pino-http's customProps.
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* - extractParentContext(): parses a traceparent string into an OTel Context
|
|
20
|
-
* with a remote SpanContext. Used by consumers to link their OTel spans
|
|
21
|
-
* back to the producer's trace. Returns ROOT_CONTEXT on malformed input.
|
|
22
|
-
* - getTraceContext(): returns { traceId, spanId, traceparent } for log
|
|
23
|
-
* enrichment. Called lazily at log-write time (not at format creation time)
|
|
24
|
-
* so the trace context is always current.
|
|
16
|
+
* buildTraceparent() — NR → OTel active span → generate fresh IDs.
|
|
17
|
+
* Used by producers (injectSnsAttributes, injectJobData) and the
|
|
18
|
+
* Express/Fastify middleware at request start. Always returns a
|
|
19
|
+
* traceparent so every message/request gets trace context.
|
|
25
20
|
*
|
|
26
21
|
* Edge cases covered:
|
|
27
|
-
* - NR
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* - Malformed traceparent
|
|
31
|
-
*
|
|
32
|
-
* trace instead of crashing.
|
|
33
|
-
* - Flags field always set to 01 (sampled) on buildTraceparent because we
|
|
34
|
-
* want all async boundary crossings traced.
|
|
22
|
+
* - NR's getTraceMetadata() can throw TypeError when the transaction is
|
|
23
|
+
* null (pino-http fires after NR ends the transaction). Guarded with
|
|
24
|
+
* try/catch.
|
|
25
|
+
* - Malformed traceparent: extractParentContext returns ROOT_CONTEXT.
|
|
26
|
+
* - Flags always 01 (sampled) on buildTraceparent.
|
|
35
27
|
*/
|
|
36
28
|
import { type Context } from '@opentelemetry/api';
|
|
37
29
|
import type { TraceContext } from './types';
|
|
30
|
+
/**
|
|
31
|
+
* Always returns a traceparent — from NR, OTel active span, or freshly
|
|
32
|
+
* generated. Used by producers to inject into SNS attributes / job data,
|
|
33
|
+
* and by the Express/Fastify middleware at request start.
|
|
34
|
+
*/
|
|
38
35
|
export declare function buildTraceparent(): string;
|
|
39
36
|
export declare function extractParentContext(traceparent: string): Context;
|
|
37
|
+
/**
|
|
38
|
+
* Returns trace context from active sources only (NR or OTel active span).
|
|
39
|
+
* Returns empty strings when neither is active — never generates fresh IDs.
|
|
40
|
+
* Used for log enrichment where fake IDs would create false correlations.
|
|
41
|
+
*/
|
|
40
42
|
export declare function getTraceContext(): TraceContext;
|
|
41
43
|
export declare function getActiveContext(): Context;
|
|
@@ -8,31 +8,23 @@
|
|
|
8
8
|
* propagation works whether NR is installed, partially loaded, or completely
|
|
9
9
|
* removed.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* Two resolution modes:
|
|
12
|
+
* resolveActiveTraceIds() — NR → OTel active span → empty strings.
|
|
13
|
+
* Used by getTraceContext() (log enrichment) and createPinoMixin().
|
|
14
|
+
* Never generates IDs — returns empty when no real source exists, so
|
|
15
|
+
* it doesn't overwrite valid IDs injected by pino-http's customProps.
|
|
15
16
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* - extractParentContext(): parses a traceparent string into an OTel Context
|
|
21
|
-
* with a remote SpanContext. Used by consumers to link their OTel spans
|
|
22
|
-
* back to the producer's trace. Returns ROOT_CONTEXT on malformed input.
|
|
23
|
-
* - getTraceContext(): returns { traceId, spanId, traceparent } for log
|
|
24
|
-
* enrichment. Called lazily at log-write time (not at format creation time)
|
|
25
|
-
* so the trace context is always current.
|
|
17
|
+
* buildTraceparent() — NR → OTel active span → generate fresh IDs.
|
|
18
|
+
* Used by producers (injectSnsAttributes, injectJobData) and the
|
|
19
|
+
* Express/Fastify middleware at request start. Always returns a
|
|
20
|
+
* traceparent so every message/request gets trace context.
|
|
26
21
|
*
|
|
27
22
|
* Edge cases covered:
|
|
28
|
-
* - NR
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* - Malformed traceparent
|
|
32
|
-
*
|
|
33
|
-
* trace instead of crashing.
|
|
34
|
-
* - Flags field always set to 01 (sampled) on buildTraceparent because we
|
|
35
|
-
* want all async boundary crossings traced.
|
|
23
|
+
* - NR's getTraceMetadata() can throw TypeError when the transaction is
|
|
24
|
+
* null (pino-http fires after NR ends the transaction). Guarded with
|
|
25
|
+
* try/catch.
|
|
26
|
+
* - Malformed traceparent: extractParentContext returns ROOT_CONTEXT.
|
|
27
|
+
* - Flags always 01 (sampled) on buildTraceparent.
|
|
36
28
|
*/
|
|
37
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
30
|
exports.buildTraceparent = buildTraceparent;
|
|
@@ -48,14 +40,22 @@ function generateTraceId() {
|
|
|
48
40
|
function generateSpanId() {
|
|
49
41
|
return (0, node_crypto_1.randomBytes)(8).toString('hex');
|
|
50
42
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Resolves trace IDs from active sources only (NR or OTel active span).
|
|
45
|
+
* Returns empty strings when neither source has an active trace — never
|
|
46
|
+
* generates fresh IDs.
|
|
47
|
+
*/
|
|
48
|
+
function resolveActiveTraceIds() {
|
|
49
|
+
try {
|
|
50
|
+
const nr = (0, nr_1.getNr)();
|
|
51
|
+
const meta = nr.getTraceMetadata?.() ?? { traceId: '', spanId: '' };
|
|
52
|
+
if (meta.traceId && meta.spanId) {
|
|
53
|
+
return { traceId: meta.traceId, spanId: meta.spanId };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// NR transaction is null or getTraceMetadata threw
|
|
57
58
|
}
|
|
58
|
-
// 2. Fall back to OTel's own active span
|
|
59
59
|
const activeSpan = api_1.trace.getActiveSpan();
|
|
60
60
|
if (activeSpan) {
|
|
61
61
|
const sc = activeSpan.spanContext();
|
|
@@ -63,12 +63,19 @@ function resolveTraceIds() {
|
|
|
63
63
|
return { traceId: sc.traceId, spanId: sc.spanId };
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
return { traceId: generateTraceId(), spanId: generateSpanId() };
|
|
66
|
+
return { traceId: '', spanId: '' };
|
|
68
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Always returns a traceparent — from NR, OTel active span, or freshly
|
|
70
|
+
* generated. Used by producers to inject into SNS attributes / job data,
|
|
71
|
+
* and by the Express/Fastify middleware at request start.
|
|
72
|
+
*/
|
|
69
73
|
function buildTraceparent() {
|
|
70
|
-
const { traceId, spanId } =
|
|
71
|
-
|
|
74
|
+
const { traceId, spanId } = resolveActiveTraceIds();
|
|
75
|
+
if (traceId && spanId) {
|
|
76
|
+
return `00-${traceId}-${spanId}-01`;
|
|
77
|
+
}
|
|
78
|
+
return `00-${generateTraceId()}-${generateSpanId()}-01`;
|
|
72
79
|
}
|
|
73
80
|
function extractParentContext(traceparent) {
|
|
74
81
|
const parts = traceparent.split('-');
|
|
@@ -88,8 +95,13 @@ function extractParentContext(traceparent) {
|
|
|
88
95
|
};
|
|
89
96
|
return api_1.trace.setSpanContext(api_1.ROOT_CONTEXT, spanContext);
|
|
90
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Returns trace context from active sources only (NR or OTel active span).
|
|
100
|
+
* Returns empty strings when neither is active — never generates fresh IDs.
|
|
101
|
+
* Used for log enrichment where fake IDs would create false correlations.
|
|
102
|
+
*/
|
|
91
103
|
function getTraceContext() {
|
|
92
|
-
const { traceId, spanId } =
|
|
104
|
+
const { traceId, spanId } = resolveActiveTraceIds();
|
|
93
105
|
const traceparent = traceId && spanId ? `00-${traceId}-${spanId}-01` : '';
|
|
94
106
|
return { traceId, spanId, traceparent };
|
|
95
107
|
}
|
|
@@ -61,6 +61,9 @@ export interface FoamInstance {
|
|
|
61
61
|
createWinstonFormat(): WinstonFormat;
|
|
62
62
|
createWinstonTransport(): WinstonTransport;
|
|
63
63
|
createPinoMixin(): () => Record<string, string>;
|
|
64
|
+
createPinoHttpOptions(): {
|
|
65
|
+
customProps: (req: any) => Record<string, string>;
|
|
66
|
+
};
|
|
64
67
|
createPinoDestination(): NodeJS.WritableStream;
|
|
65
68
|
buildTraceparent(): string;
|
|
66
69
|
injectSnsAttributes(attrs: Record<string, SnsMessageAttributeValue>): Record<string, SnsMessageAttributeValue>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foam-ai/node-cliengo",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.9",
|
|
4
4
|
"description": "Unified observability (traces, logs, metrics) for Cliengo Node.js services, connecting New Relic APM with Foam's OTel collector.",
|
|
5
5
|
"main": "dist/node-cliengo/src/index.js",
|
|
6
6
|
"types": "dist/node-cliengo/src/index.d.ts",
|