logixia 1.12.6 → 1.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -4
- package/dist/{async-context-CwBNMm2E.mjs → async-context-BzJrbfQs.mjs} +8 -4
- package/dist/async-context-BzJrbfQs.mjs.map +1 -0
- package/dist/{async-context-CSHWoHMB.js → async-context-DyQQYq3I.js} +8 -4
- package/dist/async-context-DyQQYq3I.js.map +1 -0
- package/dist/{azure-monitor.transport-CG7ClkYj.d.ts → azure-monitor.transport-BDFO4F-i.d.ts} +2 -2
- package/dist/{azure-monitor.transport-CG7ClkYj.d.ts.map → azure-monitor.transport-BDFO4F-i.d.ts.map} +1 -1
- package/dist/{azure-monitor.transport-Cm74kXZS.d.mts → azure-monitor.transport-D5wQEH04.d.mts} +2 -2
- package/dist/{azure-monitor.transport-Cm74kXZS.d.mts.map → azure-monitor.transport-D5wQEH04.d.mts.map} +1 -1
- package/dist/azure.d.mts +2 -2
- package/dist/azure.d.ts +2 -2
- package/dist/cli/index.js +98 -52
- package/dist/cloudwatch.d.mts +2 -2
- package/dist/cloudwatch.d.ts +2 -2
- package/dist/cloudwatch.js +2 -1
- package/dist/cloudwatch.mjs +2 -1
- package/dist/{cloudwatch.transport-CGHY4buE.d.mts → cloudwatch.transport-B00st6Xv.d.ts} +9 -2
- package/dist/cloudwatch.transport-B00st6Xv.d.ts.map +1 -0
- package/dist/{cloudwatch.transport-pNBNWJLt.js → cloudwatch.transport-BO1ahxw-.js} +26 -19
- package/dist/cloudwatch.transport-BO1ahxw-.js.map +1 -0
- package/dist/{cloudwatch.transport-DK_8cKd3.d.ts → cloudwatch.transport-Blc7Gtxt.d.mts} +9 -2
- package/dist/cloudwatch.transport-Blc7Gtxt.d.mts.map +1 -0
- package/dist/{cloudwatch.transport-yBwm4OeO.mjs → cloudwatch.transport-DZ6ies7g.mjs} +26 -19
- package/dist/cloudwatch.transport-DZ6ies7g.mjs.map +1 -0
- package/dist/{coerce.utils-BN980Y9V.js → coerce.utils-B-LilukN.js} +1 -1
- package/dist/{coerce.utils-BN980Y9V.js.map → coerce.utils-B-LilukN.js.map} +1 -1
- package/dist/{coerce.utils-Dxv4_ikC.mjs → coerce.utils-Cu6IUTgX.mjs} +1 -1
- package/dist/{coerce.utils-Dxv4_ikC.mjs.map → coerce.utils-Cu6IUTgX.mjs.map} +1 -1
- package/dist/correlation.d.mts +31 -6
- package/dist/correlation.d.mts.map +1 -1
- package/dist/correlation.d.ts +31 -6
- package/dist/correlation.d.ts.map +1 -1
- package/dist/correlation.js +45 -20
- package/dist/correlation.js.map +1 -1
- package/dist/correlation.mjs +45 -20
- package/dist/correlation.mjs.map +1 -1
- package/dist/entry-fields-BJkuPnIg.mjs +30 -0
- package/dist/entry-fields-BJkuPnIg.mjs.map +1 -0
- package/dist/entry-fields-BvOLFlJ4.js +36 -0
- package/dist/entry-fields-BvOLFlJ4.js.map +1 -0
- package/dist/gcp.d.mts +2 -2
- package/dist/gcp.d.ts +2 -2
- package/dist/gcp.js +2 -1
- package/dist/gcp.mjs +2 -1
- package/dist/{gcp.transport-SHdtc1n0.js → gcp.transport-CHV8MGgz.js} +5 -5
- package/dist/gcp.transport-CHV8MGgz.js.map +1 -0
- package/dist/{gcp.transport-BJuNRFe_.mjs → gcp.transport-DBX5TPIy.mjs} +5 -5
- package/dist/gcp.transport-DBX5TPIy.mjs.map +1 -0
- package/dist/{gcp.transport-BOhkVKlI.d.ts → gcp.transport-m4fVw0b1.d.ts} +2 -2
- package/dist/gcp.transport-m4fVw0b1.d.ts.map +1 -0
- package/dist/{gcp.transport-Bi6CUqZR.d.mts → gcp.transport-zzNyIoih.d.mts} +2 -2
- package/dist/gcp.transport-zzNyIoih.d.mts.map +1 -0
- package/dist/{index-BPKwwERo.d.mts → index-BPwEisy4.d.mts} +2 -2
- package/dist/{index-BPKwwERo.d.mts.map → index-BPwEisy4.d.mts.map} +1 -1
- package/dist/{index-C1X-TE5I.d.ts → index-nZEeCr3X.d.ts} +2 -2
- package/dist/{index-C1X-TE5I.d.ts.map → index-nZEeCr3X.d.ts.map} +1 -1
- package/dist/index.d.mts +63 -98
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +63 -98
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +167 -443
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +98 -377
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.d.mts +11 -4
- package/dist/middleware.d.mts.map +1 -1
- package/dist/middleware.d.ts +11 -4
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +148 -24
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +148 -24
- package/dist/middleware.mjs.map +1 -1
- package/dist/nest.d.mts +4 -4
- package/dist/nest.d.ts +4 -4
- package/dist/nest.js +20 -11
- package/dist/nest.mjs +7 -5
- package/dist/{logitron-logger.module-BsDZR5BH.js → nestjs-extras-DnJ6vrIC.js} +562 -444
- package/dist/nestjs-extras-DnJ6vrIC.js.map +1 -0
- package/dist/{logitron-logger.module-Bs3XYZgF.mjs → nestjs-extras-ImbuDhs1.mjs} +505 -349
- package/dist/nestjs-extras-ImbuDhs1.mjs.map +1 -0
- package/dist/{logitron-logger.module-zDKVd_o0.d.mts → nestjs-extras-OkC796py.d.mts} +113 -6
- package/dist/nestjs-extras-OkC796py.d.mts.map +1 -0
- package/dist/{logitron-logger.module-CxT1F6Cm.d.ts → nestjs-extras-mKrHE9S6.d.ts} +113 -6
- package/dist/nestjs-extras-mKrHE9S6.d.ts.map +1 -0
- package/dist/{search-njTI2hdo.js → search-B0MH1SJJ.js} +23 -10
- package/dist/search-B0MH1SJJ.js.map +1 -0
- package/dist/{search-DujhR94O.d.ts → search-BfBzDIT9.d.mts} +2 -3
- package/dist/search-BfBzDIT9.d.mts.map +1 -0
- package/dist/{search-CQOo-Iq6.d.mts → search-CHUZPl0h.d.ts} +2 -3
- package/dist/search-CHUZPl0h.d.ts.map +1 -0
- package/dist/{search-CFQRISUv.mjs → search-DjXSn6v5.mjs} +23 -10
- package/dist/search-DjXSn6v5.mjs.map +1 -0
- package/dist/search.d.mts +3 -3
- package/dist/search.d.ts +3 -3
- package/dist/search.js +2 -2
- package/dist/search.mjs +2 -2
- package/dist/testing.d.mts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/trace.utils-CvXgaiQT.mjs +278 -0
- package/dist/trace.utils-CvXgaiQT.mjs.map +1 -0
- package/dist/trace.utils-DpBOLDtq.js +358 -0
- package/dist/trace.utils-DpBOLDtq.js.map +1 -0
- package/dist/{transport.manager-D6KL3t9V.mjs → transport.manager-CpdVHEGP.mjs} +365 -86
- package/dist/transport.manager-CpdVHEGP.mjs.map +1 -0
- package/dist/{transport.manager-BivyUTtU.js → transport.manager-Crau3Rle.js} +370 -85
- package/dist/transport.manager-Crau3Rle.js.map +1 -0
- package/dist/{transport.types-B5vcyv2k.d.mts → transport.types-CtNr3D7S.d.mts} +19 -2
- package/dist/transport.types-CtNr3D7S.d.mts.map +1 -0
- package/dist/{transport.types-BuY84TwO.d.ts → transport.types-DqVLZTyf.d.ts} +19 -2
- package/dist/transport.types-DqVLZTyf.d.ts.map +1 -0
- package/dist/transports.d.mts +38 -13
- package/dist/transports.d.mts.map +1 -1
- package/dist/transports.d.ts +38 -13
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +6 -5
- package/dist/transports.mjs +6 -5
- package/dist/worker-entry.js +307 -55
- package/dist/worker.d.mts +2 -2
- package/dist/worker.d.ts +2 -2
- package/dist/worker.js +1 -1
- package/dist/worker.mjs +1 -1
- package/dist/{worker.transport-BP8Q28Z2.d.mts → worker.transport-Bgv9heDI.d.mts} +2 -2
- package/dist/{worker.transport-BP8Q28Z2.d.mts.map → worker.transport-Bgv9heDI.d.mts.map} +1 -1
- package/dist/{worker.transport-mKXgNK0L.d.ts → worker.transport-Bj-i0P9V.d.ts} +2 -2
- package/dist/{worker.transport-mKXgNK0L.d.ts.map → worker.transport-Bj-i0P9V.d.ts.map} +1 -1
- package/dist/{worker.transport-CfH6z37Z.js → worker.transport-JP8YHBuU.js} +1 -1
- package/dist/{worker.transport-CfH6z37Z.js.map → worker.transport-JP8YHBuU.js.map} +1 -1
- package/dist/{worker.transport-BhMyvqQ2.mjs → worker.transport-otwhJ_Et.mjs} +1 -1
- package/dist/{worker.transport-BhMyvqQ2.mjs.map → worker.transport-otwhJ_Et.mjs.map} +1 -1
- package/package.json +10 -6
- package/dist/.tsbuildinfo +0 -1
- package/dist/async-context-CSHWoHMB.js.map +0 -1
- package/dist/async-context-CwBNMm2E.mjs.map +0 -1
- package/dist/cloudwatch.transport-CGHY4buE.d.mts.map +0 -1
- package/dist/cloudwatch.transport-DK_8cKd3.d.ts.map +0 -1
- package/dist/cloudwatch.transport-pNBNWJLt.js.map +0 -1
- package/dist/cloudwatch.transport-yBwm4OeO.mjs.map +0 -1
- package/dist/gcp.transport-BJuNRFe_.mjs.map +0 -1
- package/dist/gcp.transport-BOhkVKlI.d.ts.map +0 -1
- package/dist/gcp.transport-Bi6CUqZR.d.mts.map +0 -1
- package/dist/gcp.transport-SHdtc1n0.js.map +0 -1
- package/dist/logitron-logger.module-Bs3XYZgF.mjs.map +0 -1
- package/dist/logitron-logger.module-BsDZR5BH.js.map +0 -1
- package/dist/logitron-logger.module-CxT1F6Cm.d.ts.map +0 -1
- package/dist/logitron-logger.module-zDKVd_o0.d.mts.map +0 -1
- package/dist/search-CFQRISUv.mjs.map +0 -1
- package/dist/search-CQOo-Iq6.d.mts.map +0 -1
- package/dist/search-DujhR94O.d.ts.map +0 -1
- package/dist/search-njTI2hdo.js.map +0 -1
- package/dist/transport.manager-BivyUTtU.js.map +0 -1
- package/dist/transport.manager-D6KL3t9V.mjs.map +0 -1
- package/dist/transport.types-B5vcyv2k.d.mts.map +0 -1
- package/dist/transport.types-BuY84TwO.d.ts.map +0 -1
- package/scripts/postinstall.js +0 -61
package/dist/correlation.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-nOFOJqeH.js');
|
|
2
|
-
const
|
|
2
|
+
const require_trace_utils = require('./trace.utils-DpBOLDtq.js');
|
|
3
|
+
const require_async_context = require('./async-context-DyQQYq3I.js');
|
|
3
4
|
let node_crypto = require("node:crypto");
|
|
4
5
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
5
6
|
|
|
@@ -18,8 +19,6 @@ function generateCorrelationId() {
|
|
|
18
19
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
19
20
|
return (0, node_crypto.randomUUID)();
|
|
20
21
|
}
|
|
21
|
-
const MAX_INCOMING_ID_LENGTH = 128;
|
|
22
|
-
const VALID_INCOMING_ID = /^[A-Za-z0-9._:-]+$/;
|
|
23
22
|
/**
|
|
24
23
|
* Return an incoming correlation ID only if it is short and made of safe
|
|
25
24
|
* characters. Anything else (over-long, empty, or containing newlines or other
|
|
@@ -27,8 +26,8 @@ const VALID_INCOMING_ID = /^[A-Za-z0-9._:-]+$/;
|
|
|
27
26
|
* writing client-supplied text into every log line.
|
|
28
27
|
*/
|
|
29
28
|
function validIncomingId(value) {
|
|
30
|
-
if (value ===
|
|
31
|
-
return
|
|
29
|
+
if (typeof value === "string" && value.trim() !== value) return void 0;
|
|
30
|
+
return require_trace_utils.sanitizeIncomingId(value);
|
|
32
31
|
}
|
|
33
32
|
/**
|
|
34
33
|
* Return the correlation ID from the current `LogixiaContext`, or `undefined`
|
|
@@ -69,11 +68,11 @@ function withCorrelationId(correlationId, callback) {
|
|
|
69
68
|
* ```
|
|
70
69
|
*/
|
|
71
70
|
function correlationMiddleware(options = {}) {
|
|
72
|
-
const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, setResponseHeader = true } = options;
|
|
71
|
+
const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, trustIncoming = true, setResponseHeader = true } = options;
|
|
73
72
|
return function logixiaCorrelationMiddleware(req, res, next) {
|
|
74
73
|
const headers = req["headers"] ?? {};
|
|
75
|
-
const correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();
|
|
76
|
-
const originService = headers[originServiceHeader.toLowerCase()];
|
|
74
|
+
const correlationId = (trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : void 0) ?? generate();
|
|
75
|
+
const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);
|
|
77
76
|
if (setResponseHeader) {
|
|
78
77
|
const setHeader = res["setHeader"];
|
|
79
78
|
if (typeof setHeader === "function") setHeader.call(res, header, correlationId);
|
|
@@ -96,11 +95,11 @@ function correlationMiddleware(options = {}) {
|
|
|
96
95
|
* ```
|
|
97
96
|
*/
|
|
98
97
|
function correlationFastifyHook(options = {}) {
|
|
99
|
-
const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, setResponseHeader = true } = options;
|
|
98
|
+
const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, trustIncoming = true, setResponseHeader = true } = options;
|
|
100
99
|
return function logixiaCorrelationFastifyHook(request, reply, done) {
|
|
101
100
|
const headers = request["headers"] ?? {};
|
|
102
|
-
const correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();
|
|
103
|
-
const originService = headers[originServiceHeader.toLowerCase()];
|
|
101
|
+
const correlationId = (trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : void 0) ?? generate();
|
|
102
|
+
const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);
|
|
104
103
|
if (setResponseHeader) {
|
|
105
104
|
const replyHeader = reply["header"];
|
|
106
105
|
if (typeof replyHeader === "function") replyHeader.call(reply, header, correlationId);
|
|
@@ -165,12 +164,16 @@ function createCorrelationAxiosInterceptor(options = {}) {
|
|
|
165
164
|
};
|
|
166
165
|
};
|
|
167
166
|
}
|
|
167
|
+
const MAX_USER_AGENT_LENGTH = 512;
|
|
168
|
+
const MAX_IP_LENGTH = 64;
|
|
168
169
|
/**
|
|
169
170
|
* Create a child logger enriched with all correlation-relevant identifiers
|
|
170
171
|
* extracted from the incoming HTTP request.
|
|
171
172
|
*
|
|
172
173
|
* The child logger's every call automatically includes:
|
|
173
|
-
* `correlationId`, `
|
|
174
|
+
* `correlationId`, `method`, `url`, `userAgent`, `ip`
|
|
175
|
+
*
|
|
176
|
+
* The IP comes from `req.ip` / the socket address unless `trustProxy` is set.
|
|
174
177
|
*
|
|
175
178
|
* @example
|
|
176
179
|
* ```ts
|
|
@@ -188,21 +191,43 @@ function createCorrelationAxiosInterceptor(options = {}) {
|
|
|
188
191
|
* ```
|
|
189
192
|
*/
|
|
190
193
|
function childFromRequest(req, logger, options = {}) {
|
|
191
|
-
var _headers$xForwarded, _req$socket;
|
|
192
194
|
const correlationHeader = options.correlationHeader ?? "x-correlation-id";
|
|
193
195
|
const headers = req["headers"] ?? {};
|
|
194
196
|
const correlationId = validIncomingId(headers[correlationHeader.toLowerCase()]) ?? getCurrentCorrelationId() ?? generateCorrelationId();
|
|
195
197
|
const method = req["method"];
|
|
196
198
|
const url = req["url"] ?? req["originalUrl"];
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
|
|
199
|
+
const rawUserAgent = headers["user-agent"];
|
|
200
|
+
const userAgent = typeof rawUserAgent === "string" ? rawUserAgent.slice(0, MAX_USER_AGENT_LENGTH) : void 0;
|
|
201
|
+
const ip = resolveClientIp(req, headers, options.trustProxy === true);
|
|
202
|
+
const fields = {
|
|
200
203
|
correlationId,
|
|
201
204
|
...method !== void 0 ? { method } : {},
|
|
202
205
|
...url !== void 0 ? { url } : {},
|
|
203
206
|
...userAgent !== void 0 ? { userAgent } : {},
|
|
204
207
|
...ip !== void 0 ? { ip } : {}
|
|
205
|
-
}
|
|
208
|
+
};
|
|
209
|
+
const child = logger.child;
|
|
210
|
+
if (options.childContext !== void 0) return child.call(logger, options.childContext, fields);
|
|
211
|
+
if (usesStringContext(logger)) return child.call(logger, "request", fields);
|
|
212
|
+
return child.call(logger, fields);
|
|
213
|
+
}
|
|
214
|
+
/** ILogger implementations (LogixiaLogger, LogixiaLoggerService, mock logger) expose these methods. */
|
|
215
|
+
function usesStringContext(logger) {
|
|
216
|
+
const l = logger;
|
|
217
|
+
return typeof l["getContext"] === "function" || typeof l["getAvailableTransports"] === "function";
|
|
218
|
+
}
|
|
219
|
+
function resolveClientIp(req, headers, trustProxy) {
|
|
220
|
+
var _req$socket;
|
|
221
|
+
if (trustProxy) {
|
|
222
|
+
var _forwarded$split$;
|
|
223
|
+
const forwarded = headers["x-forwarded-for"];
|
|
224
|
+
const first = typeof forwarded === "string" ? (_forwarded$split$ = forwarded.split(",")[0]) === null || _forwarded$split$ === void 0 ? void 0 : _forwarded$split$.trim() : void 0;
|
|
225
|
+
if (first) return first.slice(0, MAX_IP_LENGTH);
|
|
226
|
+
}
|
|
227
|
+
const fromReq = req["ip"];
|
|
228
|
+
if (typeof fromReq === "string" && fromReq) return fromReq.slice(0, MAX_IP_LENGTH);
|
|
229
|
+
const remote = (_req$socket = req["socket"]) === null || _req$socket === void 0 ? void 0 : _req$socket["remoteAddress"];
|
|
230
|
+
return typeof remote === "string" ? remote.slice(0, MAX_IP_LENGTH) : void 0;
|
|
206
231
|
}
|
|
207
232
|
/**
|
|
208
233
|
* Extract a correlation context from a Kafka / RabbitMQ / SQS message object.
|
|
@@ -226,14 +251,14 @@ function extractMessageCorrelationId(message) {
|
|
|
226
251
|
const kafkaHeaders = message["headers"];
|
|
227
252
|
if (kafkaHeaders) {
|
|
228
253
|
const raw = kafkaHeaders["x-correlation-id"] ?? kafkaHeaders["correlationId"] ?? kafkaHeaders["correlation_id"];
|
|
229
|
-
if (raw !== void 0) return Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
|
|
254
|
+
if (raw !== void 0) return validIncomingId(Buffer.isBuffer(raw) ? raw.toString("utf8", 0, 512) : String(raw));
|
|
230
255
|
}
|
|
231
256
|
const sqsAttrs = message["MessageAttributes"];
|
|
232
257
|
if (sqsAttrs) {
|
|
233
258
|
var _sqsAttrs$xCorrelati, _sqsAttrs$correlation, _sqsAttrs$correlation2;
|
|
234
|
-
return ((_sqsAttrs$xCorrelati = sqsAttrs["x-correlation-id"]) === null || _sqsAttrs$xCorrelati === void 0 ? void 0 : _sqsAttrs$xCorrelati.StringValue) ?? ((_sqsAttrs$correlation = sqsAttrs["correlationId"]) === null || _sqsAttrs$correlation === void 0 ? void 0 : _sqsAttrs$correlation.StringValue) ?? ((_sqsAttrs$correlation2 = sqsAttrs["correlation_id"]) === null || _sqsAttrs$correlation2 === void 0 ? void 0 : _sqsAttrs$correlation2.StringValue);
|
|
259
|
+
return validIncomingId(((_sqsAttrs$xCorrelati = sqsAttrs["x-correlation-id"]) === null || _sqsAttrs$xCorrelati === void 0 ? void 0 : _sqsAttrs$xCorrelati.StringValue) ?? ((_sqsAttrs$correlation = sqsAttrs["correlationId"]) === null || _sqsAttrs$correlation === void 0 ? void 0 : _sqsAttrs$correlation.StringValue) ?? ((_sqsAttrs$correlation2 = sqsAttrs["correlation_id"]) === null || _sqsAttrs$correlation2 === void 0 ? void 0 : _sqsAttrs$correlation2.StringValue));
|
|
235
260
|
}
|
|
236
|
-
return message["correlationId"] ?? message["correlation_id"];
|
|
261
|
+
return validIncomingId(message["correlationId"] ?? message["correlation_id"]);
|
|
237
262
|
}
|
|
238
263
|
/**
|
|
239
264
|
* Build Kafka message headers containing the current correlation context.
|
package/dist/correlation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"correlation.js","names":["LogixiaContext","ctx: CorrelationContext","headers: Record<string, string>"],"sources":["../src/correlation.ts"],"sourcesContent":["/**\n * logixia — Microservices Correlation ID Propagation\n *\n * Provides zero-boilerplate correlation ID generation and propagation for\n * distributed systems. A single correlation ID travels through every service\n * in a request fan-out, making it trivial to join logs across services.\n *\n * Features:\n * - UUID v4 correlation ID generation (via `crypto.randomUUID()`)\n * - Express / Fastify / NestJS incoming-request middleware\n * - `correlationFetch()` — wraps global `fetch`, auto-injects `X-Correlation-ID`\n * - `createCorrelationAxiosInterceptor()` — axios request interceptor\n * - `childFromRequest(req, logger)` — child logger with all request identifiers\n * - Kafka / SQS / RabbitMQ message context helpers\n * - Integration with `LogixiaContext` (AsyncLocalStorage) so every `logger.info()`\n * call automatically includes the correlationId without per-call passing.\n *\n * @example Express middleware\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use(correlationMiddleware());\n *\n * app.get('/orders', (req, res) => {\n * // correlationId is automatically in every log inside this request\n * logger.info('Listing orders');\n * res.json({ ok: true });\n * });\n * ```\n *\n * @example Outbound fetch with correlation\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Reads correlationId from current LogixiaContext and injects it as header\n * const data = await correlationFetch('https://payments.internal/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n *\n * @example Manual correlation context\n * ```ts\n * import { withCorrelationId } from 'logixia/correlation';\n *\n * await withCorrelationId('trace-abc-123', async () => {\n * // All logs and outbound fetch calls in here carry 'trace-abc-123'\n * await processOrder();\n * });\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\n\nimport { LogixiaContext } from './context/async-context';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/** Fields injected into the log context by the correlation middleware. */\nexport interface CorrelationContext {\n correlationId: string;\n traceId?: string;\n /** Service that originated the request (from `X-Origin-Service` header). */\n originService?: string;\n /** Index signature so CorrelationContext is assignable to LogContext. */\n [key: string]: unknown;\n}\n\nexport interface CorrelationMiddlewareOptions {\n /**\n * HTTP header to read/write the correlation ID from/to.\n * @default 'x-correlation-id'\n */\n header?: string;\n /**\n * HTTP header for the originating service name.\n * @default 'x-origin-service'\n */\n originServiceHeader?: string;\n /**\n * Function to generate a new correlation ID when none is present in the request.\n * @default () => crypto.randomUUID()\n */\n generate?: () => string;\n /**\n * When `true`, the response will include the correlation ID header so\n * clients can log it client-side.\n * @default true\n */\n setResponseHeader?: boolean;\n}\n\n// ── ID generation ─────────────────────────────────────────────────────────────\n\n/**\n * Generate a new correlation ID using `crypto.randomUUID()` (available in\n * Node.js 14.17+, all modern browsers, and Edge runtimes).\n *\n * When the Web Crypto global is unavailable (e.g. Node 18 without\n * `--experimental-global-webcrypto`), fall back to `node:crypto`'s\n * `randomUUID`, which returns a proper UUID v4 — matching the shape this\n * function's JSDoc and README promise. `logixia/correlation` is Node-only\n * (it builds on `AsyncLocalStorage`), so importing `node:crypto` is safe here.\n */\nexport function generateCorrelationId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return randomUUID();\n}\n\nconst MAX_INCOMING_ID_LENGTH = 128;\nconst VALID_INCOMING_ID = /^[A-Za-z0-9._:-]+$/;\n\n/**\n * Return an incoming correlation ID only if it is short and made of safe\n * characters. Anything else (over-long, empty, or containing newlines or other\n * control characters) is dropped so the caller generates a fresh ID instead of\n * writing client-supplied text into every log line.\n */\nfunction validIncomingId(value: string | undefined): string | undefined {\n if (value === undefined || value.length > MAX_INCOMING_ID_LENGTH) return undefined;\n return VALID_INCOMING_ID.test(value) ? value : undefined;\n}\n\n// ── Context helpers ───────────────────────────────────────────────────────────\n\n/**\n * Return the correlation ID from the current `LogixiaContext`, or `undefined`\n * when called outside any context scope.\n */\nexport function getCurrentCorrelationId(): string | undefined {\n const ctx = LogixiaContext.get();\n return ctx?.correlationId as string | undefined;\n}\n\n/**\n * Run `callback` inside a correlation context scope.\n *\n * If a correlation ID is not provided, a new UUID is generated.\n *\n * @example\n * ```ts\n * await withCorrelationId('cid-abc', async () => {\n * logger.info('Processing message'); // → { correlationId: 'cid-abc', ... }\n * await callDownstream(); // correlationFetch() picks up cid-abc\n * });\n * ```\n */\nexport function withCorrelationId<T>(correlationId: string | undefined, callback: () => T): T {\n const id = correlationId ?? generateCorrelationId();\n return LogixiaContext.run({ correlationId: id }, callback);\n}\n\n// ── Express / Connect middleware ──────────────────────────────────────────────\n\n/**\n * Express-compatible middleware that:\n * 1. Reads `X-Correlation-ID` from the incoming request (or generates a new one)\n * 2. Wraps the request in a `LogixiaContext` so all loggers in the handler\n * automatically include `correlationId`\n * 3. Writes the correlation ID back to the response header (opt-out via options)\n *\n * @example\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * app.use(correlationMiddleware());\n * ```\n */\nexport function correlationMiddleware(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationMiddleware(\n req: Record<string, unknown>,\n res: Record<string, unknown>,\n next: () => void\n ): void {\n const headers = (req['headers'] ?? {}) as Record<string, string | undefined>;\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();\n const originService = headers[originServiceHeader.toLowerCase()];\n\n if (setResponseHeader) {\n const setHeader = res['setHeader'] as ((name: string, value: string) => void) | undefined;\n if (typeof setHeader === 'function') {\n setHeader.call(res, header, correlationId);\n }\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, next);\n };\n}\n\n// ── Fastify hook ──────────────────────────────────────────────────────────────\n\n/**\n * Fastify `onRequest` lifecycle hook — same functionality as `correlationMiddleware`\n * but adapted to Fastify's hook API.\n *\n * @example\n * ```ts\n * import { correlationFastifyHook } from 'logixia/correlation';\n * fastify.addHook('onRequest', correlationFastifyHook());\n * ```\n */\nexport function correlationFastifyHook(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationFastifyHook(\n request: Record<string, unknown>,\n reply: Record<string, unknown>,\n done: () => void\n ): void {\n const headers = (request['headers'] ?? {}) as Record<string, string | undefined>;\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();\n const originService = headers[originServiceHeader.toLowerCase()];\n\n if (setResponseHeader) {\n const replyHeader = reply['header'] as ((name: string, value: string) => void) | undefined;\n if (typeof replyHeader === 'function') {\n replyHeader.call(reply, header, correlationId);\n }\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, done);\n };\n}\n\n// ── Outbound fetch wrapper ────────────────────────────────────────────────────\n\n/**\n * A drop-in replacement for `fetch` that automatically injects the current\n * `correlationId` from `LogixiaContext` as an `X-Correlation-ID` request header.\n *\n * Falls back to the global `fetch` when no correlation context is active.\n *\n * @example\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Inside a request handler with correlationMiddleware active:\n * const response = await correlationFetch('https://payments.internal/v1/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n */\nexport async function correlationFetch(\n input: string | URL | Request,\n init: RequestInit = {},\n options: { header?: string } = {}\n): Promise<Response> {\n const header = options.header ?? 'x-correlation-id';\n const correlationId = getCurrentCorrelationId();\n\n // `init.headers` can be a plain object, a `Headers` instance, or an array of\n // `[name, value]` tuples. Spreading it into a plain object loses everything\n // but the object form: a `Headers` instance has no own enumerable properties,\n // and an array spread yields numeric indices. `Headers` accepts all three, is\n // case-insensitive, and is the shape `fetch` expects back.\n //\n // A `Request` carries headers of its own, and `fetch(request, { headers })`\n // replaces them rather than merging them, so they are seeded first and the\n // caller's `init.headers` is applied on top. Without this, accepting a\n // `Request` here would drop its headers, which is the same defect one input\n // shape over.\n const headers = new Headers(input instanceof Request ? input.headers : undefined);\n for (const [name, value] of new Headers(init.headers)) {\n headers.set(name, value);\n }\n if (correlationId && !headers.has(header)) {\n headers.set(header, correlationId);\n }\n\n return globalThis.fetch(input, { ...init, headers });\n}\n\n// ── Axios interceptor factory ─────────────────────────────────────────────────\n\n/**\n * Returns an axios request interceptor that automatically injects the current\n * correlation ID into outgoing axios requests.\n *\n * @example\n * ```ts\n * import axios from 'axios';\n * import { createCorrelationAxiosInterceptor } from 'logixia/correlation';\n *\n * axios.interceptors.request.use(createCorrelationAxiosInterceptor());\n * ```\n */\nexport function createCorrelationAxiosInterceptor(\n options: { header?: string } = {}\n): (config: Record<string, unknown>) => Record<string, unknown> {\n const header = options.header ?? 'x-correlation-id';\n\n return function correlationAxiosInterceptor(\n config: Record<string, unknown>\n ): Record<string, unknown> {\n const correlationId = getCurrentCorrelationId();\n if (!correlationId) return config;\n\n const headers = (config['headers'] ?? {}) as Record<string, unknown>;\n if (!headers[header]) {\n headers[header] = correlationId;\n }\n\n return { ...config, headers };\n };\n}\n\n// ── Child logger from request ─────────────────────────────────────────────────\n\n/**\n * Create a child logger enriched with all correlation-relevant identifiers\n * extracted from the incoming HTTP request.\n *\n * The child logger's every call automatically includes:\n * `correlationId`, `requestId`, `method`, `url`, `userAgent`, `ip`\n *\n * @example\n * ```ts\n * import { childFromRequest } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use((req, res, next) => {\n * req.logger = childFromRequest(req, logger);\n * next();\n * });\n *\n * app.get('/orders', (req, res) => {\n * req.logger.info('Listing orders'); // → correlationId, requestId, method, url ...\n * });\n * ```\n */\nexport function childFromRequest<TLogger extends { child(ctx: Record<string, unknown>): TLogger }>(\n req: Record<string, unknown>,\n logger: TLogger,\n options: { correlationHeader?: string } = {}\n): TLogger {\n const correlationHeader = options.correlationHeader ?? 'x-correlation-id';\n\n const headers = (req['headers'] ?? {}) as Record<string, string | undefined>;\n // Node lower-cases incoming header names, so look up with a lower-cased key.\n const correlationId =\n validIncomingId(headers[correlationHeader.toLowerCase()]) ??\n getCurrentCorrelationId() ??\n generateCorrelationId();\n\n const method = req['method'] as string | undefined;\n const url = (req['url'] as string | undefined) ?? (req['originalUrl'] as string | undefined);\n const userAgent = headers['user-agent'];\n const ip =\n (headers['x-forwarded-for'] as string | undefined)?.split(',')[0]?.trim() ??\n ((req['socket'] as Record<string, unknown> | undefined)?.['remoteAddress'] as\n string | undefined);\n\n return logger.child({\n correlationId,\n ...(method !== undefined ? { method } : {}),\n ...(url !== undefined ? { url } : {}),\n ...(userAgent !== undefined ? { userAgent } : {}),\n ...(ip !== undefined ? { ip } : {}),\n });\n}\n\n// ── Message queue helpers ─────────────────────────────────────────────────────\n\n/**\n * Extract a correlation context from a Kafka / RabbitMQ / SQS message object.\n *\n * Looks for `correlationId`, `correlation_id`, or `x-correlation-id` in\n * `message.headers` (Kafka) or `message.MessageAttributes` (SQS).\n *\n * @example Kafka consumer\n * ```ts\n * import { extractMessageCorrelationId, withCorrelationId } from 'logixia/correlation';\n *\n * consumer.run({\n * eachMessage: async ({ message }) => {\n * const correlationId = extractMessageCorrelationId(message);\n * await withCorrelationId(correlationId, () => processMessage(message));\n * },\n * });\n * ```\n */\nexport function extractMessageCorrelationId(message: Record<string, unknown>): string | undefined {\n // Kafka: message.headers is Record<string, Buffer | string>\n const kafkaHeaders = message['headers'] as Record<string, Buffer | string> | undefined;\n if (kafkaHeaders) {\n const raw =\n kafkaHeaders['x-correlation-id'] ??\n kafkaHeaders['correlationId'] ??\n kafkaHeaders['correlation_id'];\n if (raw !== undefined) {\n return Buffer.isBuffer(raw) ? raw.toString('utf8') : String(raw);\n }\n }\n\n // SQS: message.MessageAttributes\n const sqsAttrs = message['MessageAttributes'] as\n Record<string, { StringValue?: string }> | undefined;\n if (sqsAttrs) {\n return (\n sqsAttrs['x-correlation-id']?.StringValue ??\n sqsAttrs['correlationId']?.StringValue ??\n sqsAttrs['correlation_id']?.StringValue\n );\n }\n\n // AMQP / generic envelope\n return (\n (message['correlationId'] as string | undefined) ??\n (message['correlation_id'] as string | undefined)\n );\n}\n\n/**\n * Build Kafka message headers containing the current correlation context.\n *\n * @example\n * ```ts\n * import { buildKafkaCorrelationHeaders } from 'logixia/correlation';\n *\n * await producer.send({\n * topic: 'orders',\n * messages: [{ value: JSON.stringify(order), headers: buildKafkaCorrelationHeaders() }],\n * });\n * ```\n */\nexport function buildKafkaCorrelationHeaders(): Record<string, string> {\n const ctx = LogixiaContext.get();\n const headers: Record<string, string> = {};\n\n const correlationId = ctx?.correlationId as string | undefined;\n if (correlationId) headers['x-correlation-id'] = correlationId;\n\n const traceId = ctx?.traceId as string | undefined;\n if (traceId) headers['x-trace-id'] = traceId;\n\n return headers;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"correlation.js","names":["sanitizeIncomingId","LogixiaContext","ctx: CorrelationContext","headers: Record<string, string>"],"sources":["../src/correlation.ts"],"sourcesContent":["/**\n * logixia — Microservices Correlation ID Propagation\n *\n * Provides zero-boilerplate correlation ID generation and propagation for\n * distributed systems. A single correlation ID travels through every service\n * in a request fan-out, making it trivial to join logs across services.\n *\n * Features:\n * - UUID v4 correlation ID generation (via `crypto.randomUUID()`)\n * - Express / Fastify / NestJS incoming-request middleware\n * - `correlationFetch()` — wraps global `fetch`, auto-injects `X-Correlation-ID`\n * - `createCorrelationAxiosInterceptor()` — axios request interceptor\n * - `childFromRequest(req, logger)` — child logger with all request identifiers\n * - Kafka / SQS / RabbitMQ message context helpers\n * - Integration with `LogixiaContext` (AsyncLocalStorage) so every `logger.info()`\n * call automatically includes the correlationId without per-call passing.\n *\n * @example Express middleware\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use(correlationMiddleware());\n *\n * app.get('/orders', (req, res) => {\n * // correlationId is automatically in every log inside this request\n * logger.info('Listing orders');\n * res.json({ ok: true });\n * });\n * ```\n *\n * @example Outbound fetch with correlation\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Reads correlationId from current LogixiaContext and injects it as header\n * const data = await correlationFetch('https://payments.internal/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n *\n * @example Manual correlation context\n * ```ts\n * import { withCorrelationId } from 'logixia/correlation';\n *\n * await withCorrelationId('trace-abc-123', async () => {\n * // All logs and outbound fetch calls in here carry 'trace-abc-123'\n * await processOrder();\n * });\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\n\nimport { LogixiaContext } from './context/async-context';\nimport { sanitizeIncomingId } from './utils/trace.utils';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/** Fields injected into the log context by the correlation middleware. */\nexport interface CorrelationContext {\n correlationId: string;\n traceId?: string;\n /** Service that originated the request (from `X-Origin-Service` header). */\n originService?: string;\n /** Index signature so CorrelationContext is assignable to LogContext. */\n [key: string]: unknown;\n}\n\nexport interface CorrelationMiddlewareOptions {\n /**\n * HTTP header to read/write the correlation ID from/to.\n * @default 'x-correlation-id'\n */\n header?: string;\n /**\n * HTTP header for the originating service name.\n * @default 'x-origin-service'\n */\n originServiceHeader?: string;\n /**\n * Function to generate a new correlation ID when none is present in the request.\n * @default () => crypto.randomUUID()\n */\n generate?: () => string;\n /**\n * When `false`, the incoming correlation header is ignored and a new ID is\n * always generated. Use this at the edge when client-supplied IDs should not\n * be trusted.\n * @default true\n */\n trustIncoming?: boolean;\n /**\n * When `true`, the response will include the correlation ID header so\n * clients can log it client-side.\n * @default true\n */\n setResponseHeader?: boolean;\n}\n\n// ── ID generation ─────────────────────────────────────────────────────────────\n\n/**\n * Generate a new correlation ID using `crypto.randomUUID()` (available in\n * Node.js 14.17+, all modern browsers, and Edge runtimes).\n *\n * When the Web Crypto global is unavailable (e.g. Node 18 without\n * `--experimental-global-webcrypto`), fall back to `node:crypto`'s\n * `randomUUID`, which returns a proper UUID v4 — matching the shape this\n * function's JSDoc and README promise. `logixia/correlation` is Node-only\n * (it builds on `AsyncLocalStorage`), so importing `node:crypto` is safe here.\n */\nexport function generateCorrelationId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return randomUUID();\n}\n\n/**\n * Return an incoming correlation ID only if it is short and made of safe\n * characters. Anything else (over-long, empty, or containing newlines or other\n * control characters) is dropped so the caller generates a fresh ID instead of\n * writing client-supplied text into every log line.\n */\nfunction validIncomingId(value: unknown): string | undefined {\n if (typeof value === 'string' && value.trim() !== value) return undefined;\n return sanitizeIncomingId(value);\n}\n\n// ── Context helpers ───────────────────────────────────────────────────────────\n\n/**\n * Return the correlation ID from the current `LogixiaContext`, or `undefined`\n * when called outside any context scope.\n */\nexport function getCurrentCorrelationId(): string | undefined {\n const ctx = LogixiaContext.get();\n return ctx?.correlationId as string | undefined;\n}\n\n/**\n * Run `callback` inside a correlation context scope.\n *\n * If a correlation ID is not provided, a new UUID is generated.\n *\n * @example\n * ```ts\n * await withCorrelationId('cid-abc', async () => {\n * logger.info('Processing message'); // → { correlationId: 'cid-abc', ... }\n * await callDownstream(); // correlationFetch() picks up cid-abc\n * });\n * ```\n */\nexport function withCorrelationId<T>(correlationId: string | undefined, callback: () => T): T {\n const id = correlationId ?? generateCorrelationId();\n return LogixiaContext.run({ correlationId: id }, callback);\n}\n\n// ── Express / Connect middleware ──────────────────────────────────────────────\n\n/**\n * Express-compatible middleware that:\n * 1. Reads `X-Correlation-ID` from the incoming request (or generates a new one)\n * 2. Wraps the request in a `LogixiaContext` so all loggers in the handler\n * automatically include `correlationId`\n * 3. Writes the correlation ID back to the response header (opt-out via options)\n *\n * @example\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * app.use(correlationMiddleware());\n * ```\n */\nexport function correlationMiddleware(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationMiddleware(\n req: Record<string, unknown>,\n res: Record<string, unknown>,\n next: () => void\n ): void {\n const headers = (req['headers'] ?? {}) as Record<string, string | undefined>;\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n const setHeader = res['setHeader'] as ((name: string, value: string) => void) | undefined;\n if (typeof setHeader === 'function') {\n setHeader.call(res, header, correlationId);\n }\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, next);\n };\n}\n\n// ── Fastify hook ──────────────────────────────────────────────────────────────\n\n/**\n * Fastify `onRequest` lifecycle hook — same functionality as `correlationMiddleware`\n * but adapted to Fastify's hook API.\n *\n * @example\n * ```ts\n * import { correlationFastifyHook } from 'logixia/correlation';\n * fastify.addHook('onRequest', correlationFastifyHook());\n * ```\n */\nexport function correlationFastifyHook(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationFastifyHook(\n request: Record<string, unknown>,\n reply: Record<string, unknown>,\n done: () => void\n ): void {\n const headers = (request['headers'] ?? {}) as Record<string, string | undefined>;\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n const replyHeader = reply['header'] as ((name: string, value: string) => void) | undefined;\n if (typeof replyHeader === 'function') {\n replyHeader.call(reply, header, correlationId);\n }\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, done);\n };\n}\n\n// ── Outbound fetch wrapper ────────────────────────────────────────────────────\n\n/**\n * A drop-in replacement for `fetch` that automatically injects the current\n * `correlationId` from `LogixiaContext` as an `X-Correlation-ID` request header.\n *\n * Falls back to the global `fetch` when no correlation context is active.\n *\n * @example\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Inside a request handler with correlationMiddleware active:\n * const response = await correlationFetch('https://payments.internal/v1/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n */\nexport async function correlationFetch(\n input: string | URL | Request,\n init: RequestInit = {},\n options: { header?: string } = {}\n): Promise<Response> {\n const header = options.header ?? 'x-correlation-id';\n const correlationId = getCurrentCorrelationId();\n\n // `init.headers` can be a plain object, a `Headers` instance, or an array of\n // `[name, value]` tuples. Spreading it into a plain object loses everything\n // but the object form: a `Headers` instance has no own enumerable properties,\n // and an array spread yields numeric indices. `Headers` accepts all three, is\n // case-insensitive, and is the shape `fetch` expects back.\n //\n // A `Request` carries headers of its own, and `fetch(request, { headers })`\n // replaces them rather than merging them, so they are seeded first and the\n // caller's `init.headers` is applied on top. Without this, accepting a\n // `Request` here would drop its headers, which is the same defect one input\n // shape over.\n const headers = new Headers(input instanceof Request ? input.headers : undefined);\n for (const [name, value] of new Headers(init.headers)) {\n headers.set(name, value);\n }\n if (correlationId && !headers.has(header)) {\n headers.set(header, correlationId);\n }\n\n return globalThis.fetch(input, { ...init, headers });\n}\n\n// ── Axios interceptor factory ─────────────────────────────────────────────────\n\n/**\n * Returns an axios request interceptor that automatically injects the current\n * correlation ID into outgoing axios requests.\n *\n * @example\n * ```ts\n * import axios from 'axios';\n * import { createCorrelationAxiosInterceptor } from 'logixia/correlation';\n *\n * axios.interceptors.request.use(createCorrelationAxiosInterceptor());\n * ```\n */\nexport function createCorrelationAxiosInterceptor(\n options: { header?: string } = {}\n): (config: Record<string, unknown>) => Record<string, unknown> {\n const header = options.header ?? 'x-correlation-id';\n\n return function correlationAxiosInterceptor(\n config: Record<string, unknown>\n ): Record<string, unknown> {\n const correlationId = getCurrentCorrelationId();\n if (!correlationId) return config;\n\n const headers = (config['headers'] ?? {}) as Record<string, unknown>;\n if (!headers[header]) {\n headers[header] = correlationId;\n }\n\n return { ...config, headers };\n };\n}\n\n// ── Child logger from request ─────────────────────────────────────────────────\n\nconst MAX_USER_AGENT_LENGTH = 512;\nconst MAX_IP_LENGTH = 64;\n\nexport interface ChildFromRequestOptions {\n /** Header to read the correlation ID from. @default 'x-correlation-id' */\n correlationHeader?: string;\n /**\n * Take the client IP from the first `X-Forwarded-For` entry. Only enable this\n * when the app sits behind a proxy that overwrites the header; otherwise any\n * client can choose the logged IP.\n * @default false\n */\n trustProxy?: boolean;\n /**\n * Context name passed as the first argument to `logger.child(context, data)`.\n * When omitted, logixia loggers (detected by their `getContext` /\n * `getAvailableTransports` methods) get `child('request', fields)` and any other\n * logger gets `child(fields)`.\n */\n childContext?: string;\n}\n\n/**\n * Create a child logger enriched with all correlation-relevant identifiers\n * extracted from the incoming HTTP request.\n *\n * The child logger's every call automatically includes:\n * `correlationId`, `method`, `url`, `userAgent`, `ip`\n *\n * The IP comes from `req.ip` / the socket address unless `trustProxy` is set.\n *\n * @example\n * ```ts\n * import { childFromRequest } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use((req, res, next) => {\n * req.logger = childFromRequest(req, logger);\n * next();\n * });\n *\n * app.get('/orders', (req, res) => {\n * req.logger.info('Listing orders'); // → correlationId, requestId, method, url ...\n * });\n * ```\n */\nexport function childFromRequest<TLogger extends { child(...args: never[]): TLogger }>(\n req: Record<string, unknown>,\n logger: TLogger,\n options: ChildFromRequestOptions = {}\n): TLogger {\n const correlationHeader = options.correlationHeader ?? 'x-correlation-id';\n\n const headers = (req['headers'] ?? {}) as Record<string, string | undefined>;\n // Node lower-cases incoming header names, so look up with a lower-cased key.\n const correlationId =\n validIncomingId(headers[correlationHeader.toLowerCase()]) ??\n getCurrentCorrelationId() ??\n generateCorrelationId();\n\n const method = req['method'] as string | undefined;\n const url = (req['url'] as string | undefined) ?? (req['originalUrl'] as string | undefined);\n const rawUserAgent = headers['user-agent'];\n const userAgent =\n typeof rawUserAgent === 'string' ? rawUserAgent.slice(0, MAX_USER_AGENT_LENGTH) : undefined;\n const ip = resolveClientIp(req, headers, options.trustProxy === true);\n\n const fields = {\n correlationId,\n ...(method !== undefined ? { method } : {}),\n ...(url !== undefined ? { url } : {}),\n ...(userAgent !== undefined ? { userAgent } : {}),\n ...(ip !== undefined ? { ip } : {}),\n };\n\n const child = logger.child as (...args: unknown[]) => TLogger;\n if (options.childContext !== undefined) {\n return child.call(logger, options.childContext, fields);\n }\n // logixia's ILogger.child(context: string, data?) takes a string namespace;\n // pino/winston-style loggers take a bindings object.\n if (usesStringContext(logger)) {\n return child.call(logger, 'request', fields);\n }\n return child.call(logger, fields);\n}\n\n/** ILogger implementations (LogixiaLogger, LogixiaLoggerService, mock logger) expose these methods. */\nfunction usesStringContext(logger: object): boolean {\n const l = logger as Record<string, unknown>;\n return typeof l['getContext'] === 'function' || typeof l['getAvailableTransports'] === 'function';\n}\n\nfunction resolveClientIp(\n req: Record<string, unknown>,\n headers: Record<string, string | undefined>,\n trustProxy: boolean\n): string | undefined {\n if (trustProxy) {\n const forwarded = headers['x-forwarded-for'];\n const first = typeof forwarded === 'string' ? forwarded.split(',')[0]?.trim() : undefined;\n if (first) return first.slice(0, MAX_IP_LENGTH);\n }\n const fromReq = req['ip'];\n if (typeof fromReq === 'string' && fromReq) return fromReq.slice(0, MAX_IP_LENGTH);\n const remote = (req['socket'] as Record<string, unknown> | undefined)?.['remoteAddress'];\n return typeof remote === 'string' ? remote.slice(0, MAX_IP_LENGTH) : undefined;\n}\n\n// ── Message queue helpers ─────────────────────────────────────────────────────\n\n/**\n * Extract a correlation context from a Kafka / RabbitMQ / SQS message object.\n *\n * Looks for `correlationId`, `correlation_id`, or `x-correlation-id` in\n * `message.headers` (Kafka) or `message.MessageAttributes` (SQS).\n *\n * @example Kafka consumer\n * ```ts\n * import { extractMessageCorrelationId, withCorrelationId } from 'logixia/correlation';\n *\n * consumer.run({\n * eachMessage: async ({ message }) => {\n * const correlationId = extractMessageCorrelationId(message);\n * await withCorrelationId(correlationId, () => processMessage(message));\n * },\n * });\n * ```\n */\nexport function extractMessageCorrelationId(message: Record<string, unknown>): string | undefined {\n // Kafka: message.headers is Record<string, Buffer | string>\n const kafkaHeaders = message['headers'] as Record<string, Buffer | string> | undefined;\n if (kafkaHeaders) {\n const raw =\n kafkaHeaders['x-correlation-id'] ??\n kafkaHeaders['correlationId'] ??\n kafkaHeaders['correlation_id'];\n if (raw !== undefined) {\n return validIncomingId(Buffer.isBuffer(raw) ? raw.toString('utf8', 0, 512) : String(raw));\n }\n }\n\n // SQS: message.MessageAttributes\n const sqsAttrs = message['MessageAttributes'] as\n Record<string, { StringValue?: string }> | undefined;\n if (sqsAttrs) {\n return validIncomingId(\n sqsAttrs['x-correlation-id']?.StringValue ??\n sqsAttrs['correlationId']?.StringValue ??\n sqsAttrs['correlation_id']?.StringValue\n );\n }\n\n // AMQP / generic envelope\n return validIncomingId(message['correlationId'] ?? message['correlation_id']);\n}\n\n/**\n * Build Kafka message headers containing the current correlation context.\n *\n * @example\n * ```ts\n * import { buildKafkaCorrelationHeaders } from 'logixia/correlation';\n *\n * await producer.send({\n * topic: 'orders',\n * messages: [{ value: JSON.stringify(order), headers: buildKafkaCorrelationHeaders() }],\n * });\n * ```\n */\nexport function buildKafkaCorrelationHeaders(): Record<string, string> {\n const ctx = LogixiaContext.get();\n const headers: Record<string, string> = {};\n\n const correlationId = ctx?.correlationId as string | undefined;\n if (correlationId) headers['x-correlation-id'] = correlationId;\n\n const traceId = ctx?.traceId as string | undefined;\n if (traceId) headers['x-trace-id'] = traceId;\n\n return headers;\n}\n"],"mappings":"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"}
|
package/dist/correlation.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as sanitizeIncomingId } from "./trace.utils-CvXgaiQT.mjs";
|
|
2
|
+
import { t as LogixiaContext } from "./async-context-BzJrbfQs.mjs";
|
|
2
3
|
import { randomUUID } from "node:crypto";
|
|
3
4
|
|
|
4
5
|
//#region src/correlation.ts
|
|
@@ -16,8 +17,6 @@ function generateCorrelationId() {
|
|
|
16
17
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
17
18
|
return randomUUID();
|
|
18
19
|
}
|
|
19
|
-
const MAX_INCOMING_ID_LENGTH = 128;
|
|
20
|
-
const VALID_INCOMING_ID = /^[A-Za-z0-9._:-]+$/;
|
|
21
20
|
/**
|
|
22
21
|
* Return an incoming correlation ID only if it is short and made of safe
|
|
23
22
|
* characters. Anything else (over-long, empty, or containing newlines or other
|
|
@@ -25,8 +24,8 @@ const VALID_INCOMING_ID = /^[A-Za-z0-9._:-]+$/;
|
|
|
25
24
|
* writing client-supplied text into every log line.
|
|
26
25
|
*/
|
|
27
26
|
function validIncomingId(value) {
|
|
28
|
-
if (value ===
|
|
29
|
-
return
|
|
27
|
+
if (typeof value === "string" && value.trim() !== value) return void 0;
|
|
28
|
+
return sanitizeIncomingId(value);
|
|
30
29
|
}
|
|
31
30
|
/**
|
|
32
31
|
* Return the correlation ID from the current `LogixiaContext`, or `undefined`
|
|
@@ -67,11 +66,11 @@ function withCorrelationId(correlationId, callback) {
|
|
|
67
66
|
* ```
|
|
68
67
|
*/
|
|
69
68
|
function correlationMiddleware(options = {}) {
|
|
70
|
-
const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, setResponseHeader = true } = options;
|
|
69
|
+
const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, trustIncoming = true, setResponseHeader = true } = options;
|
|
71
70
|
return function logixiaCorrelationMiddleware(req, res, next) {
|
|
72
71
|
const headers = req["headers"] ?? {};
|
|
73
|
-
const correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();
|
|
74
|
-
const originService = headers[originServiceHeader.toLowerCase()];
|
|
72
|
+
const correlationId = (trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : void 0) ?? generate();
|
|
73
|
+
const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);
|
|
75
74
|
if (setResponseHeader) {
|
|
76
75
|
const setHeader = res["setHeader"];
|
|
77
76
|
if (typeof setHeader === "function") setHeader.call(res, header, correlationId);
|
|
@@ -94,11 +93,11 @@ function correlationMiddleware(options = {}) {
|
|
|
94
93
|
* ```
|
|
95
94
|
*/
|
|
96
95
|
function correlationFastifyHook(options = {}) {
|
|
97
|
-
const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, setResponseHeader = true } = options;
|
|
96
|
+
const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, trustIncoming = true, setResponseHeader = true } = options;
|
|
98
97
|
return function logixiaCorrelationFastifyHook(request, reply, done) {
|
|
99
98
|
const headers = request["headers"] ?? {};
|
|
100
|
-
const correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();
|
|
101
|
-
const originService = headers[originServiceHeader.toLowerCase()];
|
|
99
|
+
const correlationId = (trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : void 0) ?? generate();
|
|
100
|
+
const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);
|
|
102
101
|
if (setResponseHeader) {
|
|
103
102
|
const replyHeader = reply["header"];
|
|
104
103
|
if (typeof replyHeader === "function") replyHeader.call(reply, header, correlationId);
|
|
@@ -163,12 +162,16 @@ function createCorrelationAxiosInterceptor(options = {}) {
|
|
|
163
162
|
};
|
|
164
163
|
};
|
|
165
164
|
}
|
|
165
|
+
const MAX_USER_AGENT_LENGTH = 512;
|
|
166
|
+
const MAX_IP_LENGTH = 64;
|
|
166
167
|
/**
|
|
167
168
|
* Create a child logger enriched with all correlation-relevant identifiers
|
|
168
169
|
* extracted from the incoming HTTP request.
|
|
169
170
|
*
|
|
170
171
|
* The child logger's every call automatically includes:
|
|
171
|
-
* `correlationId`, `
|
|
172
|
+
* `correlationId`, `method`, `url`, `userAgent`, `ip`
|
|
173
|
+
*
|
|
174
|
+
* The IP comes from `req.ip` / the socket address unless `trustProxy` is set.
|
|
172
175
|
*
|
|
173
176
|
* @example
|
|
174
177
|
* ```ts
|
|
@@ -186,21 +189,43 @@ function createCorrelationAxiosInterceptor(options = {}) {
|
|
|
186
189
|
* ```
|
|
187
190
|
*/
|
|
188
191
|
function childFromRequest(req, logger, options = {}) {
|
|
189
|
-
var _headers$xForwarded, _req$socket;
|
|
190
192
|
const correlationHeader = options.correlationHeader ?? "x-correlation-id";
|
|
191
193
|
const headers = req["headers"] ?? {};
|
|
192
194
|
const correlationId = validIncomingId(headers[correlationHeader.toLowerCase()]) ?? getCurrentCorrelationId() ?? generateCorrelationId();
|
|
193
195
|
const method = req["method"];
|
|
194
196
|
const url = req["url"] ?? req["originalUrl"];
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
|
|
197
|
+
const rawUserAgent = headers["user-agent"];
|
|
198
|
+
const userAgent = typeof rawUserAgent === "string" ? rawUserAgent.slice(0, MAX_USER_AGENT_LENGTH) : void 0;
|
|
199
|
+
const ip = resolveClientIp(req, headers, options.trustProxy === true);
|
|
200
|
+
const fields = {
|
|
198
201
|
correlationId,
|
|
199
202
|
...method !== void 0 ? { method } : {},
|
|
200
203
|
...url !== void 0 ? { url } : {},
|
|
201
204
|
...userAgent !== void 0 ? { userAgent } : {},
|
|
202
205
|
...ip !== void 0 ? { ip } : {}
|
|
203
|
-
}
|
|
206
|
+
};
|
|
207
|
+
const child = logger.child;
|
|
208
|
+
if (options.childContext !== void 0) return child.call(logger, options.childContext, fields);
|
|
209
|
+
if (usesStringContext(logger)) return child.call(logger, "request", fields);
|
|
210
|
+
return child.call(logger, fields);
|
|
211
|
+
}
|
|
212
|
+
/** ILogger implementations (LogixiaLogger, LogixiaLoggerService, mock logger) expose these methods. */
|
|
213
|
+
function usesStringContext(logger) {
|
|
214
|
+
const l = logger;
|
|
215
|
+
return typeof l["getContext"] === "function" || typeof l["getAvailableTransports"] === "function";
|
|
216
|
+
}
|
|
217
|
+
function resolveClientIp(req, headers, trustProxy) {
|
|
218
|
+
var _req$socket;
|
|
219
|
+
if (trustProxy) {
|
|
220
|
+
var _forwarded$split$;
|
|
221
|
+
const forwarded = headers["x-forwarded-for"];
|
|
222
|
+
const first = typeof forwarded === "string" ? (_forwarded$split$ = forwarded.split(",")[0]) === null || _forwarded$split$ === void 0 ? void 0 : _forwarded$split$.trim() : void 0;
|
|
223
|
+
if (first) return first.slice(0, MAX_IP_LENGTH);
|
|
224
|
+
}
|
|
225
|
+
const fromReq = req["ip"];
|
|
226
|
+
if (typeof fromReq === "string" && fromReq) return fromReq.slice(0, MAX_IP_LENGTH);
|
|
227
|
+
const remote = (_req$socket = req["socket"]) === null || _req$socket === void 0 ? void 0 : _req$socket["remoteAddress"];
|
|
228
|
+
return typeof remote === "string" ? remote.slice(0, MAX_IP_LENGTH) : void 0;
|
|
204
229
|
}
|
|
205
230
|
/**
|
|
206
231
|
* Extract a correlation context from a Kafka / RabbitMQ / SQS message object.
|
|
@@ -224,14 +249,14 @@ function extractMessageCorrelationId(message) {
|
|
|
224
249
|
const kafkaHeaders = message["headers"];
|
|
225
250
|
if (kafkaHeaders) {
|
|
226
251
|
const raw = kafkaHeaders["x-correlation-id"] ?? kafkaHeaders["correlationId"] ?? kafkaHeaders["correlation_id"];
|
|
227
|
-
if (raw !== void 0) return Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
|
|
252
|
+
if (raw !== void 0) return validIncomingId(Buffer.isBuffer(raw) ? raw.toString("utf8", 0, 512) : String(raw));
|
|
228
253
|
}
|
|
229
254
|
const sqsAttrs = message["MessageAttributes"];
|
|
230
255
|
if (sqsAttrs) {
|
|
231
256
|
var _sqsAttrs$xCorrelati, _sqsAttrs$correlation, _sqsAttrs$correlation2;
|
|
232
|
-
return ((_sqsAttrs$xCorrelati = sqsAttrs["x-correlation-id"]) === null || _sqsAttrs$xCorrelati === void 0 ? void 0 : _sqsAttrs$xCorrelati.StringValue) ?? ((_sqsAttrs$correlation = sqsAttrs["correlationId"]) === null || _sqsAttrs$correlation === void 0 ? void 0 : _sqsAttrs$correlation.StringValue) ?? ((_sqsAttrs$correlation2 = sqsAttrs["correlation_id"]) === null || _sqsAttrs$correlation2 === void 0 ? void 0 : _sqsAttrs$correlation2.StringValue);
|
|
257
|
+
return validIncomingId(((_sqsAttrs$xCorrelati = sqsAttrs["x-correlation-id"]) === null || _sqsAttrs$xCorrelati === void 0 ? void 0 : _sqsAttrs$xCorrelati.StringValue) ?? ((_sqsAttrs$correlation = sqsAttrs["correlationId"]) === null || _sqsAttrs$correlation === void 0 ? void 0 : _sqsAttrs$correlation.StringValue) ?? ((_sqsAttrs$correlation2 = sqsAttrs["correlation_id"]) === null || _sqsAttrs$correlation2 === void 0 ? void 0 : _sqsAttrs$correlation2.StringValue));
|
|
233
258
|
}
|
|
234
|
-
return message["correlationId"] ?? message["correlation_id"];
|
|
259
|
+
return validIncomingId(message["correlationId"] ?? message["correlation_id"]);
|
|
235
260
|
}
|
|
236
261
|
/**
|
|
237
262
|
* Build Kafka message headers containing the current correlation context.
|
package/dist/correlation.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"correlation.mjs","names":["ctx: CorrelationContext","headers: Record<string, string>"],"sources":["../src/correlation.ts"],"sourcesContent":["/**\n * logixia — Microservices Correlation ID Propagation\n *\n * Provides zero-boilerplate correlation ID generation and propagation for\n * distributed systems. A single correlation ID travels through every service\n * in a request fan-out, making it trivial to join logs across services.\n *\n * Features:\n * - UUID v4 correlation ID generation (via `crypto.randomUUID()`)\n * - Express / Fastify / NestJS incoming-request middleware\n * - `correlationFetch()` — wraps global `fetch`, auto-injects `X-Correlation-ID`\n * - `createCorrelationAxiosInterceptor()` — axios request interceptor\n * - `childFromRequest(req, logger)` — child logger with all request identifiers\n * - Kafka / SQS / RabbitMQ message context helpers\n * - Integration with `LogixiaContext` (AsyncLocalStorage) so every `logger.info()`\n * call automatically includes the correlationId without per-call passing.\n *\n * @example Express middleware\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use(correlationMiddleware());\n *\n * app.get('/orders', (req, res) => {\n * // correlationId is automatically in every log inside this request\n * logger.info('Listing orders');\n * res.json({ ok: true });\n * });\n * ```\n *\n * @example Outbound fetch with correlation\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Reads correlationId from current LogixiaContext and injects it as header\n * const data = await correlationFetch('https://payments.internal/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n *\n * @example Manual correlation context\n * ```ts\n * import { withCorrelationId } from 'logixia/correlation';\n *\n * await withCorrelationId('trace-abc-123', async () => {\n * // All logs and outbound fetch calls in here carry 'trace-abc-123'\n * await processOrder();\n * });\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\n\nimport { LogixiaContext } from './context/async-context';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/** Fields injected into the log context by the correlation middleware. */\nexport interface CorrelationContext {\n correlationId: string;\n traceId?: string;\n /** Service that originated the request (from `X-Origin-Service` header). */\n originService?: string;\n /** Index signature so CorrelationContext is assignable to LogContext. */\n [key: string]: unknown;\n}\n\nexport interface CorrelationMiddlewareOptions {\n /**\n * HTTP header to read/write the correlation ID from/to.\n * @default 'x-correlation-id'\n */\n header?: string;\n /**\n * HTTP header for the originating service name.\n * @default 'x-origin-service'\n */\n originServiceHeader?: string;\n /**\n * Function to generate a new correlation ID when none is present in the request.\n * @default () => crypto.randomUUID()\n */\n generate?: () => string;\n /**\n * When `true`, the response will include the correlation ID header so\n * clients can log it client-side.\n * @default true\n */\n setResponseHeader?: boolean;\n}\n\n// ── ID generation ─────────────────────────────────────────────────────────────\n\n/**\n * Generate a new correlation ID using `crypto.randomUUID()` (available in\n * Node.js 14.17+, all modern browsers, and Edge runtimes).\n *\n * When the Web Crypto global is unavailable (e.g. Node 18 without\n * `--experimental-global-webcrypto`), fall back to `node:crypto`'s\n * `randomUUID`, which returns a proper UUID v4 — matching the shape this\n * function's JSDoc and README promise. `logixia/correlation` is Node-only\n * (it builds on `AsyncLocalStorage`), so importing `node:crypto` is safe here.\n */\nexport function generateCorrelationId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return randomUUID();\n}\n\nconst MAX_INCOMING_ID_LENGTH = 128;\nconst VALID_INCOMING_ID = /^[A-Za-z0-9._:-]+$/;\n\n/**\n * Return an incoming correlation ID only if it is short and made of safe\n * characters. Anything else (over-long, empty, or containing newlines or other\n * control characters) is dropped so the caller generates a fresh ID instead of\n * writing client-supplied text into every log line.\n */\nfunction validIncomingId(value: string | undefined): string | undefined {\n if (value === undefined || value.length > MAX_INCOMING_ID_LENGTH) return undefined;\n return VALID_INCOMING_ID.test(value) ? value : undefined;\n}\n\n// ── Context helpers ───────────────────────────────────────────────────────────\n\n/**\n * Return the correlation ID from the current `LogixiaContext`, or `undefined`\n * when called outside any context scope.\n */\nexport function getCurrentCorrelationId(): string | undefined {\n const ctx = LogixiaContext.get();\n return ctx?.correlationId as string | undefined;\n}\n\n/**\n * Run `callback` inside a correlation context scope.\n *\n * If a correlation ID is not provided, a new UUID is generated.\n *\n * @example\n * ```ts\n * await withCorrelationId('cid-abc', async () => {\n * logger.info('Processing message'); // → { correlationId: 'cid-abc', ... }\n * await callDownstream(); // correlationFetch() picks up cid-abc\n * });\n * ```\n */\nexport function withCorrelationId<T>(correlationId: string | undefined, callback: () => T): T {\n const id = correlationId ?? generateCorrelationId();\n return LogixiaContext.run({ correlationId: id }, callback);\n}\n\n// ── Express / Connect middleware ──────────────────────────────────────────────\n\n/**\n * Express-compatible middleware that:\n * 1. Reads `X-Correlation-ID` from the incoming request (or generates a new one)\n * 2. Wraps the request in a `LogixiaContext` so all loggers in the handler\n * automatically include `correlationId`\n * 3. Writes the correlation ID back to the response header (opt-out via options)\n *\n * @example\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * app.use(correlationMiddleware());\n * ```\n */\nexport function correlationMiddleware(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationMiddleware(\n req: Record<string, unknown>,\n res: Record<string, unknown>,\n next: () => void\n ): void {\n const headers = (req['headers'] ?? {}) as Record<string, string | undefined>;\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();\n const originService = headers[originServiceHeader.toLowerCase()];\n\n if (setResponseHeader) {\n const setHeader = res['setHeader'] as ((name: string, value: string) => void) | undefined;\n if (typeof setHeader === 'function') {\n setHeader.call(res, header, correlationId);\n }\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, next);\n };\n}\n\n// ── Fastify hook ──────────────────────────────────────────────────────────────\n\n/**\n * Fastify `onRequest` lifecycle hook — same functionality as `correlationMiddleware`\n * but adapted to Fastify's hook API.\n *\n * @example\n * ```ts\n * import { correlationFastifyHook } from 'logixia/correlation';\n * fastify.addHook('onRequest', correlationFastifyHook());\n * ```\n */\nexport function correlationFastifyHook(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationFastifyHook(\n request: Record<string, unknown>,\n reply: Record<string, unknown>,\n done: () => void\n ): void {\n const headers = (request['headers'] ?? {}) as Record<string, string | undefined>;\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();\n const originService = headers[originServiceHeader.toLowerCase()];\n\n if (setResponseHeader) {\n const replyHeader = reply['header'] as ((name: string, value: string) => void) | undefined;\n if (typeof replyHeader === 'function') {\n replyHeader.call(reply, header, correlationId);\n }\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, done);\n };\n}\n\n// ── Outbound fetch wrapper ────────────────────────────────────────────────────\n\n/**\n * A drop-in replacement for `fetch` that automatically injects the current\n * `correlationId` from `LogixiaContext` as an `X-Correlation-ID` request header.\n *\n * Falls back to the global `fetch` when no correlation context is active.\n *\n * @example\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Inside a request handler with correlationMiddleware active:\n * const response = await correlationFetch('https://payments.internal/v1/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n */\nexport async function correlationFetch(\n input: string | URL | Request,\n init: RequestInit = {},\n options: { header?: string } = {}\n): Promise<Response> {\n const header = options.header ?? 'x-correlation-id';\n const correlationId = getCurrentCorrelationId();\n\n // `init.headers` can be a plain object, a `Headers` instance, or an array of\n // `[name, value]` tuples. Spreading it into a plain object loses everything\n // but the object form: a `Headers` instance has no own enumerable properties,\n // and an array spread yields numeric indices. `Headers` accepts all three, is\n // case-insensitive, and is the shape `fetch` expects back.\n //\n // A `Request` carries headers of its own, and `fetch(request, { headers })`\n // replaces them rather than merging them, so they are seeded first and the\n // caller's `init.headers` is applied on top. Without this, accepting a\n // `Request` here would drop its headers, which is the same defect one input\n // shape over.\n const headers = new Headers(input instanceof Request ? input.headers : undefined);\n for (const [name, value] of new Headers(init.headers)) {\n headers.set(name, value);\n }\n if (correlationId && !headers.has(header)) {\n headers.set(header, correlationId);\n }\n\n return globalThis.fetch(input, { ...init, headers });\n}\n\n// ── Axios interceptor factory ─────────────────────────────────────────────────\n\n/**\n * Returns an axios request interceptor that automatically injects the current\n * correlation ID into outgoing axios requests.\n *\n * @example\n * ```ts\n * import axios from 'axios';\n * import { createCorrelationAxiosInterceptor } from 'logixia/correlation';\n *\n * axios.interceptors.request.use(createCorrelationAxiosInterceptor());\n * ```\n */\nexport function createCorrelationAxiosInterceptor(\n options: { header?: string } = {}\n): (config: Record<string, unknown>) => Record<string, unknown> {\n const header = options.header ?? 'x-correlation-id';\n\n return function correlationAxiosInterceptor(\n config: Record<string, unknown>\n ): Record<string, unknown> {\n const correlationId = getCurrentCorrelationId();\n if (!correlationId) return config;\n\n const headers = (config['headers'] ?? {}) as Record<string, unknown>;\n if (!headers[header]) {\n headers[header] = correlationId;\n }\n\n return { ...config, headers };\n };\n}\n\n// ── Child logger from request ─────────────────────────────────────────────────\n\n/**\n * Create a child logger enriched with all correlation-relevant identifiers\n * extracted from the incoming HTTP request.\n *\n * The child logger's every call automatically includes:\n * `correlationId`, `requestId`, `method`, `url`, `userAgent`, `ip`\n *\n * @example\n * ```ts\n * import { childFromRequest } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use((req, res, next) => {\n * req.logger = childFromRequest(req, logger);\n * next();\n * });\n *\n * app.get('/orders', (req, res) => {\n * req.logger.info('Listing orders'); // → correlationId, requestId, method, url ...\n * });\n * ```\n */\nexport function childFromRequest<TLogger extends { child(ctx: Record<string, unknown>): TLogger }>(\n req: Record<string, unknown>,\n logger: TLogger,\n options: { correlationHeader?: string } = {}\n): TLogger {\n const correlationHeader = options.correlationHeader ?? 'x-correlation-id';\n\n const headers = (req['headers'] ?? {}) as Record<string, string | undefined>;\n // Node lower-cases incoming header names, so look up with a lower-cased key.\n const correlationId =\n validIncomingId(headers[correlationHeader.toLowerCase()]) ??\n getCurrentCorrelationId() ??\n generateCorrelationId();\n\n const method = req['method'] as string | undefined;\n const url = (req['url'] as string | undefined) ?? (req['originalUrl'] as string | undefined);\n const userAgent = headers['user-agent'];\n const ip =\n (headers['x-forwarded-for'] as string | undefined)?.split(',')[0]?.trim() ??\n ((req['socket'] as Record<string, unknown> | undefined)?.['remoteAddress'] as\n string | undefined);\n\n return logger.child({\n correlationId,\n ...(method !== undefined ? { method } : {}),\n ...(url !== undefined ? { url } : {}),\n ...(userAgent !== undefined ? { userAgent } : {}),\n ...(ip !== undefined ? { ip } : {}),\n });\n}\n\n// ── Message queue helpers ─────────────────────────────────────────────────────\n\n/**\n * Extract a correlation context from a Kafka / RabbitMQ / SQS message object.\n *\n * Looks for `correlationId`, `correlation_id`, or `x-correlation-id` in\n * `message.headers` (Kafka) or `message.MessageAttributes` (SQS).\n *\n * @example Kafka consumer\n * ```ts\n * import { extractMessageCorrelationId, withCorrelationId } from 'logixia/correlation';\n *\n * consumer.run({\n * eachMessage: async ({ message }) => {\n * const correlationId = extractMessageCorrelationId(message);\n * await withCorrelationId(correlationId, () => processMessage(message));\n * },\n * });\n * ```\n */\nexport function extractMessageCorrelationId(message: Record<string, unknown>): string | undefined {\n // Kafka: message.headers is Record<string, Buffer | string>\n const kafkaHeaders = message['headers'] as Record<string, Buffer | string> | undefined;\n if (kafkaHeaders) {\n const raw =\n kafkaHeaders['x-correlation-id'] ??\n kafkaHeaders['correlationId'] ??\n kafkaHeaders['correlation_id'];\n if (raw !== undefined) {\n return Buffer.isBuffer(raw) ? raw.toString('utf8') : String(raw);\n }\n }\n\n // SQS: message.MessageAttributes\n const sqsAttrs = message['MessageAttributes'] as\n Record<string, { StringValue?: string }> | undefined;\n if (sqsAttrs) {\n return (\n sqsAttrs['x-correlation-id']?.StringValue ??\n sqsAttrs['correlationId']?.StringValue ??\n sqsAttrs['correlation_id']?.StringValue\n );\n }\n\n // AMQP / generic envelope\n return (\n (message['correlationId'] as string | undefined) ??\n (message['correlation_id'] as string | undefined)\n );\n}\n\n/**\n * Build Kafka message headers containing the current correlation context.\n *\n * @example\n * ```ts\n * import { buildKafkaCorrelationHeaders } from 'logixia/correlation';\n *\n * await producer.send({\n * topic: 'orders',\n * messages: [{ value: JSON.stringify(order), headers: buildKafkaCorrelationHeaders() }],\n * });\n * ```\n */\nexport function buildKafkaCorrelationHeaders(): Record<string, string> {\n const ctx = LogixiaContext.get();\n const headers: Record<string, string> = {};\n\n const correlationId = ctx?.correlationId as string | undefined;\n if (correlationId) headers['x-correlation-id'] = correlationId;\n\n const traceId = ctx?.traceId as string | undefined;\n if (traceId) headers['x-trace-id'] = traceId;\n\n return headers;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"correlation.mjs","names":["ctx: CorrelationContext","headers: Record<string, string>"],"sources":["../src/correlation.ts"],"sourcesContent":["/**\n * logixia — Microservices Correlation ID Propagation\n *\n * Provides zero-boilerplate correlation ID generation and propagation for\n * distributed systems. A single correlation ID travels through every service\n * in a request fan-out, making it trivial to join logs across services.\n *\n * Features:\n * - UUID v4 correlation ID generation (via `crypto.randomUUID()`)\n * - Express / Fastify / NestJS incoming-request middleware\n * - `correlationFetch()` — wraps global `fetch`, auto-injects `X-Correlation-ID`\n * - `createCorrelationAxiosInterceptor()` — axios request interceptor\n * - `childFromRequest(req, logger)` — child logger with all request identifiers\n * - Kafka / SQS / RabbitMQ message context helpers\n * - Integration with `LogixiaContext` (AsyncLocalStorage) so every `logger.info()`\n * call automatically includes the correlationId without per-call passing.\n *\n * @example Express middleware\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use(correlationMiddleware());\n *\n * app.get('/orders', (req, res) => {\n * // correlationId is automatically in every log inside this request\n * logger.info('Listing orders');\n * res.json({ ok: true });\n * });\n * ```\n *\n * @example Outbound fetch with correlation\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Reads correlationId from current LogixiaContext and injects it as header\n * const data = await correlationFetch('https://payments.internal/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n *\n * @example Manual correlation context\n * ```ts\n * import { withCorrelationId } from 'logixia/correlation';\n *\n * await withCorrelationId('trace-abc-123', async () => {\n * // All logs and outbound fetch calls in here carry 'trace-abc-123'\n * await processOrder();\n * });\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\n\nimport { LogixiaContext } from './context/async-context';\nimport { sanitizeIncomingId } from './utils/trace.utils';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/** Fields injected into the log context by the correlation middleware. */\nexport interface CorrelationContext {\n correlationId: string;\n traceId?: string;\n /** Service that originated the request (from `X-Origin-Service` header). */\n originService?: string;\n /** Index signature so CorrelationContext is assignable to LogContext. */\n [key: string]: unknown;\n}\n\nexport interface CorrelationMiddlewareOptions {\n /**\n * HTTP header to read/write the correlation ID from/to.\n * @default 'x-correlation-id'\n */\n header?: string;\n /**\n * HTTP header for the originating service name.\n * @default 'x-origin-service'\n */\n originServiceHeader?: string;\n /**\n * Function to generate a new correlation ID when none is present in the request.\n * @default () => crypto.randomUUID()\n */\n generate?: () => string;\n /**\n * When `false`, the incoming correlation header is ignored and a new ID is\n * always generated. Use this at the edge when client-supplied IDs should not\n * be trusted.\n * @default true\n */\n trustIncoming?: boolean;\n /**\n * When `true`, the response will include the correlation ID header so\n * clients can log it client-side.\n * @default true\n */\n setResponseHeader?: boolean;\n}\n\n// ── ID generation ─────────────────────────────────────────────────────────────\n\n/**\n * Generate a new correlation ID using `crypto.randomUUID()` (available in\n * Node.js 14.17+, all modern browsers, and Edge runtimes).\n *\n * When the Web Crypto global is unavailable (e.g. Node 18 without\n * `--experimental-global-webcrypto`), fall back to `node:crypto`'s\n * `randomUUID`, which returns a proper UUID v4 — matching the shape this\n * function's JSDoc and README promise. `logixia/correlation` is Node-only\n * (it builds on `AsyncLocalStorage`), so importing `node:crypto` is safe here.\n */\nexport function generateCorrelationId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return randomUUID();\n}\n\n/**\n * Return an incoming correlation ID only if it is short and made of safe\n * characters. Anything else (over-long, empty, or containing newlines or other\n * control characters) is dropped so the caller generates a fresh ID instead of\n * writing client-supplied text into every log line.\n */\nfunction validIncomingId(value: unknown): string | undefined {\n if (typeof value === 'string' && value.trim() !== value) return undefined;\n return sanitizeIncomingId(value);\n}\n\n// ── Context helpers ───────────────────────────────────────────────────────────\n\n/**\n * Return the correlation ID from the current `LogixiaContext`, or `undefined`\n * when called outside any context scope.\n */\nexport function getCurrentCorrelationId(): string | undefined {\n const ctx = LogixiaContext.get();\n return ctx?.correlationId as string | undefined;\n}\n\n/**\n * Run `callback` inside a correlation context scope.\n *\n * If a correlation ID is not provided, a new UUID is generated.\n *\n * @example\n * ```ts\n * await withCorrelationId('cid-abc', async () => {\n * logger.info('Processing message'); // → { correlationId: 'cid-abc', ... }\n * await callDownstream(); // correlationFetch() picks up cid-abc\n * });\n * ```\n */\nexport function withCorrelationId<T>(correlationId: string | undefined, callback: () => T): T {\n const id = correlationId ?? generateCorrelationId();\n return LogixiaContext.run({ correlationId: id }, callback);\n}\n\n// ── Express / Connect middleware ──────────────────────────────────────────────\n\n/**\n * Express-compatible middleware that:\n * 1. Reads `X-Correlation-ID` from the incoming request (or generates a new one)\n * 2. Wraps the request in a `LogixiaContext` so all loggers in the handler\n * automatically include `correlationId`\n * 3. Writes the correlation ID back to the response header (opt-out via options)\n *\n * @example\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * app.use(correlationMiddleware());\n * ```\n */\nexport function correlationMiddleware(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationMiddleware(\n req: Record<string, unknown>,\n res: Record<string, unknown>,\n next: () => void\n ): void {\n const headers = (req['headers'] ?? {}) as Record<string, string | undefined>;\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n const setHeader = res['setHeader'] as ((name: string, value: string) => void) | undefined;\n if (typeof setHeader === 'function') {\n setHeader.call(res, header, correlationId);\n }\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, next);\n };\n}\n\n// ── Fastify hook ──────────────────────────────────────────────────────────────\n\n/**\n * Fastify `onRequest` lifecycle hook — same functionality as `correlationMiddleware`\n * but adapted to Fastify's hook API.\n *\n * @example\n * ```ts\n * import { correlationFastifyHook } from 'logixia/correlation';\n * fastify.addHook('onRequest', correlationFastifyHook());\n * ```\n */\nexport function correlationFastifyHook(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationFastifyHook(\n request: Record<string, unknown>,\n reply: Record<string, unknown>,\n done: () => void\n ): void {\n const headers = (request['headers'] ?? {}) as Record<string, string | undefined>;\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n const replyHeader = reply['header'] as ((name: string, value: string) => void) | undefined;\n if (typeof replyHeader === 'function') {\n replyHeader.call(reply, header, correlationId);\n }\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, done);\n };\n}\n\n// ── Outbound fetch wrapper ────────────────────────────────────────────────────\n\n/**\n * A drop-in replacement for `fetch` that automatically injects the current\n * `correlationId` from `LogixiaContext` as an `X-Correlation-ID` request header.\n *\n * Falls back to the global `fetch` when no correlation context is active.\n *\n * @example\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Inside a request handler with correlationMiddleware active:\n * const response = await correlationFetch('https://payments.internal/v1/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n */\nexport async function correlationFetch(\n input: string | URL | Request,\n init: RequestInit = {},\n options: { header?: string } = {}\n): Promise<Response> {\n const header = options.header ?? 'x-correlation-id';\n const correlationId = getCurrentCorrelationId();\n\n // `init.headers` can be a plain object, a `Headers` instance, or an array of\n // `[name, value]` tuples. Spreading it into a plain object loses everything\n // but the object form: a `Headers` instance has no own enumerable properties,\n // and an array spread yields numeric indices. `Headers` accepts all three, is\n // case-insensitive, and is the shape `fetch` expects back.\n //\n // A `Request` carries headers of its own, and `fetch(request, { headers })`\n // replaces them rather than merging them, so they are seeded first and the\n // caller's `init.headers` is applied on top. Without this, accepting a\n // `Request` here would drop its headers, which is the same defect one input\n // shape over.\n const headers = new Headers(input instanceof Request ? input.headers : undefined);\n for (const [name, value] of new Headers(init.headers)) {\n headers.set(name, value);\n }\n if (correlationId && !headers.has(header)) {\n headers.set(header, correlationId);\n }\n\n return globalThis.fetch(input, { ...init, headers });\n}\n\n// ── Axios interceptor factory ─────────────────────────────────────────────────\n\n/**\n * Returns an axios request interceptor that automatically injects the current\n * correlation ID into outgoing axios requests.\n *\n * @example\n * ```ts\n * import axios from 'axios';\n * import { createCorrelationAxiosInterceptor } from 'logixia/correlation';\n *\n * axios.interceptors.request.use(createCorrelationAxiosInterceptor());\n * ```\n */\nexport function createCorrelationAxiosInterceptor(\n options: { header?: string } = {}\n): (config: Record<string, unknown>) => Record<string, unknown> {\n const header = options.header ?? 'x-correlation-id';\n\n return function correlationAxiosInterceptor(\n config: Record<string, unknown>\n ): Record<string, unknown> {\n const correlationId = getCurrentCorrelationId();\n if (!correlationId) return config;\n\n const headers = (config['headers'] ?? {}) as Record<string, unknown>;\n if (!headers[header]) {\n headers[header] = correlationId;\n }\n\n return { ...config, headers };\n };\n}\n\n// ── Child logger from request ─────────────────────────────────────────────────\n\nconst MAX_USER_AGENT_LENGTH = 512;\nconst MAX_IP_LENGTH = 64;\n\nexport interface ChildFromRequestOptions {\n /** Header to read the correlation ID from. @default 'x-correlation-id' */\n correlationHeader?: string;\n /**\n * Take the client IP from the first `X-Forwarded-For` entry. Only enable this\n * when the app sits behind a proxy that overwrites the header; otherwise any\n * client can choose the logged IP.\n * @default false\n */\n trustProxy?: boolean;\n /**\n * Context name passed as the first argument to `logger.child(context, data)`.\n * When omitted, logixia loggers (detected by their `getContext` /\n * `getAvailableTransports` methods) get `child('request', fields)` and any other\n * logger gets `child(fields)`.\n */\n childContext?: string;\n}\n\n/**\n * Create a child logger enriched with all correlation-relevant identifiers\n * extracted from the incoming HTTP request.\n *\n * The child logger's every call automatically includes:\n * `correlationId`, `method`, `url`, `userAgent`, `ip`\n *\n * The IP comes from `req.ip` / the socket address unless `trustProxy` is set.\n *\n * @example\n * ```ts\n * import { childFromRequest } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use((req, res, next) => {\n * req.logger = childFromRequest(req, logger);\n * next();\n * });\n *\n * app.get('/orders', (req, res) => {\n * req.logger.info('Listing orders'); // → correlationId, requestId, method, url ...\n * });\n * ```\n */\nexport function childFromRequest<TLogger extends { child(...args: never[]): TLogger }>(\n req: Record<string, unknown>,\n logger: TLogger,\n options: ChildFromRequestOptions = {}\n): TLogger {\n const correlationHeader = options.correlationHeader ?? 'x-correlation-id';\n\n const headers = (req['headers'] ?? {}) as Record<string, string | undefined>;\n // Node lower-cases incoming header names, so look up with a lower-cased key.\n const correlationId =\n validIncomingId(headers[correlationHeader.toLowerCase()]) ??\n getCurrentCorrelationId() ??\n generateCorrelationId();\n\n const method = req['method'] as string | undefined;\n const url = (req['url'] as string | undefined) ?? (req['originalUrl'] as string | undefined);\n const rawUserAgent = headers['user-agent'];\n const userAgent =\n typeof rawUserAgent === 'string' ? rawUserAgent.slice(0, MAX_USER_AGENT_LENGTH) : undefined;\n const ip = resolveClientIp(req, headers, options.trustProxy === true);\n\n const fields = {\n correlationId,\n ...(method !== undefined ? { method } : {}),\n ...(url !== undefined ? { url } : {}),\n ...(userAgent !== undefined ? { userAgent } : {}),\n ...(ip !== undefined ? { ip } : {}),\n };\n\n const child = logger.child as (...args: unknown[]) => TLogger;\n if (options.childContext !== undefined) {\n return child.call(logger, options.childContext, fields);\n }\n // logixia's ILogger.child(context: string, data?) takes a string namespace;\n // pino/winston-style loggers take a bindings object.\n if (usesStringContext(logger)) {\n return child.call(logger, 'request', fields);\n }\n return child.call(logger, fields);\n}\n\n/** ILogger implementations (LogixiaLogger, LogixiaLoggerService, mock logger) expose these methods. */\nfunction usesStringContext(logger: object): boolean {\n const l = logger as Record<string, unknown>;\n return typeof l['getContext'] === 'function' || typeof l['getAvailableTransports'] === 'function';\n}\n\nfunction resolveClientIp(\n req: Record<string, unknown>,\n headers: Record<string, string | undefined>,\n trustProxy: boolean\n): string | undefined {\n if (trustProxy) {\n const forwarded = headers['x-forwarded-for'];\n const first = typeof forwarded === 'string' ? forwarded.split(',')[0]?.trim() : undefined;\n if (first) return first.slice(0, MAX_IP_LENGTH);\n }\n const fromReq = req['ip'];\n if (typeof fromReq === 'string' && fromReq) return fromReq.slice(0, MAX_IP_LENGTH);\n const remote = (req['socket'] as Record<string, unknown> | undefined)?.['remoteAddress'];\n return typeof remote === 'string' ? remote.slice(0, MAX_IP_LENGTH) : undefined;\n}\n\n// ── Message queue helpers ─────────────────────────────────────────────────────\n\n/**\n * Extract a correlation context from a Kafka / RabbitMQ / SQS message object.\n *\n * Looks for `correlationId`, `correlation_id`, or `x-correlation-id` in\n * `message.headers` (Kafka) or `message.MessageAttributes` (SQS).\n *\n * @example Kafka consumer\n * ```ts\n * import { extractMessageCorrelationId, withCorrelationId } from 'logixia/correlation';\n *\n * consumer.run({\n * eachMessage: async ({ message }) => {\n * const correlationId = extractMessageCorrelationId(message);\n * await withCorrelationId(correlationId, () => processMessage(message));\n * },\n * });\n * ```\n */\nexport function extractMessageCorrelationId(message: Record<string, unknown>): string | undefined {\n // Kafka: message.headers is Record<string, Buffer | string>\n const kafkaHeaders = message['headers'] as Record<string, Buffer | string> | undefined;\n if (kafkaHeaders) {\n const raw =\n kafkaHeaders['x-correlation-id'] ??\n kafkaHeaders['correlationId'] ??\n kafkaHeaders['correlation_id'];\n if (raw !== undefined) {\n return validIncomingId(Buffer.isBuffer(raw) ? raw.toString('utf8', 0, 512) : String(raw));\n }\n }\n\n // SQS: message.MessageAttributes\n const sqsAttrs = message['MessageAttributes'] as\n Record<string, { StringValue?: string }> | undefined;\n if (sqsAttrs) {\n return validIncomingId(\n sqsAttrs['x-correlation-id']?.StringValue ??\n sqsAttrs['correlationId']?.StringValue ??\n sqsAttrs['correlation_id']?.StringValue\n );\n }\n\n // AMQP / generic envelope\n return validIncomingId(message['correlationId'] ?? message['correlation_id']);\n}\n\n/**\n * Build Kafka message headers containing the current correlation context.\n *\n * @example\n * ```ts\n * import { buildKafkaCorrelationHeaders } from 'logixia/correlation';\n *\n * await producer.send({\n * topic: 'orders',\n * messages: [{ value: JSON.stringify(order), headers: buildKafkaCorrelationHeaders() }],\n * });\n * ```\n */\nexport function buildKafkaCorrelationHeaders(): Record<string, string> {\n const ctx = LogixiaContext.get();\n const headers: Record<string, string> = {};\n\n const correlationId = ctx?.correlationId as string | undefined;\n if (correlationId) headers['x-correlation-id'] = correlationId;\n\n const traceId = ctx?.traceId as string | undefined;\n if (traceId) headers['x-trace-id'] = traceId;\n\n return headers;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/transports/entry-fields.ts
|
|
2
|
+
const PAYLOAD_PREFIX = "data_";
|
|
3
|
+
const UNSAFE_KEYS = new Set(["__proto__"]);
|
|
4
|
+
/**
|
|
5
|
+
* Copies `core` and then the user payload into one object. The core fields stay
|
|
6
|
+
* authoritative: a payload key that matches a core key (even one whose value is
|
|
7
|
+
* undefined) or a reserved name is kept under a `data_` prefix instead. The
|
|
8
|
+
* prefix is repeated until the name is free of core keys and of every other
|
|
9
|
+
* payload key, so the result does not depend on key order and a payload's own
|
|
10
|
+
* `data_level` stays where it is.
|
|
11
|
+
*/
|
|
12
|
+
function mergeCoreFields(core, data, extraReserved = []) {
|
|
13
|
+
const merged = { ...core };
|
|
14
|
+
if (!data) return merged;
|
|
15
|
+
const reserved = new Set([...Object.keys(core), ...extraReserved]);
|
|
16
|
+
const payloadKeys = new Set(Object.keys(data));
|
|
17
|
+
for (const key of payloadKeys) {
|
|
18
|
+
if (UNSAFE_KEYS.has(key)) continue;
|
|
19
|
+
let target = key;
|
|
20
|
+
if (reserved.has(target)) do
|
|
21
|
+
target = `${PAYLOAD_PREFIX}${target}`;
|
|
22
|
+
while (reserved.has(target) || payloadKeys.has(target));
|
|
23
|
+
merged[target] = data[key];
|
|
24
|
+
}
|
|
25
|
+
return merged;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { mergeCoreFields as t };
|
|
30
|
+
//# sourceMappingURL=entry-fields-BJkuPnIg.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-fields-BJkuPnIg.mjs","names":["merged: Record<string, unknown>"],"sources":["../src/transports/entry-fields.ts"],"sourcesContent":["const PAYLOAD_PREFIX = 'data_';\nconst UNSAFE_KEYS = new Set(['__proto__']);\n\n/**\n * Copies `core` and then the user payload into one object. The core fields stay\n * authoritative: a payload key that matches a core key (even one whose value is\n * undefined) or a reserved name is kept under a `data_` prefix instead. The\n * prefix is repeated until the name is free of core keys and of every other\n * payload key, so the result does not depend on key order and a payload's own\n * `data_level` stays where it is.\n */\nexport function mergeCoreFields(\n core: Record<string, unknown>,\n data: Record<string, unknown> | undefined,\n extraReserved: readonly string[] = []\n): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...core };\n if (!data) return merged;\n const reserved = new Set([...Object.keys(core), ...extraReserved]);\n const payloadKeys = new Set(Object.keys(data));\n for (const key of payloadKeys) {\n if (UNSAFE_KEYS.has(key)) continue;\n let target = key;\n if (reserved.has(target)) {\n do {\n target = `${PAYLOAD_PREFIX}${target}`;\n } while (reserved.has(target) || payloadKeys.has(target));\n }\n merged[target] = data[key];\n }\n return merged;\n}\n"],"mappings":";AAAA,MAAM,iBAAiB;AACvB,MAAM,cAAc,IAAI,IAAI,CAAC,YAAY,CAAC;;;;;;;;;AAU1C,SAAgB,gBACd,MACA,MACA,gBAAmC,EAAE,EACZ;CACzB,MAAMA,SAAkC,EAAE,GAAG,MAAM;AACnD,KAAI,CAAC,KAAM,QAAO;CAClB,MAAM,WAAW,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,KAAK,EAAE,GAAG,cAAc,CAAC;CAClE,MAAM,cAAc,IAAI,IAAI,OAAO,KAAK,KAAK,CAAC;AAC9C,MAAK,MAAM,OAAO,aAAa;AAC7B,MAAI,YAAY,IAAI,IAAI,CAAE;EAC1B,IAAI,SAAS;AACb,MAAI,SAAS,IAAI,OAAO,CACtB;AACE,YAAS,GAAG,iBAAiB;SACtB,SAAS,IAAI,OAAO,IAAI,YAAY,IAAI,OAAO;AAE1D,SAAO,UAAU,KAAK;;AAExB,QAAO"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/transports/entry-fields.ts
|
|
3
|
+
const PAYLOAD_PREFIX = "data_";
|
|
4
|
+
const UNSAFE_KEYS = new Set(["__proto__"]);
|
|
5
|
+
/**
|
|
6
|
+
* Copies `core` and then the user payload into one object. The core fields stay
|
|
7
|
+
* authoritative: a payload key that matches a core key (even one whose value is
|
|
8
|
+
* undefined) or a reserved name is kept under a `data_` prefix instead. The
|
|
9
|
+
* prefix is repeated until the name is free of core keys and of every other
|
|
10
|
+
* payload key, so the result does not depend on key order and a payload's own
|
|
11
|
+
* `data_level` stays where it is.
|
|
12
|
+
*/
|
|
13
|
+
function mergeCoreFields(core, data, extraReserved = []) {
|
|
14
|
+
const merged = { ...core };
|
|
15
|
+
if (!data) return merged;
|
|
16
|
+
const reserved = new Set([...Object.keys(core), ...extraReserved]);
|
|
17
|
+
const payloadKeys = new Set(Object.keys(data));
|
|
18
|
+
for (const key of payloadKeys) {
|
|
19
|
+
if (UNSAFE_KEYS.has(key)) continue;
|
|
20
|
+
let target = key;
|
|
21
|
+
if (reserved.has(target)) do
|
|
22
|
+
target = `${PAYLOAD_PREFIX}${target}`;
|
|
23
|
+
while (reserved.has(target) || payloadKeys.has(target));
|
|
24
|
+
merged[target] = data[key];
|
|
25
|
+
}
|
|
26
|
+
return merged;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
Object.defineProperty(exports, 'mergeCoreFields', {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
get: function () {
|
|
33
|
+
return mergeCoreFields;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
//# sourceMappingURL=entry-fields-BvOLFlJ4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-fields-BvOLFlJ4.js","names":["merged: Record<string, unknown>"],"sources":["../src/transports/entry-fields.ts"],"sourcesContent":["const PAYLOAD_PREFIX = 'data_';\nconst UNSAFE_KEYS = new Set(['__proto__']);\n\n/**\n * Copies `core` and then the user payload into one object. The core fields stay\n * authoritative: a payload key that matches a core key (even one whose value is\n * undefined) or a reserved name is kept under a `data_` prefix instead. The\n * prefix is repeated until the name is free of core keys and of every other\n * payload key, so the result does not depend on key order and a payload's own\n * `data_level` stays where it is.\n */\nexport function mergeCoreFields(\n core: Record<string, unknown>,\n data: Record<string, unknown> | undefined,\n extraReserved: readonly string[] = []\n): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...core };\n if (!data) return merged;\n const reserved = new Set([...Object.keys(core), ...extraReserved]);\n const payloadKeys = new Set(Object.keys(data));\n for (const key of payloadKeys) {\n if (UNSAFE_KEYS.has(key)) continue;\n let target = key;\n if (reserved.has(target)) {\n do {\n target = `${PAYLOAD_PREFIX}${target}`;\n } while (reserved.has(target) || payloadKeys.has(target));\n }\n merged[target] = data[key];\n }\n return merged;\n}\n"],"mappings":";;AAAA,MAAM,iBAAiB;AACvB,MAAM,cAAc,IAAI,IAAI,CAAC,YAAY,CAAC;;;;;;;;;AAU1C,SAAgB,gBACd,MACA,MACA,gBAAmC,EAAE,EACZ;CACzB,MAAMA,SAAkC,EAAE,GAAG,MAAM;AACnD,KAAI,CAAC,KAAM,QAAO;CAClB,MAAM,WAAW,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,KAAK,EAAE,GAAG,cAAc,CAAC;CAClE,MAAM,cAAc,IAAI,IAAI,OAAO,KAAK,KAAK,CAAC;AAC9C,MAAK,MAAM,OAAO,aAAa;AAC7B,MAAI,YAAY,IAAI,IAAI,CAAE;EAC1B,IAAI,SAAS;AACb,MAAI,SAAS,IAAI,OAAO,CACtB;AACE,YAAS,GAAG,iBAAiB;SACtB,SAAS,IAAI,OAAO,IAAI,YAAY,IAAI,OAAO;AAE1D,SAAO,UAAU,KAAK;;AAExB,QAAO"}
|
package/dist/gcp.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./transport.types-
|
|
2
|
-
import { n as GCPTransport, r as GCPTransportConfig, t as GCPMonitoredResource } from "./gcp.transport-
|
|
1
|
+
import "./transport.types-CtNr3D7S.mjs";
|
|
2
|
+
import { n as GCPTransport, r as GCPTransportConfig, t as GCPMonitoredResource } from "./gcp.transport-zzNyIoih.mjs";
|
|
3
3
|
export { GCPMonitoredResource, GCPTransport, GCPTransportConfig };
|
package/dist/gcp.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./transport.types-
|
|
2
|
-
import { n as GCPTransport, r as GCPTransportConfig, t as GCPMonitoredResource } from "./gcp.transport-
|
|
1
|
+
import "./transport.types-DqVLZTyf.js";
|
|
2
|
+
import { n as GCPTransport, r as GCPTransportConfig, t as GCPMonitoredResource } from "./gcp.transport-m4fVw0b1.js";
|
|
3
3
|
export { GCPMonitoredResource, GCPTransport, GCPTransportConfig };
|
package/dist/gcp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require('./internal-log-BUJYMZbE.js');
|
|
2
2
|
require('./safe-stringify-DWo3aZp_.js');
|
|
3
3
|
require('./batch-buffer-BY-mRWIK.js');
|
|
4
|
-
|
|
4
|
+
require('./entry-fields-BvOLFlJ4.js');
|
|
5
|
+
const require_gcp_transport = require('./gcp.transport-CHV8MGgz.js');
|
|
5
6
|
|
|
6
7
|
exports.GCPTransport = require_gcp_transport.GCPTransport;
|