logixia 1.10.2 → 1.11.0
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 +121 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/{index-Co47qPnq.d.mts → index-CSFeEGLb.d.ts} +32 -2
- package/dist/index-CSFeEGLb.d.ts.map +1 -0
- package/dist/{index-F-A7hg1u.d.ts → index-Cw-sN_0_.d.mts} +32 -2
- package/dist/index-Cw-sN_0_.d.mts.map +1 -0
- package/dist/index.d.mts +256 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +256 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +569 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +557 -34
- package/dist/index.mjs.map +1 -1
- package/dist/{logitron-logger.module-BLT1y5Iq.d.ts → logitron-logger.module-DGwNfjBX.d.mts} +21 -2
- package/dist/logitron-logger.module-DGwNfjBX.d.mts.map +1 -0
- package/dist/{logitron-logger.module-bJ1hGhaL.js → logitron-logger.module-DHFampon.js} +186 -38
- package/dist/logitron-logger.module-DHFampon.js.map +1 -0
- package/dist/{logitron-logger.module-B8NklSC4.d.mts → logitron-logger.module-DfyBsT_K.d.ts} +21 -2
- package/dist/logitron-logger.module-DfyBsT_K.d.ts.map +1 -0
- package/dist/{logitron-logger.module-Bt_Jei1V.mjs → logitron-logger.module-QYBy_Kkq.mjs} +186 -38
- package/dist/logitron-logger.module-QYBy_Kkq.mjs.map +1 -0
- package/dist/middleware.d.mts +1 -1
- package/dist/middleware.d.mts.map +1 -1
- package/dist/middleware.d.ts +1 -1
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +4 -3
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +4 -3
- package/dist/middleware.mjs.map +1 -1
- package/dist/nest.d.mts +2 -2
- package/dist/nest.d.mts.map +1 -1
- package/dist/nest.d.ts +2 -2
- package/dist/nest.d.ts.map +1 -1
- package/dist/nest.js +2 -2
- package/dist/nest.mjs +2 -2
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{transport.manager-zgEZCJhR.js → transport.manager-B9LF9uDd.js} +130 -56
- package/dist/transport.manager-B9LF9uDd.js.map +1 -0
- package/dist/{transport.manager-CaL4XuLD.mjs → transport.manager-Cij_sA-b.mjs} +128 -56
- package/dist/transport.manager-Cij_sA-b.mjs.map +1 -0
- package/dist/transports.d.mts +42 -3
- package/dist/transports.d.mts.map +1 -1
- package/dist/transports.d.ts +42 -3
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +1 -1
- package/dist/transports.mjs +1 -1
- package/package.json +1 -1
- package/dist/index-Co47qPnq.d.mts.map +0 -1
- package/dist/index-F-A7hg1u.d.ts.map +0 -1
- package/dist/logitron-logger.module-B8NklSC4.d.mts.map +0 -1
- package/dist/logitron-logger.module-BLT1y5Iq.d.ts.map +0 -1
- package/dist/logitron-logger.module-Bt_Jei1V.mjs.map +0 -1
- package/dist/logitron-logger.module-bJ1hGhaL.js.map +0 -1
- package/dist/transport.manager-CaL4XuLD.mjs.map +0 -1
- package/dist/transport.manager-zgEZCJhR.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
const require_transport_manager = require('./transport.manager-
|
|
2
|
-
const require_logitron_logger_module = require('./logitron-logger.module-
|
|
1
|
+
const require_transport_manager = require('./transport.manager-B9LF9uDd.js');
|
|
2
|
+
const require_logitron_logger_module = require('./logitron-logger.module-DHFampon.js');
|
|
3
3
|
require('./search-DoZF3RZj.js');
|
|
4
|
+
let node_async_hooks = require("node:async_hooks");
|
|
5
|
+
node_async_hooks = require_transport_manager.__toESM(node_async_hooks);
|
|
4
6
|
let __nestjs_common = require("@nestjs/common");
|
|
5
7
|
__nestjs_common = require_transport_manager.__toESM(__nestjs_common);
|
|
6
8
|
|
|
@@ -228,8 +230,11 @@ const InjectLogger = () => (0, __nestjs_common.Inject)(`${require_logitron_logge
|
|
|
228
230
|
/**
|
|
229
231
|
* Method decorator that auto-logs entry, exit, duration, and errors.
|
|
230
232
|
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
+
* Preserves the original method's sync/async contract: a synchronous method
|
|
234
|
+
* stays synchronous (returns its value directly, with logs emitted
|
|
235
|
+
* fire-and-forget), and an async method is awaited so exit/error logs reflect
|
|
236
|
+
* the resolved result. Attaches to the logger found on the class instance via a
|
|
237
|
+
* `logger` property (the conventional NestJS name).
|
|
233
238
|
*
|
|
234
239
|
* @example
|
|
235
240
|
* ```ts
|
|
@@ -247,7 +252,23 @@ function LogMethod(options = {}) {
|
|
|
247
252
|
const className = ((_constructor = target.constructor) === null || _constructor === void 0 ? void 0 : _constructor.name) ?? "Unknown";
|
|
248
253
|
const label = options.label ?? `${className}.${methodName}`;
|
|
249
254
|
let _warnedNoLogger = false;
|
|
250
|
-
|
|
255
|
+
const reportLogFailure = (phase, err) => {
|
|
256
|
+
process.stderr.write(`[logixia] @LogMethod(${label}) ${phase} log failed: ${String(err)}\n`);
|
|
257
|
+
};
|
|
258
|
+
const emit$1 = (logger$1, phase, message, data) => {
|
|
259
|
+
const logFnRaw = logger$1[level];
|
|
260
|
+
const p = (typeof logFnRaw === "function" ? logFnRaw : logger$1.debug).bind(logger$1)(message, data);
|
|
261
|
+
if (p && typeof p.catch === "function") p.catch((e) => reportLogFailure(phase, e));
|
|
262
|
+
};
|
|
263
|
+
const emitError = (logger$1, error, start) => {
|
|
264
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
265
|
+
const errLog = logger$1.error(err, {
|
|
266
|
+
method: label,
|
|
267
|
+
durationMs: Date.now() - start
|
|
268
|
+
});
|
|
269
|
+
if (errLog && typeof errLog.catch === "function") errLog.catch((e) => reportLogFailure("error", e));
|
|
270
|
+
};
|
|
271
|
+
descriptor.value = function(...args) {
|
|
251
272
|
const logger$1 = this.logger ?? require_logitron_logger_module.LogixiaLoggerModule._globalLogger ?? void 0;
|
|
252
273
|
if (!logger$1 && !_warnedNoLogger) {
|
|
253
274
|
_warnedNoLogger = true;
|
|
@@ -256,36 +277,31 @@ function LogMethod(options = {}) {
|
|
|
256
277
|
const start = Date.now();
|
|
257
278
|
const entry = { method: label };
|
|
258
279
|
if (logArgs && args.length > 0) entry["args"] = args;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
};
|
|
262
|
-
if (logger$1) {
|
|
263
|
-
const logFnRaw = logger$1[level];
|
|
264
|
-
await (typeof logFnRaw === "function" ? logFnRaw : logger$1.debug).bind(logger$1)(`→ ${label}`, entry).catch((e) => reportLogFailure("entry", e));
|
|
265
|
-
}
|
|
266
|
-
try {
|
|
267
|
-
const result = await originalMethod.apply(this, args);
|
|
280
|
+
if (logger$1) emit$1(logger$1, "entry", `→ ${label}`, entry);
|
|
281
|
+
const buildExit = (result$1) => {
|
|
268
282
|
const exit = {
|
|
269
283
|
method: label,
|
|
270
284
|
durationMs: Date.now() - start
|
|
271
285
|
};
|
|
272
|
-
if (logResult) exit["result"] = result;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
286
|
+
if (logResult) exit["result"] = result$1;
|
|
287
|
+
return exit;
|
|
288
|
+
};
|
|
289
|
+
let result;
|
|
290
|
+
try {
|
|
291
|
+
result = originalMethod.apply(this, args);
|
|
278
292
|
} catch (error) {
|
|
279
|
-
if (logger$1 && logErrors)
|
|
280
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
281
|
-
const errLog = logger$1.error(err, {
|
|
282
|
-
method: label,
|
|
283
|
-
durationMs: Date.now() - start
|
|
284
|
-
});
|
|
285
|
-
if (errLog !== void 0 && errLog !== null && typeof errLog.catch === "function") errLog.catch((e) => reportLogFailure("error", e));
|
|
286
|
-
}
|
|
293
|
+
if (logger$1 && logErrors) emitError(logger$1, error, start);
|
|
287
294
|
throw error;
|
|
288
295
|
}
|
|
296
|
+
if (result && typeof result.then === "function") return result.then((resolved) => {
|
|
297
|
+
if (logger$1) emit$1(logger$1, "exit", `← ${label}`, buildExit(resolved));
|
|
298
|
+
return resolved;
|
|
299
|
+
}, (error) => {
|
|
300
|
+
if (logger$1 && logErrors) emitError(logger$1, error, start);
|
|
301
|
+
throw error;
|
|
302
|
+
});
|
|
303
|
+
if (logger$1) emit$1(logger$1, "exit", `← ${label}`, buildExit(result));
|
|
304
|
+
return result;
|
|
289
305
|
};
|
|
290
306
|
return descriptor;
|
|
291
307
|
};
|
|
@@ -345,6 +361,21 @@ LogixiaExceptionFilter = require_logitron_logger_module.__decorate([
|
|
|
345
361
|
|
|
346
362
|
//#endregion
|
|
347
363
|
//#region src/formatters/json.formatter.ts
|
|
364
|
+
/**
|
|
365
|
+
* Build a JSON.stringify replacer that replaces circular references with the
|
|
366
|
+
* string '[Circular]' instead of throwing. A fresh replacer must be created per
|
|
367
|
+
* stringify call because it holds per-serialization state (the seen set).
|
|
368
|
+
*/
|
|
369
|
+
function createCircularReplacer() {
|
|
370
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
371
|
+
return function(_key, value) {
|
|
372
|
+
if (typeof value === "object" && value !== null) {
|
|
373
|
+
if (seen.has(value)) return "[Circular]";
|
|
374
|
+
seen.add(value);
|
|
375
|
+
}
|
|
376
|
+
return value;
|
|
377
|
+
};
|
|
378
|
+
}
|
|
348
379
|
var JsonFormatter = class {
|
|
349
380
|
constructor(options = {}) {
|
|
350
381
|
this.includeTimestamp = options.includeTimestamp ?? true;
|
|
@@ -372,7 +403,8 @@ var JsonFormatter = class {
|
|
|
372
403
|
hostname: process.env.HOSTNAME || "unknown",
|
|
373
404
|
version: process.version
|
|
374
405
|
};
|
|
375
|
-
|
|
406
|
+
const replacer = createCircularReplacer();
|
|
407
|
+
return this.prettyPrint ? JSON.stringify(formatted, replacer, 2) : JSON.stringify(formatted, replacer);
|
|
376
408
|
}
|
|
377
409
|
serializePayload(payload) {
|
|
378
410
|
const serialized = {};
|
|
@@ -475,11 +507,11 @@ var TextFormatter = class TextFormatter {
|
|
|
475
507
|
if (typeof value === "string") return `${key}="${value}"`;
|
|
476
508
|
if (typeof value === "number" || typeof value === "boolean") return `${key}=${value}`;
|
|
477
509
|
if (value instanceof Date) return `${key}=${value.toISOString()}`;
|
|
478
|
-
if (typeof value === "object") return `${key}=${
|
|
510
|
+
if (typeof value === "object") return `${key}=${stripControls(require_transport_manager.safeToString(value))}`;
|
|
479
511
|
return `${key}=${String(value)}`;
|
|
480
512
|
}).join(" ");
|
|
481
513
|
} catch {
|
|
482
|
-
return
|
|
514
|
+
return stripControls(require_transport_manager.safeToString(payload));
|
|
483
515
|
}
|
|
484
516
|
}
|
|
485
517
|
/**
|
|
@@ -516,6 +548,234 @@ var TextFormatter = class TextFormatter {
|
|
|
516
548
|
}
|
|
517
549
|
};
|
|
518
550
|
|
|
551
|
+
//#endregion
|
|
552
|
+
//#region src/utils/runtime-control.ts
|
|
553
|
+
const DEFAULT_CYCLE = [
|
|
554
|
+
"error",
|
|
555
|
+
"warn",
|
|
556
|
+
"info",
|
|
557
|
+
"debug",
|
|
558
|
+
"trace",
|
|
559
|
+
"verbose"
|
|
560
|
+
];
|
|
561
|
+
/**
|
|
562
|
+
* Register an OS-signal handler that cycles the logger's global level on each
|
|
563
|
+
* signal. Returns a dispose function that removes the listener.
|
|
564
|
+
*
|
|
565
|
+
* Cycling (rather than jumping straight to a fixed level) means a single,
|
|
566
|
+
* memorizable command (`kill -USR2 <pid>`) is enough to ratchet verbosity up
|
|
567
|
+
* while chasing a bug and back down again — no value to remember.
|
|
568
|
+
*/
|
|
569
|
+
function registerLevelSignal(logger$1, options = {}) {
|
|
570
|
+
const signal = options.signal ?? "SIGUSR2";
|
|
571
|
+
const cycle = options.cycle && options.cycle.length > 0 ? options.cycle : [...DEFAULT_CYCLE];
|
|
572
|
+
const handler = () => {
|
|
573
|
+
const current = logger$1.getLevel();
|
|
574
|
+
const next = cycle[(cycle.indexOf(current) + 1) % cycle.length];
|
|
575
|
+
logger$1.setLevel(next);
|
|
576
|
+
require_transport_manager.internalLog(`runtime level changed via ${signal}: ${current} → ${next}`);
|
|
577
|
+
};
|
|
578
|
+
process.on(signal, handler);
|
|
579
|
+
return () => {
|
|
580
|
+
process.removeListener(signal, handler);
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
const VALID_LEVELS = new Set([
|
|
584
|
+
"error",
|
|
585
|
+
"warn",
|
|
586
|
+
"info",
|
|
587
|
+
"debug",
|
|
588
|
+
"trace",
|
|
589
|
+
"verbose",
|
|
590
|
+
"fatal"
|
|
591
|
+
]);
|
|
592
|
+
/**
|
|
593
|
+
* Create an HTTP handler (Node `http`/Express-compatible) that reads and sets
|
|
594
|
+
* the logger's level at runtime.
|
|
595
|
+
*
|
|
596
|
+
* - `GET` → `{ level, namespaceLevels }`
|
|
597
|
+
* - `POST` → body `{ level?, namespaceLevels? }` applies them, returns the new state
|
|
598
|
+
*
|
|
599
|
+
* Custom levels are accepted too: any level the logger already knows passes
|
|
600
|
+
* through. Unknown levels are rejected with 400 so a typo can't silently mute
|
|
601
|
+
* logging. Mount behind your own auth — this intentionally has none.
|
|
602
|
+
*/
|
|
603
|
+
function createLevelControlHandler(logger$1, options = {}) {
|
|
604
|
+
const allowed = options.allowedLevels && options.allowedLevels.length > 0 ? new Set(options.allowedLevels.map((l) => l.toLowerCase())) : VALID_LEVELS;
|
|
605
|
+
const snapshot = () => {
|
|
606
|
+
var _logger$getNamespaceL;
|
|
607
|
+
return {
|
|
608
|
+
level: logger$1.getLevel(),
|
|
609
|
+
namespaceLevels: ((_logger$getNamespaceL = logger$1.getNamespaceLevels) === null || _logger$getNamespaceL === void 0 ? void 0 : _logger$getNamespaceL.call(logger$1)) ?? {}
|
|
610
|
+
};
|
|
611
|
+
};
|
|
612
|
+
const send = (res, status, body) => {
|
|
613
|
+
var _res$setHeader;
|
|
614
|
+
res.statusCode = status;
|
|
615
|
+
(_res$setHeader = res.setHeader) === null || _res$setHeader === void 0 || _res$setHeader.call(res, "Content-Type", "application/json");
|
|
616
|
+
res.end(JSON.stringify(body));
|
|
617
|
+
};
|
|
618
|
+
const applyBody = (res, raw) => {
|
|
619
|
+
let parsed;
|
|
620
|
+
try {
|
|
621
|
+
parsed = raw ? JSON.parse(raw) : {};
|
|
622
|
+
} catch {
|
|
623
|
+
send(res, 400, { error: "invalid JSON body" });
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (parsed.level !== void 0) {
|
|
627
|
+
const lvl = String(parsed.level).toLowerCase();
|
|
628
|
+
if (!allowed.has(lvl)) {
|
|
629
|
+
send(res, 400, {
|
|
630
|
+
error: `unknown level "${parsed.level}"`,
|
|
631
|
+
allowed: [...allowed]
|
|
632
|
+
});
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
logger$1.setLevel(lvl);
|
|
636
|
+
}
|
|
637
|
+
if (parsed.namespaceLevels !== void 0) {
|
|
638
|
+
if (typeof parsed.namespaceLevels !== "object" || parsed.namespaceLevels === null || Array.isArray(parsed.namespaceLevels)) {
|
|
639
|
+
send(res, 400, { error: "namespaceLevels must be an object" });
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
const nl = parsed.namespaceLevels;
|
|
643
|
+
for (const [pat, lvl] of Object.entries(nl)) if (!allowed.has(String(lvl).toLowerCase())) {
|
|
644
|
+
send(res, 400, { error: `unknown level "${String(lvl)}" for namespace "${pat}"` });
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (logger$1.setNamespaceLevels) {
|
|
648
|
+
const coerced = {};
|
|
649
|
+
for (const [pat, lvl] of Object.entries(nl)) coerced[pat] = String(lvl).toLowerCase();
|
|
650
|
+
logger$1.setNamespaceLevels(coerced);
|
|
651
|
+
} else require_transport_manager.internalWarn("level control: logger does not support setNamespaceLevels — ignored");
|
|
652
|
+
}
|
|
653
|
+
send(res, 200, snapshot());
|
|
654
|
+
};
|
|
655
|
+
return function levelControlHandler(req, res) {
|
|
656
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
657
|
+
if (method === "GET") {
|
|
658
|
+
send(res, 200, snapshot());
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
if (method === "POST" || method === "PUT" || method === "PATCH") {
|
|
662
|
+
const maybeBody = req.body;
|
|
663
|
+
if (maybeBody !== void 0 && typeof req.on !== "function") {
|
|
664
|
+
applyBody(res, typeof maybeBody === "string" ? maybeBody : JSON.stringify(maybeBody));
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
if (typeof req.on === "function") {
|
|
668
|
+
let raw = "";
|
|
669
|
+
req.on("data", (chunk) => {
|
|
670
|
+
raw += String(chunk ?? "");
|
|
671
|
+
});
|
|
672
|
+
req.on("end", () => applyBody(res, raw));
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
applyBody(res, "");
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
send(res, 405, { error: `method ${method} not allowed` });
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
//#endregion
|
|
683
|
+
//#region src/utils/safe-stringify.ts
|
|
684
|
+
/** Build a JSONPath like `$["a"][0]["b"]` for a decycle pointer. */
|
|
685
|
+
function jsonPath(parts) {
|
|
686
|
+
let path = "$";
|
|
687
|
+
for (const p of parts) path += typeof p === "number" ? `[${p}]` : `[${JSON.stringify(p)}]`;
|
|
688
|
+
return path;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Serialize any value to JSON without throwing on circular references or BigInt.
|
|
692
|
+
* Circular refs become `"[Circular]"` (or `{ $ref }` pointers when `decycle`).
|
|
693
|
+
*/
|
|
694
|
+
function safeStringify(value, options = {}) {
|
|
695
|
+
const { indent, deterministic = false, decycle = false, bigint = "string" } = options;
|
|
696
|
+
if (decycle) return JSON.stringify(decycleValue(value, bigint), void 0, indent);
|
|
697
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
698
|
+
const transform = (val) => {
|
|
699
|
+
if (typeof val === "bigint") return bigint === "number" ? Number(val) : val.toString();
|
|
700
|
+
if (typeof val === "function") return `[Function: ${val.name || "anonymous"}]`;
|
|
701
|
+
if (typeof val === "symbol") return val.toString();
|
|
702
|
+
if (val === null || typeof val !== "object") return val;
|
|
703
|
+
if (seen.has(val)) return "[Circular]";
|
|
704
|
+
seen.add(val);
|
|
705
|
+
let out;
|
|
706
|
+
if (Array.isArray(val)) out = val.map((item) => transform(item));
|
|
707
|
+
else if (val instanceof Date) out = val.toISOString();
|
|
708
|
+
else {
|
|
709
|
+
const rec = val;
|
|
710
|
+
const keys = deterministic ? Object.keys(rec).sort() : Object.keys(rec);
|
|
711
|
+
const obj = {};
|
|
712
|
+
for (const k of keys) {
|
|
713
|
+
if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
|
|
714
|
+
obj[k] = transform(rec[k]);
|
|
715
|
+
}
|
|
716
|
+
out = obj;
|
|
717
|
+
}
|
|
718
|
+
seen.delete(val);
|
|
719
|
+
return out;
|
|
720
|
+
};
|
|
721
|
+
return JSON.stringify(transform(value), void 0, indent);
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Replace repeated object references with round-trippable `{ "$ref": "$..." }`
|
|
725
|
+
* JSONPath pointers (the classic Crockford decycle). Pair with {@link retrocycle}
|
|
726
|
+
* to reconstruct the original shared/circular graph.
|
|
727
|
+
*/
|
|
728
|
+
function decycleValue(value, bigint = "string") {
|
|
729
|
+
const paths = /* @__PURE__ */ new WeakMap();
|
|
730
|
+
const walk = (val, path) => {
|
|
731
|
+
if (typeof val === "bigint") return bigint === "number" ? Number(val) : val.toString();
|
|
732
|
+
if (val === null || typeof val !== "object") return val;
|
|
733
|
+
if (val instanceof Date) return val.toISOString();
|
|
734
|
+
const existing = paths.get(val);
|
|
735
|
+
if (existing !== void 0) return { $ref: existing };
|
|
736
|
+
paths.set(val, jsonPath(path));
|
|
737
|
+
if (Array.isArray(val)) return val.map((item, i) => walk(item, [...path, i]));
|
|
738
|
+
const rec = val;
|
|
739
|
+
const obj = {};
|
|
740
|
+
for (const k of Object.keys(rec)) {
|
|
741
|
+
if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
|
|
742
|
+
obj[k] = walk(rec[k], [...path, k]);
|
|
743
|
+
}
|
|
744
|
+
return obj;
|
|
745
|
+
};
|
|
746
|
+
return walk(value, []);
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Inverse of {@link decycleValue}: resolve `{ "$ref": "$..." }` pointers back
|
|
750
|
+
* into the live object graph (mutates and returns the parsed input).
|
|
751
|
+
*/
|
|
752
|
+
function retrocycle(root) {
|
|
753
|
+
const refRe = /^\$(?:\[(?:\d+|"(?:[^"\\]|\\.)*")\])*$/;
|
|
754
|
+
const resolve = (path) => {
|
|
755
|
+
const segs = [];
|
|
756
|
+
const partRe = /\[(\d+|"(?:[^"\\]|\\.)*")\]/g;
|
|
757
|
+
let m;
|
|
758
|
+
while ((m = partRe.exec(path)) !== null) {
|
|
759
|
+
const raw = m[1];
|
|
760
|
+
segs.push(raw.startsWith("\"") ? JSON.parse(raw) : Number(raw));
|
|
761
|
+
}
|
|
762
|
+
let node = root;
|
|
763
|
+
for (const s of segs) node = node[s];
|
|
764
|
+
return node;
|
|
765
|
+
};
|
|
766
|
+
const walk = (val) => {
|
|
767
|
+
if (val === null || typeof val !== "object") return;
|
|
768
|
+
const rec = val;
|
|
769
|
+
for (const k of Object.keys(rec)) {
|
|
770
|
+
const child = rec[k];
|
|
771
|
+
if (child !== null && typeof child === "object" && typeof child.$ref === "string" && refRe.test(child.$ref)) rec[k] = resolve(child.$ref);
|
|
772
|
+
else walk(child);
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
walk(root);
|
|
776
|
+
return root;
|
|
777
|
+
}
|
|
778
|
+
|
|
519
779
|
//#endregion
|
|
520
780
|
//#region src/utils/typed-logger.ts
|
|
521
781
|
/**
|
|
@@ -576,6 +836,258 @@ function createTypedLogger(logger$1, schema) {
|
|
|
576
836
|
};
|
|
577
837
|
}
|
|
578
838
|
|
|
839
|
+
//#endregion
|
|
840
|
+
//#region src/wide-events.ts
|
|
841
|
+
const _storage = new node_async_hooks.AsyncLocalStorage();
|
|
842
|
+
/**
|
|
843
|
+
* Merge fields into the wide event for the current async scope. No-op (with no
|
|
844
|
+
* throw) when called outside a `withWideEvent` / middleware scope, so business
|
|
845
|
+
* code can call it unconditionally.
|
|
846
|
+
*/
|
|
847
|
+
function addEventFields(fields) {
|
|
848
|
+
const state = _storage.getStore();
|
|
849
|
+
if (!state || state.emitted) return;
|
|
850
|
+
Object.assign(state.fields, fields);
|
|
851
|
+
}
|
|
852
|
+
/** Set a single field on the current wide event. */
|
|
853
|
+
function setEventField(key, value) {
|
|
854
|
+
addEventFields({ [key]: value });
|
|
855
|
+
}
|
|
856
|
+
/** Read a shallow copy of the wide event accumulated so far, or undefined. */
|
|
857
|
+
function getEventFields() {
|
|
858
|
+
const state = _storage.getStore();
|
|
859
|
+
return state ? { ...state.fields } : void 0;
|
|
860
|
+
}
|
|
861
|
+
function emit(logger$1, state, options, extra) {
|
|
862
|
+
if (state.emitted) return;
|
|
863
|
+
state.emitted = true;
|
|
864
|
+
const level = options.level ?? "info";
|
|
865
|
+
const message = options.message ?? "request";
|
|
866
|
+
const durationField = options.durationField ?? "durationMs";
|
|
867
|
+
const includeTrace = options.includeTrace ?? true;
|
|
868
|
+
const payload = { ...state.fields };
|
|
869
|
+
if (extra) Object.assign(payload, extra);
|
|
870
|
+
payload[durationField] = Date.now() - state.startMs;
|
|
871
|
+
if (includeTrace) {
|
|
872
|
+
const traceId = require_logitron_logger_module.getCurrentTraceId();
|
|
873
|
+
if (traceId !== void 0 && payload["traceId"] === void 0) payload["traceId"] = traceId;
|
|
874
|
+
}
|
|
875
|
+
const p = logger$1.logLevel(level, message, payload);
|
|
876
|
+
if (p && typeof p.catch === "function") p.catch(() => {});
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Run `callback` inside a wide-event scope. `addEventFields` calls anywhere in
|
|
880
|
+
* the (async) call tree accumulate onto one event, which is emitted exactly once
|
|
881
|
+
* when the callback settles — on success OR error (the canonical "emit in
|
|
882
|
+
* finally" guarantee). On error, `error` + `errorMessage` fields are added.
|
|
883
|
+
*/
|
|
884
|
+
async function withWideEvent(logger$1, initialFields, callback, options = {}) {
|
|
885
|
+
const state = {
|
|
886
|
+
fields: { ...initialFields },
|
|
887
|
+
startMs: Date.now(),
|
|
888
|
+
emitted: false
|
|
889
|
+
};
|
|
890
|
+
return _storage.run(state, async () => {
|
|
891
|
+
try {
|
|
892
|
+
const result = await callback();
|
|
893
|
+
emit(logger$1, state, options);
|
|
894
|
+
return result;
|
|
895
|
+
} catch (error) {
|
|
896
|
+
emit(logger$1, state, options, {
|
|
897
|
+
error: true,
|
|
898
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
899
|
+
});
|
|
900
|
+
throw error;
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Express/Connect middleware that opens a wide-event scope per request and
|
|
906
|
+
* emits ONE canonical event on response `finish`/`close` — even if the handler
|
|
907
|
+
* throws or the client disconnects. Pre-populates method/url/ip; handlers add
|
|
908
|
+
* more via `addEventFields`. The completion event includes `statusCode` and the
|
|
909
|
+
* request duration.
|
|
910
|
+
*/
|
|
911
|
+
function wideEventMiddleware(logger$1, options = {}) {
|
|
912
|
+
return function logixiaWideEventMiddleware(req, res, next) {
|
|
913
|
+
var _options$skip, _req$socket, _res$once, _res$once2;
|
|
914
|
+
if ((_options$skip = options.skip) === null || _options$skip === void 0 ? void 0 : _options$skip.call(options, req)) {
|
|
915
|
+
next();
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
const state = {
|
|
919
|
+
fields: {
|
|
920
|
+
method: req.method,
|
|
921
|
+
url: req.originalUrl ?? req.url,
|
|
922
|
+
ip: req.ip ?? ((_req$socket = req.socket) === null || _req$socket === void 0 ? void 0 : _req$socket.remoteAddress),
|
|
923
|
+
...options.enrich ? options.enrich(req) : {}
|
|
924
|
+
},
|
|
925
|
+
startMs: Date.now(),
|
|
926
|
+
emitted: false
|
|
927
|
+
};
|
|
928
|
+
const finalize = () => {
|
|
929
|
+
emit(logger$1, state, options, { statusCode: res.statusCode ?? 0 });
|
|
930
|
+
};
|
|
931
|
+
(_res$once = res.once) === null || _res$once === void 0 || _res$once.call(res, "finish", finalize);
|
|
932
|
+
(_res$once2 = res.once) === null || _res$once2 === void 0 || _res$once2.call(res, "close", finalize);
|
|
933
|
+
_storage.run(state, () => next());
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
//#endregion
|
|
938
|
+
//#region src/transports/otlp.transport.ts
|
|
939
|
+
/**
|
|
940
|
+
* Map a logixia level name to an OTel SeverityNumber (1–24) and text.
|
|
941
|
+
* Per the OTel logs SDK spec: TRACE=1, DEBUG=5, INFO=9, WARN=13, ERROR=17,
|
|
942
|
+
* FATAL=21. Custom/unknown levels fall back to INFO (9).
|
|
943
|
+
*/
|
|
944
|
+
function toOtelSeverity(level) {
|
|
945
|
+
switch (level.toLowerCase()) {
|
|
946
|
+
case "trace": return {
|
|
947
|
+
number: 1,
|
|
948
|
+
text: "TRACE"
|
|
949
|
+
};
|
|
950
|
+
case "verbose": return {
|
|
951
|
+
number: 5,
|
|
952
|
+
text: "DEBUG"
|
|
953
|
+
};
|
|
954
|
+
case "debug": return {
|
|
955
|
+
number: 5,
|
|
956
|
+
text: "DEBUG"
|
|
957
|
+
};
|
|
958
|
+
case "info": return {
|
|
959
|
+
number: 9,
|
|
960
|
+
text: "INFO"
|
|
961
|
+
};
|
|
962
|
+
case "warn":
|
|
963
|
+
case "warning": return {
|
|
964
|
+
number: 13,
|
|
965
|
+
text: "WARN"
|
|
966
|
+
};
|
|
967
|
+
case "error": return {
|
|
968
|
+
number: 17,
|
|
969
|
+
text: "ERROR"
|
|
970
|
+
};
|
|
971
|
+
case "fatal": return {
|
|
972
|
+
number: 21,
|
|
973
|
+
text: "FATAL"
|
|
974
|
+
};
|
|
975
|
+
default: return {
|
|
976
|
+
number: 9,
|
|
977
|
+
text: "INFO"
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
/** Coerce a JS value into an OTLP AnyValue. */
|
|
982
|
+
function toAnyValue(value) {
|
|
983
|
+
if (typeof value === "string") return { stringValue: value };
|
|
984
|
+
if (typeof value === "boolean") return { boolValue: value };
|
|
985
|
+
if (typeof value === "number") return Number.isInteger(value) ? { intValue: value } : { doubleValue: value };
|
|
986
|
+
if (typeof value === "bigint") return { stringValue: value.toString() };
|
|
987
|
+
if (value === null || value === void 0) return { stringValue: "" };
|
|
988
|
+
try {
|
|
989
|
+
return { stringValue: JSON.stringify(value) };
|
|
990
|
+
} catch {
|
|
991
|
+
return { stringValue: String(value) };
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
/** Build the OTLP KeyValue attribute list from a flat record. */
|
|
995
|
+
function toAttributes(rec) {
|
|
996
|
+
return Object.entries(rec).map(([key, value]) => ({
|
|
997
|
+
key,
|
|
998
|
+
value: toAnyValue(value)
|
|
999
|
+
}));
|
|
1000
|
+
}
|
|
1001
|
+
var OtlpLogTransport = class {
|
|
1002
|
+
constructor(config) {
|
|
1003
|
+
this.name = "otlp";
|
|
1004
|
+
this.batch = [];
|
|
1005
|
+
this.flushTimer = null;
|
|
1006
|
+
this.url = config.url;
|
|
1007
|
+
this.headers = config.headers ?? {};
|
|
1008
|
+
this.batchSize = config.batchSize ?? 100;
|
|
1009
|
+
this.flushIntervalMs = config.flushIntervalMs ?? 5e3;
|
|
1010
|
+
this.level = config.level;
|
|
1011
|
+
this.resourceAttrs = toAttributes({
|
|
1012
|
+
"service.name": config.serviceName ?? "logixia",
|
|
1013
|
+
...config.serviceVersion ? { "service.version": config.serviceVersion } : {},
|
|
1014
|
+
...config.environment ? { "deployment.environment": config.environment } : {},
|
|
1015
|
+
...config.resourceAttributes ?? {}
|
|
1016
|
+
});
|
|
1017
|
+
this.flushTimer = setInterval(() => {
|
|
1018
|
+
this.flush().catch(() => {});
|
|
1019
|
+
}, this.flushIntervalMs);
|
|
1020
|
+
if (this.flushTimer.unref) this.flushTimer.unref();
|
|
1021
|
+
}
|
|
1022
|
+
write(entry) {
|
|
1023
|
+
this.batch.push(entry);
|
|
1024
|
+
if (this.batch.length >= this.batchSize) this.flush().catch(() => {});
|
|
1025
|
+
}
|
|
1026
|
+
/** Convert one entry into an OTLP LogRecord. */
|
|
1027
|
+
toLogRecord(entry) {
|
|
1028
|
+
const sev = toOtelSeverity(entry.level);
|
|
1029
|
+
const tsNanos = String(entry.timestamp.getTime() * 1e6);
|
|
1030
|
+
const attrs = { ...entry.data ?? {} };
|
|
1031
|
+
if (entry.context !== void 0) attrs["context"] = entry.context;
|
|
1032
|
+
if (entry.appName !== void 0) attrs["app.name"] = entry.appName;
|
|
1033
|
+
if (entry.environment !== void 0) attrs["deployment.environment"] = entry.environment;
|
|
1034
|
+
const record = {
|
|
1035
|
+
timeUnixNano: tsNanos,
|
|
1036
|
+
observedTimeUnixNano: tsNanos,
|
|
1037
|
+
severityNumber: sev.number,
|
|
1038
|
+
severityText: sev.text,
|
|
1039
|
+
body: { stringValue: entry.message },
|
|
1040
|
+
attributes: toAttributes(attrs)
|
|
1041
|
+
};
|
|
1042
|
+
if (entry.traceId) record["traceId"] = entry.traceId;
|
|
1043
|
+
return record;
|
|
1044
|
+
}
|
|
1045
|
+
buildPayload(entries) {
|
|
1046
|
+
return JSON.stringify({ resourceLogs: [{
|
|
1047
|
+
resource: { attributes: this.resourceAttrs },
|
|
1048
|
+
scopeLogs: [{
|
|
1049
|
+
scope: { name: "logixia" },
|
|
1050
|
+
logRecords: entries.map((e) => this.toLogRecord(e))
|
|
1051
|
+
}]
|
|
1052
|
+
}] });
|
|
1053
|
+
}
|
|
1054
|
+
async flush() {
|
|
1055
|
+
while (this.batch.length > 0) {
|
|
1056
|
+
const entries = this.batch.splice(0, this.batchSize);
|
|
1057
|
+
try {
|
|
1058
|
+
await this.send(entries);
|
|
1059
|
+
} catch (err) {
|
|
1060
|
+
require_transport_manager.internalError("OtlpLogTransport flush error", err);
|
|
1061
|
+
this.batch.unshift(...entries);
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
async send(entries) {
|
|
1067
|
+
if (typeof fetch !== "function") {
|
|
1068
|
+
require_transport_manager.internalWarn("OtlpLogTransport: global fetch unavailable — cannot export logs");
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
const res = await fetch(this.url, {
|
|
1072
|
+
method: "POST",
|
|
1073
|
+
headers: {
|
|
1074
|
+
"Content-Type": "application/json",
|
|
1075
|
+
...this.headers
|
|
1076
|
+
},
|
|
1077
|
+
body: this.buildPayload(entries)
|
|
1078
|
+
});
|
|
1079
|
+
if (!res.ok) throw new Error(`OTLP export failed: HTTP ${res.status}`);
|
|
1080
|
+
}
|
|
1081
|
+
async close() {
|
|
1082
|
+
if (this.flushTimer) {
|
|
1083
|
+
clearInterval(this.flushTimer);
|
|
1084
|
+
this.flushTimer = null;
|
|
1085
|
+
}
|
|
1086
|
+
for (let attempt = 0; attempt < 3 && this.batch.length > 0; attempt += 1) await this.flush();
|
|
1087
|
+
if (this.batch.length > 0) require_transport_manager.internalError(`OtlpLogTransport closing with ${this.batch.length} undelivered record(s)`);
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
|
|
579
1091
|
//#endregion
|
|
580
1092
|
//#region src/metrics.ts
|
|
581
1093
|
const DEFAULT_BUCKETS = [
|
|
@@ -599,7 +1111,7 @@ function buildLabelKey(config, entry) {
|
|
|
599
1111
|
const payload = entry.payload ?? {};
|
|
600
1112
|
for (const name of labelNames) {
|
|
601
1113
|
const raw = name === "level" ? entry.level : payload[name];
|
|
602
|
-
pairs[name] = raw !== void 0 && raw !== null ? String(raw) : "";
|
|
1114
|
+
pairs[sanitizePromName(name)] = raw !== void 0 && raw !== null ? String(raw) : "";
|
|
603
1115
|
}
|
|
604
1116
|
return JSON.stringify(pairs);
|
|
605
1117
|
}
|
|
@@ -612,6 +1124,18 @@ function escapeLabel(value) {
|
|
|
612
1124
|
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
613
1125
|
}
|
|
614
1126
|
/**
|
|
1127
|
+
* Coerce an arbitrary string into a valid Prometheus metric or label name
|
|
1128
|
+
* (`[a-zA-Z_][a-zA-Z0-9_]*`). Invalid characters become underscores and a
|
|
1129
|
+
* leading digit is prefixed with `_`. Without this, a user-supplied name like
|
|
1130
|
+
* `my-metric` or a label like `status code` would emit output Prometheus cannot
|
|
1131
|
+
* parse, breaking the ENTIRE scrape endpoint, not just that metric.
|
|
1132
|
+
*/
|
|
1133
|
+
function sanitizePromName(name) {
|
|
1134
|
+
let safe = name.replace(/\W/g, "_");
|
|
1135
|
+
if (safe.length > 0 && /\d/.test(safe[0])) safe = `_${safe}`;
|
|
1136
|
+
return safe.length > 0 ? safe : "_";
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
615
1139
|
* A logixia plugin that extracts Prometheus-compatible metrics from log entries.
|
|
616
1140
|
*
|
|
617
1141
|
* Implements `LogixiaPlugin` — pass directly to `logger.use()`:
|
|
@@ -685,7 +1209,7 @@ var MetricsPlugin = class {
|
|
|
685
1209
|
render() {
|
|
686
1210
|
const lines = [];
|
|
687
1211
|
for (const [rawName, config] of Object.entries(this.map)) {
|
|
688
|
-
const metricName = `logixia_${rawName}`;
|
|
1212
|
+
const metricName = `logixia_${sanitizePromName(rawName)}`;
|
|
689
1213
|
const state = this.metricState.get(rawName);
|
|
690
1214
|
if (!state) continue;
|
|
691
1215
|
const helpText = config.help ?? rawName.replace(/_/g, " ");
|
|
@@ -929,6 +1453,7 @@ Object.defineProperty(exports, 'LogixiaLoggerService', {
|
|
|
929
1453
|
}
|
|
930
1454
|
});
|
|
931
1455
|
exports.MetricsPlugin = MetricsPlugin;
|
|
1456
|
+
exports.OtlpLogTransport = OtlpLogTransport;
|
|
932
1457
|
exports.PluginRegistry = require_logitron_logger_module.PluginRegistry;
|
|
933
1458
|
exports.TRACE_CONTEXT_KEY = require_logitron_logger_module.TRACE_CONTEXT_KEY;
|
|
934
1459
|
exports.TextFormatter = TextFormatter;
|
|
@@ -940,14 +1465,17 @@ Object.defineProperty(exports, 'WebSocketTraceInterceptor', {
|
|
|
940
1465
|
}
|
|
941
1466
|
});
|
|
942
1467
|
exports._setActiveContextKey = require_logitron_logger_module._setActiveContextKey;
|
|
1468
|
+
exports.addEventFields = addEventFields;
|
|
943
1469
|
exports.applyRedaction = require_logitron_logger_module.applyRedaction;
|
|
944
1470
|
exports.createExpressContextMiddleware = require_logitron_logger_module.createExpressContextMiddleware;
|
|
945
1471
|
exports.createFastifyContextHook = require_logitron_logger_module.createFastifyContextHook;
|
|
1472
|
+
exports.createLevelControlHandler = createLevelControlHandler;
|
|
946
1473
|
exports.createLogger = createLogger;
|
|
947
1474
|
exports.createLoggerService = createLoggerService;
|
|
948
1475
|
exports.createMetricsPlugin = createMetricsPlugin;
|
|
949
1476
|
exports.createTraceMiddleware = require_logitron_logger_module.createTraceMiddleware;
|
|
950
1477
|
exports.createTypedLogger = createTypedLogger;
|
|
1478
|
+
exports.decycleValue = decycleValue;
|
|
951
1479
|
exports.defineLogSchema = defineLogSchema;
|
|
952
1480
|
exports.deregisterFromShutdown = require_logitron_logger_module.deregisterFromShutdown;
|
|
953
1481
|
exports.disableOtelBridge = require_logitron_logger_module.disableOtelBridge;
|
|
@@ -957,6 +1485,7 @@ exports.generateRequestId = generateRequestId;
|
|
|
957
1485
|
exports.generateTraceId = generateTraceId;
|
|
958
1486
|
exports.getActiveOtelContext = require_logitron_logger_module.getActiveOtelContext;
|
|
959
1487
|
exports.getCurrentTraceId = require_logitron_logger_module.getCurrentTraceId;
|
|
1488
|
+
exports.getEventFields = getEventFields;
|
|
960
1489
|
exports.getOtelMetaFields = require_logitron_logger_module.getOtelMetaFields;
|
|
961
1490
|
exports.getTraceContextKey = require_logitron_logger_module.getTraceContextKey;
|
|
962
1491
|
exports.globalPluginRegistry = require_logitron_logger_module.globalPluginRegistry;
|
|
@@ -967,10 +1496,17 @@ exports.logger = logger;
|
|
|
967
1496
|
exports.normalizeError = require_logitron_logger_module.normalizeError;
|
|
968
1497
|
exports.redactObject = require_logitron_logger_module.redactObject;
|
|
969
1498
|
exports.registerForShutdown = require_logitron_logger_module.registerForShutdown;
|
|
1499
|
+
exports.registerLevelSignal = registerLevelSignal;
|
|
970
1500
|
exports.resetShutdownHandlers = require_logitron_logger_module.resetShutdownHandlers;
|
|
1501
|
+
exports.retrocycle = retrocycle;
|
|
971
1502
|
exports.runWithTraceId = require_logitron_logger_module.runWithTraceId;
|
|
1503
|
+
exports.safeStringify = safeStringify;
|
|
972
1504
|
exports.serializeError = require_logitron_logger_module.serializeError;
|
|
1505
|
+
exports.setEventField = setEventField;
|
|
973
1506
|
exports.setTraceId = require_logitron_logger_module.setTraceId;
|
|
1507
|
+
exports.toOtelSeverity = toOtelSeverity;
|
|
974
1508
|
exports.traceStorage = require_logitron_logger_module.traceStorage;
|
|
975
1509
|
exports.usePlugin = require_logitron_logger_module.usePlugin;
|
|
1510
|
+
exports.wideEventMiddleware = wideEventMiddleware;
|
|
1511
|
+
exports.withWideEvent = withWideEvent;
|
|
976
1512
|
//# sourceMappingURL=index.js.map
|