@foam-ai/node-cliengo 0.1.0-alpha.3 → 0.1.0-alpha.6
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/init.js +3 -3
- 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 +20 -9
- 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 +16 -14
- package/dist/node-cliengo/src/trace-bridge.js +40 -21
- package/dist/node-cliengo/src/types.d.ts +2 -2
- package/package.json +1 -1
|
@@ -103,9 +103,9 @@ function createInertInstance(serviceName) {
|
|
|
103
103
|
createPinoDestination: () => new node_stream_1.Writable({
|
|
104
104
|
write: (_c, _e, cb) => cb(),
|
|
105
105
|
}),
|
|
106
|
-
buildTraceparent:
|
|
107
|
-
injectSnsAttributes:
|
|
108
|
-
injectJobData:
|
|
106
|
+
buildTraceparent: trace_bridge_1.buildTraceparent,
|
|
107
|
+
injectSnsAttributes: sns_1.injectSnsAttributes,
|
|
108
|
+
injectJobData: job_1.injectJobData,
|
|
109
109
|
wrapSqsConsumer: async (_t, _s, _m, fn) => fn(),
|
|
110
110
|
wrapJobConsumer: async (_t, _s, _j, fn) => fn(),
|
|
111
111
|
extractParentContext: () => {
|
|
@@ -26,6 +26,6 @@
|
|
|
26
26
|
import { type Tracer } from '@opentelemetry/api';
|
|
27
27
|
import type { FoamMetrics } from './types';
|
|
28
28
|
export declare function injectJobData<T extends Record<string, unknown>>(data: T): T & {
|
|
29
|
-
traceparent
|
|
29
|
+
traceparent: string;
|
|
30
30
|
};
|
|
31
31
|
export declare function wrapJobConsumer(tracer: Tracer, spanName: string, jobData: Record<string, unknown>, fn: () => Promise<void>, metrics?: FoamMetrics): Promise<void>;
|
|
@@ -31,11 +31,7 @@ const api_1 = require("@opentelemetry/api");
|
|
|
31
31
|
const nr_1 = require("./nr");
|
|
32
32
|
const trace_bridge_1 = require("./trace-bridge");
|
|
33
33
|
function injectJobData(data) {
|
|
34
|
-
|
|
35
|
-
if (!tp) {
|
|
36
|
-
return data;
|
|
37
|
-
}
|
|
38
|
-
return { ...data, traceparent: tp };
|
|
34
|
+
return { ...data, traceparent: (0, trace_bridge_1.buildTraceparent)() };
|
|
39
35
|
}
|
|
40
36
|
async function wrapJobConsumer(tracer, spanName, jobData, fn, metrics) {
|
|
41
37
|
const nr = (0, nr_1.getNr)();
|
|
@@ -21,8 +21,13 @@
|
|
|
21
21
|
* 60→FATAL. Unknown numeric levels default to INFO.
|
|
22
22
|
* - Pino uses `msg` for the message field (not `message` like Winston). We
|
|
23
23
|
* check both for compatibility with custom Pino configs.
|
|
24
|
-
* -
|
|
25
|
-
*
|
|
24
|
+
* - Trace context (traceId, spanId, traceparent) is read from the parsed
|
|
25
|
+
* JSON line, NOT from getTraceContext(). The pino mixin injects these at
|
|
26
|
+
* log-write time (when NR's transaction is still active). The stream write
|
|
27
|
+
* happens asynchronously — by then NR's transaction has ended, so calling
|
|
28
|
+
* getTraceContext() here would return empty strings.
|
|
29
|
+
* - Internal Pino fields (pid, hostname, time) and trace fields (traceId,
|
|
30
|
+
* spanId, traceparent) are excluded from forwarded attributes.
|
|
26
31
|
* - Callback always called — never blocks the Pino stream pipeline.
|
|
27
32
|
* - Buffer chunks: converted to string before parsing (handles both Buffer
|
|
28
33
|
* and string inputs from Node streams).
|
|
@@ -22,8 +22,13 @@
|
|
|
22
22
|
* 60→FATAL. Unknown numeric levels default to INFO.
|
|
23
23
|
* - Pino uses `msg` for the message field (not `message` like Winston). We
|
|
24
24
|
* check both for compatibility with custom Pino configs.
|
|
25
|
-
* -
|
|
26
|
-
*
|
|
25
|
+
* - Trace context (traceId, spanId, traceparent) is read from the parsed
|
|
26
|
+
* JSON line, NOT from getTraceContext(). The pino mixin injects these at
|
|
27
|
+
* log-write time (when NR's transaction is still active). The stream write
|
|
28
|
+
* happens asynchronously — by then NR's transaction has ended, so calling
|
|
29
|
+
* getTraceContext() here would return empty strings.
|
|
30
|
+
* - Internal Pino fields (pid, hostname, time) and trace fields (traceId,
|
|
31
|
+
* spanId, traceparent) are excluded from forwarded attributes.
|
|
27
32
|
* - Callback always called — never blocks the Pino stream pipeline.
|
|
28
33
|
* - Buffer chunks: converted to string before parsing (handles both Buffer
|
|
29
34
|
* and string inputs from Node streams).
|
|
@@ -32,7 +37,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
32
37
|
exports.createPinoDestination = createPinoDestination;
|
|
33
38
|
const api_logs_1 = require("@opentelemetry/api-logs");
|
|
34
39
|
const node_stream_1 = require("node:stream");
|
|
35
|
-
const trace_bridge_1 = require("../trace-bridge");
|
|
36
40
|
const PINO_LEVEL_MAP = {
|
|
37
41
|
10: api_logs_1.SeverityNumber.TRACE,
|
|
38
42
|
20: api_logs_1.SeverityNumber.DEBUG,
|
|
@@ -66,16 +70,23 @@ function createPinoDestination(loggerProvider, serviceName) {
|
|
|
66
70
|
const msg = parsed.msg ?? parsed.message ?? '';
|
|
67
71
|
const severity = PINO_LEVEL_MAP[level] ?? api_logs_1.SeverityNumber.INFO;
|
|
68
72
|
const severityText = PINO_LEVEL_TEXT[level] ?? 'INFO';
|
|
69
|
-
|
|
73
|
+
// Read trace context from the parsed JSON line — the pino mixin already
|
|
74
|
+
// injected traceId/spanId/traceparent at log-write time (when the NR
|
|
75
|
+
// transaction was still active). Calling getTraceContext() here would be
|
|
76
|
+
// too late — the stream write happens asynchronously after NR's
|
|
77
|
+
// transaction has ended.
|
|
78
|
+
const traceId = parsed.traceId ?? '';
|
|
79
|
+
const spanId = parsed.spanId ?? '';
|
|
70
80
|
const attributes = {};
|
|
71
|
-
if (
|
|
72
|
-
attributes['trace.id'] =
|
|
81
|
+
if (traceId) {
|
|
82
|
+
attributes['trace.id'] = traceId;
|
|
73
83
|
}
|
|
74
|
-
if (
|
|
75
|
-
attributes['span.id'] =
|
|
84
|
+
if (spanId) {
|
|
85
|
+
attributes['span.id'] = spanId;
|
|
76
86
|
}
|
|
87
|
+
const SKIP_KEYS = new Set(['level', 'msg', 'message', 'time', 'pid', 'hostname', 'traceId', 'spanId', 'traceparent']);
|
|
77
88
|
for (const [key, val] of Object.entries(parsed)) {
|
|
78
|
-
if (key
|
|
89
|
+
if (!SKIP_KEYS.has(key)) {
|
|
79
90
|
try {
|
|
80
91
|
attributes[key] = typeof val === 'string' ? val : JSON.stringify(val);
|
|
81
92
|
}
|
|
@@ -8,10 +8,7 @@
|
|
|
8
8
|
* alongside the existing ones. This function spread-merges it in.
|
|
9
9
|
*
|
|
10
10
|
* Edge cases covered:
|
|
11
|
-
* -
|
|
12
|
-
* original attributes are returned unchanged — no traceparent key added.
|
|
13
|
-
* - Empty attrs {}: returns just { traceparent: ... } if NR is active,
|
|
14
|
-
* or {} if not. Never crashes.
|
|
11
|
+
* - Empty attrs {}: returns { traceparent: ... }. Never crashes.
|
|
15
12
|
* - Existing traceparent in attrs: overwritten with the current value
|
|
16
13
|
* (spread puts our key last).
|
|
17
14
|
*/
|
|
@@ -9,10 +9,7 @@
|
|
|
9
9
|
* alongside the existing ones. This function spread-merges it in.
|
|
10
10
|
*
|
|
11
11
|
* Edge cases covered:
|
|
12
|
-
* -
|
|
13
|
-
* original attributes are returned unchanged — no traceparent key added.
|
|
14
|
-
* - Empty attrs {}: returns just { traceparent: ... } if NR is active,
|
|
15
|
-
* or {} if not. Never crashes.
|
|
12
|
+
* - Empty attrs {}: returns { traceparent: ... }. Never crashes.
|
|
16
13
|
* - Existing traceparent in attrs: overwritten with the current value
|
|
17
14
|
* (spread puts our key last).
|
|
18
15
|
*/
|
|
@@ -20,12 +17,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
20
17
|
exports.injectSnsAttributes = injectSnsAttributes;
|
|
21
18
|
const trace_bridge_1 = require("./trace-bridge");
|
|
22
19
|
function injectSnsAttributes(attrs) {
|
|
23
|
-
const tp = (0, trace_bridge_1.buildTraceparent)();
|
|
24
|
-
if (!tp) {
|
|
25
|
-
return attrs;
|
|
26
|
-
}
|
|
27
20
|
return {
|
|
28
21
|
...attrs,
|
|
29
|
-
traceparent: { DataType: 'String', StringValue:
|
|
22
|
+
traceparent: { DataType: 'String', StringValue: (0, trace_bridge_1.buildTraceparent)() },
|
|
30
23
|
};
|
|
31
24
|
}
|
|
@@ -2,16 +2,20 @@
|
|
|
2
2
|
* W3C trace context utilities for New Relic and OpenTelemetry interop.
|
|
3
3
|
*
|
|
4
4
|
* Why this file exists:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Reads trace context from two sources (NR first, then OTel's own active
|
|
6
|
+
* span) and converts it to/from W3C traceparent format. This means trace
|
|
7
|
+
* propagation works whether NR is installed, partially loaded, or completely
|
|
8
|
+
* removed.
|
|
9
|
+
*
|
|
10
|
+
* Resolution order (buildTraceparent / getTraceContext):
|
|
11
|
+
* 1. NR's getTraceMetadata() — if NR is loaded and has an active transaction
|
|
12
|
+
* 2. OTel's active span context — if our own tracer has an active span
|
|
13
|
+
* 3. Generate fresh traceId (16 bytes) + spanId (8 bytes) — always has context
|
|
9
14
|
*
|
|
10
15
|
* Functions:
|
|
11
|
-
* - buildTraceparent():
|
|
12
|
-
*
|
|
13
|
-
* BullMQ/Bull job data.
|
|
14
|
-
* active transaction exists.
|
|
16
|
+
* - buildTraceparent(): always returns a W3C traceparent string — from NR,
|
|
17
|
+
* OTel, or freshly generated. Used by producers to inject into SNS
|
|
18
|
+
* attributes or BullMQ/Bull job data.
|
|
15
19
|
* - extractParentContext(): parses a traceparent string into an OTel Context
|
|
16
20
|
* with a remote SpanContext. Used by consumers to link their OTel spans
|
|
17
21
|
* back to the producer's trace. Returns ROOT_CONTEXT on malformed input.
|
|
@@ -20,11 +24,9 @@
|
|
|
20
24
|
* so the trace context is always current.
|
|
21
25
|
*
|
|
22
26
|
* Edge cases covered:
|
|
23
|
-
* - NR not loaded:
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* - NR loaded but no active transaction: traceId/spanId are empty strings.
|
|
27
|
-
* buildTraceparent returns undefined. Consumers create a fresh trace.
|
|
27
|
+
* - NR not loaded: falls back to OTel active span, then to generated IDs.
|
|
28
|
+
* - NR loaded but no active transaction: same fallback chain.
|
|
29
|
+
* - NR removed entirely: OTel active span or generated IDs. Always works.
|
|
28
30
|
* - Malformed traceparent (wrong number of parts, wrong hex lengths):
|
|
29
31
|
* extractParentContext returns ROOT_CONTEXT — consumer creates a fresh
|
|
30
32
|
* trace instead of crashing.
|
|
@@ -33,7 +35,7 @@
|
|
|
33
35
|
*/
|
|
34
36
|
import { type Context } from '@opentelemetry/api';
|
|
35
37
|
import type { TraceContext } from './types';
|
|
36
|
-
export declare function buildTraceparent(): string
|
|
38
|
+
export declare function buildTraceparent(): string;
|
|
37
39
|
export declare function extractParentContext(traceparent: string): Context;
|
|
38
40
|
export declare function getTraceContext(): TraceContext;
|
|
39
41
|
export declare function getActiveContext(): Context;
|
|
@@ -3,16 +3,20 @@
|
|
|
3
3
|
* W3C trace context utilities for New Relic and OpenTelemetry interop.
|
|
4
4
|
*
|
|
5
5
|
* Why this file exists:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* Reads trace context from two sources (NR first, then OTel's own active
|
|
7
|
+
* span) and converts it to/from W3C traceparent format. This means trace
|
|
8
|
+
* propagation works whether NR is installed, partially loaded, or completely
|
|
9
|
+
* removed.
|
|
10
|
+
*
|
|
11
|
+
* Resolution order (buildTraceparent / getTraceContext):
|
|
12
|
+
* 1. NR's getTraceMetadata() — if NR is loaded and has an active transaction
|
|
13
|
+
* 2. OTel's active span context — if our own tracer has an active span
|
|
14
|
+
* 3. Generate fresh traceId (16 bytes) + spanId (8 bytes) — always has context
|
|
10
15
|
*
|
|
11
16
|
* Functions:
|
|
12
|
-
* - buildTraceparent():
|
|
13
|
-
*
|
|
14
|
-
* BullMQ/Bull job data.
|
|
15
|
-
* active transaction exists.
|
|
17
|
+
* - buildTraceparent(): always returns a W3C traceparent string — from NR,
|
|
18
|
+
* OTel, or freshly generated. Used by producers to inject into SNS
|
|
19
|
+
* attributes or BullMQ/Bull job data.
|
|
16
20
|
* - extractParentContext(): parses a traceparent string into an OTel Context
|
|
17
21
|
* with a remote SpanContext. Used by consumers to link their OTel spans
|
|
18
22
|
* back to the producer's trace. Returns ROOT_CONTEXT on malformed input.
|
|
@@ -21,11 +25,9 @@
|
|
|
21
25
|
* so the trace context is always current.
|
|
22
26
|
*
|
|
23
27
|
* Edge cases covered:
|
|
24
|
-
* - NR not loaded:
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* - NR loaded but no active transaction: traceId/spanId are empty strings.
|
|
28
|
-
* buildTraceparent returns undefined. Consumers create a fresh trace.
|
|
28
|
+
* - NR not loaded: falls back to OTel active span, then to generated IDs.
|
|
29
|
+
* - NR loaded but no active transaction: same fallback chain.
|
|
30
|
+
* - NR removed entirely: OTel active span or generated IDs. Always works.
|
|
29
31
|
* - Malformed traceparent (wrong number of parts, wrong hex lengths):
|
|
30
32
|
* extractParentContext returns ROOT_CONTEXT — consumer creates a fresh
|
|
31
33
|
* trace instead of crashing.
|
|
@@ -37,15 +39,35 @@ exports.buildTraceparent = buildTraceparent;
|
|
|
37
39
|
exports.extractParentContext = extractParentContext;
|
|
38
40
|
exports.getTraceContext = getTraceContext;
|
|
39
41
|
exports.getActiveContext = getActiveContext;
|
|
42
|
+
const node_crypto_1 = require("node:crypto");
|
|
40
43
|
const api_1 = require("@opentelemetry/api");
|
|
41
44
|
const nr_1 = require("./nr");
|
|
42
|
-
function
|
|
45
|
+
function generateTraceId() {
|
|
46
|
+
return (0, node_crypto_1.randomBytes)(16).toString('hex');
|
|
47
|
+
}
|
|
48
|
+
function generateSpanId() {
|
|
49
|
+
return (0, node_crypto_1.randomBytes)(8).toString('hex');
|
|
50
|
+
}
|
|
51
|
+
function resolveTraceIds() {
|
|
52
|
+
// 1. Try NR
|
|
43
53
|
const nr = (0, nr_1.getNr)();
|
|
44
54
|
const meta = nr.getTraceMetadata?.() ?? { traceId: '', spanId: '' };
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
if (meta.traceId && meta.spanId) {
|
|
56
|
+
return { traceId: meta.traceId, spanId: meta.spanId };
|
|
57
|
+
}
|
|
58
|
+
// 2. Fall back to OTel's own active span
|
|
59
|
+
const activeSpan = api_1.trace.getActiveSpan();
|
|
60
|
+
if (activeSpan) {
|
|
61
|
+
const sc = activeSpan.spanContext();
|
|
62
|
+
if (sc.traceId && sc.spanId) {
|
|
63
|
+
return { traceId: sc.traceId, spanId: sc.spanId };
|
|
64
|
+
}
|
|
48
65
|
}
|
|
66
|
+
// 3. Generate our own — ensures trace context is always available
|
|
67
|
+
return { traceId: generateTraceId(), spanId: generateSpanId() };
|
|
68
|
+
}
|
|
69
|
+
function buildTraceparent() {
|
|
70
|
+
const { traceId, spanId } = resolveTraceIds();
|
|
49
71
|
return `00-${traceId}-${spanId}-01`;
|
|
50
72
|
}
|
|
51
73
|
function extractParentContext(traceparent) {
|
|
@@ -67,10 +89,7 @@ function extractParentContext(traceparent) {
|
|
|
67
89
|
return api_1.trace.setSpanContext(api_1.ROOT_CONTEXT, spanContext);
|
|
68
90
|
}
|
|
69
91
|
function getTraceContext() {
|
|
70
|
-
const
|
|
71
|
-
const meta = nr.getTraceMetadata?.() ?? { traceId: '', spanId: '' };
|
|
72
|
-
const traceId = meta.traceId ?? '';
|
|
73
|
-
const spanId = meta.spanId ?? '';
|
|
92
|
+
const { traceId, spanId } = resolveTraceIds();
|
|
74
93
|
const traceparent = traceId && spanId ? `00-${traceId}-${spanId}-01` : '';
|
|
75
94
|
return { traceId, spanId, traceparent };
|
|
76
95
|
}
|
|
@@ -62,10 +62,10 @@ export interface FoamInstance {
|
|
|
62
62
|
createWinstonTransport(): WinstonTransport;
|
|
63
63
|
createPinoMixin(): () => Record<string, string>;
|
|
64
64
|
createPinoDestination(): NodeJS.WritableStream;
|
|
65
|
-
buildTraceparent(): string
|
|
65
|
+
buildTraceparent(): string;
|
|
66
66
|
injectSnsAttributes(attrs: Record<string, SnsMessageAttributeValue>): Record<string, SnsMessageAttributeValue>;
|
|
67
67
|
injectJobData<T extends Record<string, unknown>>(data: T): T & {
|
|
68
|
-
traceparent
|
|
68
|
+
traceparent: string;
|
|
69
69
|
};
|
|
70
70
|
wrapSqsConsumer(tracer: Tracer, spanName: string, msg: SqsMessage, fn: () => Promise<void>): Promise<void>;
|
|
71
71
|
wrapJobConsumer(tracer: Tracer, spanName: string, jobData: Record<string, unknown>, fn: () => Promise<void>): Promise<void>;
|
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.6",
|
|
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",
|