logixia 1.10.2 → 1.10.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.
Files changed (40) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/index.d.mts +5 -2
  3. package/dist/index.d.mts.map +1 -1
  4. package/dist/index.d.ts +5 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +75 -33
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +75 -33
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/logitron-logger.module-B8NklSC4.d.mts.map +1 -1
  11. package/dist/{logitron-logger.module-Bt_Jei1V.mjs → logitron-logger.module-BBC9nO5q.mjs} +118 -37
  12. package/dist/logitron-logger.module-BBC9nO5q.mjs.map +1 -0
  13. package/dist/logitron-logger.module-BLT1y5Iq.d.ts.map +1 -1
  14. package/dist/{logitron-logger.module-bJ1hGhaL.js → logitron-logger.module-Dlf5GwJ9.js} +118 -37
  15. package/dist/logitron-logger.module-Dlf5GwJ9.js.map +1 -0
  16. package/dist/middleware.d.mts.map +1 -1
  17. package/dist/middleware.d.ts.map +1 -1
  18. package/dist/middleware.js +4 -3
  19. package/dist/middleware.js.map +1 -1
  20. package/dist/middleware.mjs +4 -3
  21. package/dist/middleware.mjs.map +1 -1
  22. package/dist/nest.d.mts.map +1 -1
  23. package/dist/nest.d.ts.map +1 -1
  24. package/dist/nest.js +2 -2
  25. package/dist/nest.mjs +2 -2
  26. package/dist/{transport.manager-zgEZCJhR.js → transport.manager-B9LF9uDd.js} +130 -56
  27. package/dist/transport.manager-B9LF9uDd.js.map +1 -0
  28. package/dist/{transport.manager-CaL4XuLD.mjs → transport.manager-Cij_sA-b.mjs} +128 -56
  29. package/dist/transport.manager-Cij_sA-b.mjs.map +1 -0
  30. package/dist/transports.d.mts +41 -2
  31. package/dist/transports.d.mts.map +1 -1
  32. package/dist/transports.d.ts +41 -2
  33. package/dist/transports.d.ts.map +1 -1
  34. package/dist/transports.js +1 -1
  35. package/dist/transports.mjs +1 -1
  36. package/package.json +1 -1
  37. package/dist/logitron-logger.module-Bt_Jei1V.mjs.map +0 -1
  38. package/dist/logitron-logger.module-bJ1hGhaL.js.map +0 -1
  39. package/dist/transport.manager-CaL4XuLD.mjs.map +0 -1
  40. package/dist/transport.manager-zgEZCJhR.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { d as internalError, f as internalLog, m as __require, p as internalWarn, t as TransportManager, u as safeToString } from "./transport.manager-CaL4XuLD.mjs";
1
+ import { d as internalError, f as internalLog, m as __require, p as internalWarn, t as TransportManager, u as safeToString } from "./transport.manager-Cij_sA-b.mjs";
2
2
  import buildFastStringify from "fast-json-stringify";
3
3
  import { AsyncLocalStorage } from "node:async_hooks";
4
4
  import { randomUUID } from "node:crypto";
@@ -86,10 +86,10 @@ var PluginRegistry = class {
86
86
  register(plugin) {
87
87
  if (this._plugins.some((p) => p.name === plugin.name)) return;
88
88
  this._plugins.push(plugin);
89
- if (plugin.onInit) {
89
+ if (plugin.onInit) try {
90
90
  const result = plugin.onInit();
91
91
  if (result instanceof Promise) result.catch(() => {});
92
- }
92
+ } catch {}
93
93
  }
94
94
  /** Remove a previously registered plugin by name. No-op if not found. */
95
95
  unregister(name) {
@@ -129,16 +129,20 @@ var PluginRegistry = class {
129
129
  * Errors thrown inside hooks are swallowed to prevent error cascades.
130
130
  */
131
131
  async runOnError(error, entry) {
132
- for (const plugin of this._plugins) if (plugin.onError) {
132
+ for (const plugin of this._plugins) if (plugin.onError) try {
133
133
  const r = plugin.onError(error, entry);
134
134
  if (r instanceof Promise) await r.catch(() => {});
135
- }
135
+ } catch {}
136
136
  }
137
137
  /** Run all `onShutdown` hooks concurrently. Hook errors are swallowed. */
138
138
  async runOnShutdown() {
139
139
  await Promise.all(this._plugins.filter((p) => Boolean(p.onShutdown)).map((p) => {
140
- const r = p.onShutdown();
141
- return r instanceof Promise ? r.catch(() => {}) : Promise.resolve();
140
+ try {
141
+ const r = p.onShutdown();
142
+ return r instanceof Promise ? r.catch(() => {}) : Promise.resolve();
143
+ } catch {
144
+ return Promise.resolve();
145
+ }
142
146
  }));
143
147
  }
144
148
  };
@@ -242,9 +246,9 @@ function _serializeError(error, includeStack, maxDepth, excludeFields, depth, se
242
246
  if (includeStack && error.stack) serialized.stack = error.stack;
243
247
  const errorWithCause = error;
244
248
  if (errorWithCause.cause !== void 0) if (errorWithCause.cause instanceof Error) serialized.cause = _serializeError(errorWithCause.cause, includeStack, maxDepth, excludeFields, depth + 1, seen);
245
- else serialized.cause = serializeValue(errorWithCause.cause, maxDepth - depth - 1);
249
+ else serialized.cause = serializeValue(errorWithCause.cause, maxDepth - depth - 1, seen);
246
250
  const aggregateError = error;
247
- if (Array.isArray(aggregateError.errors)) serialized.errors = aggregateError.errors.map((e) => e instanceof Error ? _serializeError(e, includeStack, maxDepth, excludeFields, depth + 1, seen) : serializeValue(e, maxDepth - depth - 1));
251
+ if (Array.isArray(aggregateError.errors)) serialized.errors = aggregateError.errors.map((e) => e instanceof Error ? _serializeError(e, includeStack, maxDepth, excludeFields, depth + 1, seen) : serializeValue(e, maxDepth - depth - 1, seen));
248
252
  const errorRecord = error;
249
253
  for (const field of EXTRA_FIELDS) if (!excludeFields.includes(field) && field in error && errorRecord[field] !== void 0) serialized[field] = errorRecord[field];
250
254
  const skip = new Set([
@@ -260,7 +264,7 @@ function _serializeError(error, includeStack, maxDepth, excludeFields, depth, se
260
264
  if (skip.has(key)) continue;
261
265
  if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
262
266
  try {
263
- serialized[key] = serializeValue(errorRecord[key], maxDepth - depth - 1);
267
+ serialized[key] = serializeValue(errorRecord[key], maxDepth - depth - 1, seen);
264
268
  } catch {
265
269
  serialized[key] = "[Unserializable]";
266
270
  }
@@ -269,20 +273,38 @@ function _serializeError(error, includeStack, maxDepth, excludeFields, depth, se
269
273
  }
270
274
  /**
271
275
  * Recursively serialize an arbitrary value to a JSON-safe representation.
276
+ *
277
+ * The `seen` guard is threaded through (not recreated) so cross-referencing
278
+ * errors nested inside plain objects/arrays are caught by the same circular
279
+ * check that protects the top-level error tree, rather than only relying on the
280
+ * depth limit.
272
281
  */
273
- function serializeValue(value, remainingDepth) {
282
+ function serializeValue(value, remainingDepth, seen) {
274
283
  if (remainingDepth <= 0) return "[Max Depth]";
275
284
  if (value === null || value === void 0) return value;
276
285
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
277
286
  if (value instanceof Date) return value.toISOString();
278
- if (value instanceof Error) return _serializeError(value, true, remainingDepth, [], 0, /* @__PURE__ */ new WeakSet());
279
- if (Array.isArray(value)) return value.map((item) => serializeValue(item, remainingDepth - 1));
287
+ if (value instanceof Error) {
288
+ if (seen.has(value)) return {
289
+ name: value.name,
290
+ message: value.message,
291
+ _circular: true
292
+ };
293
+ return _serializeError(value, true, remainingDepth, [], 0, seen);
294
+ }
295
+ if (Array.isArray(value)) {
296
+ if (seen.has(value)) return "[Circular]";
297
+ seen.add(value);
298
+ return value.map((item) => serializeValue(item, remainingDepth - 1, seen));
299
+ }
280
300
  if (typeof value === "object") {
301
+ if (seen.has(value)) return "[Circular]";
302
+ seen.add(value);
281
303
  const out = {};
282
304
  for (const [k, v] of Object.entries(value)) {
283
305
  if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
284
306
  try {
285
- out[k] = serializeValue(v, remainingDepth - 1);
307
+ out[k] = serializeValue(v, remainingDepth - 1, seen);
286
308
  } catch {
287
309
  out[k] = "[Unserializable]";
288
310
  }
@@ -337,17 +359,21 @@ function tryLoadOtelApi() {
337
359
  function getActiveOtelContext(opts = {}) {
338
360
  const api = tryLoadOtelApi();
339
361
  if (!api) return void 0;
340
- const ctx = api.context.active();
341
- const sc = api.trace.getSpanContext(ctx);
342
- if (!sc || !api.trace.isSpanContextValid(sc)) return void 0;
343
- const isSampled = (sc.traceFlags & api.trace.TraceFlags.SAMPLED) === api.trace.TraceFlags.SAMPLED;
344
- if (opts.sampledOnly && !isSampled) return void 0;
345
- return {
346
- traceId: sc.traceId,
347
- spanId: sc.spanId,
348
- traceFlags: sc.traceFlags,
349
- isSampled
350
- };
362
+ try {
363
+ const ctx = api.context.active();
364
+ const sc = api.trace.getSpanContext(ctx);
365
+ if (!sc || !api.trace.isSpanContextValid(sc)) return void 0;
366
+ const isSampled = (sc.traceFlags & api.trace.TraceFlags.SAMPLED) === api.trace.TraceFlags.SAMPLED;
367
+ if (opts.sampledOnly && !isSampled) return void 0;
368
+ return {
369
+ traceId: sc.traceId,
370
+ spanId: sc.spanId,
371
+ traceFlags: sc.traceFlags,
372
+ isSampled
373
+ };
374
+ } catch {
375
+ return;
376
+ }
351
377
  }
352
378
  /**
353
379
  * Returns a metadata object with OTel context fields ready to merge into a log call,
@@ -582,6 +608,21 @@ function redactObject(obj, config, _currentPath = "") {
582
608
  return result;
583
609
  }
584
610
  /**
611
+ * Apply pattern-based redaction to a single string (e.g. the log message).
612
+ *
613
+ * Path-based rules don't apply to a bare string — only the `patterns` are run.
614
+ * Returns the input unchanged when no patterns are configured, so the hot path
615
+ * stays allocation-free for the common no-redact case.
616
+ */
617
+ function applyRedactionToString(value, config) {
618
+ if (!config) return value;
619
+ const resolved = resolveConfig(config);
620
+ if (!resolved.patterns || resolved.patterns.length === 0) return value;
621
+ let redacted = value;
622
+ for (const pattern of resolved.patterns) redacted = redacted.replace(pattern, resolved.censor ?? DEFAULT_CENSOR);
623
+ return redacted;
624
+ }
625
+ /**
585
626
  * Apply redaction to a log payload (top-level call convenience wrapper).
586
627
  * Returns a new object — never mutates the input.
587
628
  */
@@ -606,6 +647,7 @@ var Sampler = class {
606
647
  constructor(config, onStats) {
607
648
  this.sampledTraces = /* @__PURE__ */ new Set();
608
649
  this.droppedTraces = /* @__PURE__ */ new Set();
650
+ this.maxTrackedTraces = 1e5;
609
651
  this._tokenBucket = 0;
610
652
  this._lastRefillMs = Date.now();
611
653
  this._stats = {
@@ -651,8 +693,8 @@ var Sampler = class {
651
693
  return false;
652
694
  }
653
695
  const emit = this._sampleByRate(lvl);
654
- if (emit) this.sampledTraces.add(traceId);
655
- else this.droppedTraces.add(traceId);
696
+ if (emit) this._rememberTrace(this.sampledTraces, traceId);
697
+ else this._rememberTrace(this.droppedTraces, traceId);
656
698
  if (emit) this._trackEmitted(lvl);
657
699
  else this._trackDropped(lvl);
658
700
  return emit;
@@ -697,6 +739,16 @@ var Sampler = class {
697
739
  if (rate <= 0) return false;
698
740
  return Math.random() < rate;
699
741
  }
742
+ /**
743
+ * Record a trace decision, bounding the Set so it can't grow without limit.
744
+ * If the Set has reached the cap, clear it before inserting — stale decisions
745
+ * are simply re-made on next sight, which keeps memory bounded at the cost of
746
+ * occasional re-sampling for very high-cardinality trace workloads.
747
+ */
748
+ _rememberTrace(set, traceId) {
749
+ if (set.size >= this.maxTrackedTraces) set.clear();
750
+ set.add(traceId);
751
+ }
700
752
  _consumeToken() {
701
753
  const now = Date.now();
702
754
  const elapsed = (now - this._lastRefillMs) / 1e3;
@@ -735,6 +787,10 @@ var Sampler = class {
735
787
  /** Module-level registry of all logger instances that have opted into graceful shutdown */
736
788
  const registry = /* @__PURE__ */ new Set();
737
789
  let shutdownHandlerRegistered = false;
790
+ /** Guards the handler against re-entrancy: a second signal (e.g. SIGINT after
791
+ * SIGTERM, or an impatient double Ctrl+C) must not start a second concurrent
792
+ * flush+exit, which could exit the process and truncate the first flush mid-write. */
793
+ let shutdownInProgress = false;
738
794
  /** Our own handler + the signals we attached it to, so reset() can detach exactly it. */
739
795
  let activeHandler;
740
796
  let activeSignals = [];
@@ -766,6 +822,8 @@ function flushOnExit(options = {}) {
766
822
  shutdownHandlerRegistered = true;
767
823
  const { timeout = 5e3, signals = ["SIGTERM", "SIGINT"], beforeFlush, afterFlush } = options;
768
824
  const handler = async (signal) => {
825
+ if (shutdownInProgress) return;
826
+ shutdownInProgress = true;
769
827
  const forceExitTimer = setTimeout(() => {
770
828
  process.stderr.write(`[logixia] Graceful shutdown timed out after ${timeout}ms on ${signal}. Force-exiting.\n`);
771
829
  process.exit(1);
@@ -793,6 +851,7 @@ function resetShutdownHandlers() {
793
851
  activeHandler = void 0;
794
852
  activeSignals = [];
795
853
  shutdownHandlerRegistered = false;
854
+ shutdownInProgress = false;
796
855
  }
797
856
 
798
857
  //#endregion
@@ -1098,7 +1157,11 @@ function createTraceMiddleware(config) {
1098
1157
  if (resolvedConfig.extractor) traceId = extractTraceId(req, resolvedConfig.extractor);
1099
1158
  if (!traceId) traceId = resolvedConfig.generator ? resolvedConfig.generator() : generateTraceId();
1100
1159
  req.traceId = traceId;
1101
- res.setHeader("X-Trace-Id", traceId);
1160
+ const resObj = res;
1161
+ if (!resObj.headersSent) try {
1162
+ if (typeof resObj.setHeader === "function") resObj.setHeader("X-Trace-Id", traceId);
1163
+ else if (typeof resObj.header === "function") resObj.header("X-Trace-Id", traceId);
1164
+ } catch {}
1102
1165
  runWithTraceId(traceId, () => next());
1103
1166
  };
1104
1167
  }
@@ -1580,12 +1643,13 @@ var LogixiaLogger = class LogixiaLogger {
1580
1643
  let payload;
1581
1644
  if (rawPayload !== void 0 && rawPayload !== null) payload = this._hasRedact ? applyRedaction(rawPayload, this.config.redact) ?? rawPayload : rawPayload;
1582
1645
  const traceId = this.config.traceId ? TraceContext.instance.getCurrentTraceId() ?? this.fallbackTraceId : void 0;
1646
+ const safeMessage = this._hasRedact ? applyRedactionToString(message, this.config.redact) : message;
1583
1647
  const entry = {
1584
1648
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1585
1649
  level,
1586
1650
  appName: this.config.appName ?? "App",
1587
1651
  environment: this.config.environment ?? "development",
1588
- message
1652
+ message: safeMessage
1589
1653
  };
1590
1654
  if (this.context) entry.context = this.context;
1591
1655
  if (payload !== void 0) entry.payload = payload;
@@ -1853,8 +1917,7 @@ let LogixiaLoggerService = _LogixiaLoggerService = class LogixiaLoggerService$1
1853
1917
  }
1854
1918
  formatMessage(message) {
1855
1919
  if (typeof message === "string") return message;
1856
- if (typeof message === "object") return JSON.stringify(message);
1857
- return String(message);
1920
+ return safeToString(message);
1858
1921
  }
1859
1922
  };
1860
1923
  LogixiaLoggerService = _LogixiaLoggerService = __decorate([Injectable({ scope: Scope.TRANSIENT }), __decorateMetadata("design:paramtypes", [Object])], LogixiaLoggerService);
@@ -1937,13 +2000,15 @@ let KafkaTraceInterceptor = class KafkaTraceInterceptor$1 {
1937
2000
  timestamp: rpcData === null || rpcData === void 0 ? void 0 : rpcData.timestamp
1938
2001
  };
1939
2002
  return new Observable((subscriber) => {
2003
+ let inner;
1940
2004
  this.ctx.run(traceId, () => {
1941
- next.handle().subscribe({
2005
+ inner = next.handle().subscribe({
1942
2006
  next: (value) => subscriber.next(value),
1943
2007
  error: (err) => subscriber.error(err),
1944
2008
  complete: () => subscriber.complete()
1945
2009
  });
1946
2010
  }, kafkaContext);
2011
+ return () => inner === null || inner === void 0 ? void 0 : inner.unsubscribe();
1947
2012
  });
1948
2013
  }
1949
2014
  };
@@ -1971,6 +2036,20 @@ function resolveResponseHeader(config) {
1971
2036
  if ((config === null || config === void 0 ? void 0 : config.responseHeader) === false) return null;
1972
2037
  return (config === null || config === void 0 ? void 0 : config.responseHeader) ?? DEFAULT_TRACE_RESPONSE_HEADER;
1973
2038
  }
2039
+ /**
2040
+ * Echo the trace ID on the response, tolerating non-Express responses and
2041
+ * already-sent headers. NestJS can run on Fastify (reply.header() instead of
2042
+ * res.setHeader()), and on a closed/sent response setHeader throws — neither
2043
+ * should crash the request. The trace ID is still propagated via async context.
2044
+ */
2045
+ function writeTraceHeader(res, header, traceId) {
2046
+ const r = res;
2047
+ if (r.headersSent) return;
2048
+ try {
2049
+ if (typeof r.setHeader === "function") r.setHeader(header, traceId);
2050
+ else if (typeof r.header === "function") r.header(header, traceId);
2051
+ } catch {}
2052
+ }
1974
2053
  let TraceMiddleware = class TraceMiddleware$1 {
1975
2054
  constructor(config) {
1976
2055
  this.config = config;
@@ -1992,7 +2071,7 @@ let TraceMiddleware = class TraceMiddleware$1 {
1992
2071
  if (this.config.enabled) this.ctx.setContextKey(this.config.contextKey ?? "traceId");
1993
2072
  }
1994
2073
  use(req, res, next) {
1995
- var _this$config;
2074
+ var _this$config, _req$socket, _req$connection;
1996
2075
  if (!((_this$config = this.config) === null || _this$config === void 0 ? void 0 : _this$config.enabled)) return next();
1997
2076
  let traceId;
1998
2077
  if (this.config.extractor) traceId = extractTraceId(req, this.config.extractor);
@@ -2004,12 +2083,12 @@ let TraceMiddleware = class TraceMiddleware$1 {
2004
2083
  if (!traceId) traceId = this.ctx.generate();
2005
2084
  req.traceId = traceId;
2006
2085
  const header = resolveResponseHeader(this.config);
2007
- if (header) res.setHeader(header, traceId);
2086
+ if (header) writeTraceHeader(res, header, traceId);
2008
2087
  this.ctx.run(traceId, () => next(), {
2009
2088
  method: req.method,
2010
2089
  url: req.url,
2011
2090
  userAgent: req.get("User-Agent"),
2012
- ip: req.ip || req.connection.remoteAddress
2091
+ ip: req.ip || ((_req$socket = req.socket) === null || _req$socket === void 0 ? void 0 : _req$socket.remoteAddress) || ((_req$connection = req.connection) === null || _req$connection === void 0 ? void 0 : _req$connection.remoteAddress)
2013
2092
  });
2014
2093
  }
2015
2094
  };
@@ -2066,13 +2145,15 @@ let WebSocketTraceInterceptor = class WebSocketTraceInterceptor$1 {
2066
2145
  clientAddress: client === null || client === void 0 || (_client$handshake3 = client.handshake) === null || _client$handshake3 === void 0 ? void 0 : _client$handshake3.address
2067
2146
  };
2068
2147
  return new Observable((observer) => {
2148
+ let inner;
2069
2149
  this.ctx.run(traceId, () => {
2070
- next.handle().subscribe({
2150
+ inner = next.handle().subscribe({
2071
2151
  next: (value) => observer.next(value),
2072
2152
  error: (err) => observer.error(err),
2073
2153
  complete: () => observer.complete()
2074
2154
  });
2075
2155
  }, wsContextData);
2156
+ return () => inner === null || inner === void 0 ? void 0 : inner.unsubscribe();
2076
2157
  });
2077
2158
  }
2078
2159
  };
@@ -2338,4 +2419,4 @@ LogixiaLoggerModule = _LogixiaLoggerModule = __decorate([Module({})], LogixiaLog
2338
2419
 
2339
2420
  //#endregion
2340
2421
  export { redactObject as A, LogLevel as B, setTraceId as C, registerForShutdown as D, flushOnExit as E, isError as F, createExpressContextMiddleware as G, globalPluginRegistry as H, normalizeError as I, createFastifyContextHook as K, serializeError as L, getActiveOtelContext as M, getOtelMetaFields as N, resetShutdownHandlers as O, initOtelBridge as P, DEFAULT_LOG_COLORS as R, runWithTraceId as S, deregisterFromShutdown as T, usePlugin as U, PluginRegistry as V, LogixiaContext as W, _setActiveContextKey as _, TraceMiddleware as a, getCurrentTraceId as b, KafkaTraceInterceptor as c, __decorateMetadata as d, LogixiaLogger as f, TraceContext as g, TRACE_CONTEXT_KEY as h, WebSocketTraceInterceptor as i, disableOtelBridge as j, applyRedaction as k, LogixiaLoggerService as l, DEFAULT_TRACE_HEADERS as m, LOGIXIA_LOGGER_PREFIX as n, resolveResponseHeader as o, createLogger as p, LogixiaLoggerModule as r, __decorateParam as s, LOGIXIA_LOGGER_CONFIG as t, __decorate as u, createTraceMiddleware as v, traceStorage as w, getTraceContextKey as x, extractTraceId as y, DEFAULT_LOG_LEVELS as z };
2341
- //# sourceMappingURL=logitron-logger.module-Bt_Jei1V.mjs.map
2422
+ //# sourceMappingURL=logitron-logger.module-BBC9nO5q.mjs.map