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
- const require_transport_manager = require('./transport.manager-zgEZCJhR.js');
1
+ const require_transport_manager = require('./transport.manager-B9LF9uDd.js');
2
2
  let fast_json_stringify = require("fast-json-stringify");
3
3
  fast_json_stringify = require_transport_manager.__toESM(fast_json_stringify);
4
4
  let node_async_hooks = require("node:async_hooks");
@@ -92,10 +92,10 @@ var PluginRegistry = class {
92
92
  register(plugin) {
93
93
  if (this._plugins.some((p) => p.name === plugin.name)) return;
94
94
  this._plugins.push(plugin);
95
- if (plugin.onInit) {
95
+ if (plugin.onInit) try {
96
96
  const result = plugin.onInit();
97
97
  if (result instanceof Promise) result.catch(() => {});
98
- }
98
+ } catch {}
99
99
  }
100
100
  /** Remove a previously registered plugin by name. No-op if not found. */
101
101
  unregister(name) {
@@ -135,16 +135,20 @@ var PluginRegistry = class {
135
135
  * Errors thrown inside hooks are swallowed to prevent error cascades.
136
136
  */
137
137
  async runOnError(error, entry) {
138
- for (const plugin of this._plugins) if (plugin.onError) {
138
+ for (const plugin of this._plugins) if (plugin.onError) try {
139
139
  const r = plugin.onError(error, entry);
140
140
  if (r instanceof Promise) await r.catch(() => {});
141
- }
141
+ } catch {}
142
142
  }
143
143
  /** Run all `onShutdown` hooks concurrently. Hook errors are swallowed. */
144
144
  async runOnShutdown() {
145
145
  await Promise.all(this._plugins.filter((p) => Boolean(p.onShutdown)).map((p) => {
146
- const r = p.onShutdown();
147
- return r instanceof Promise ? r.catch(() => {}) : Promise.resolve();
146
+ try {
147
+ const r = p.onShutdown();
148
+ return r instanceof Promise ? r.catch(() => {}) : Promise.resolve();
149
+ } catch {
150
+ return Promise.resolve();
151
+ }
148
152
  }));
149
153
  }
150
154
  };
@@ -248,9 +252,9 @@ function _serializeError(error, includeStack, maxDepth, excludeFields, depth, se
248
252
  if (includeStack && error.stack) serialized.stack = error.stack;
249
253
  const errorWithCause = error;
250
254
  if (errorWithCause.cause !== void 0) if (errorWithCause.cause instanceof Error) serialized.cause = _serializeError(errorWithCause.cause, includeStack, maxDepth, excludeFields, depth + 1, seen);
251
- else serialized.cause = serializeValue(errorWithCause.cause, maxDepth - depth - 1);
255
+ else serialized.cause = serializeValue(errorWithCause.cause, maxDepth - depth - 1, seen);
252
256
  const aggregateError = error;
253
- 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));
257
+ 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));
254
258
  const errorRecord = error;
255
259
  for (const field of EXTRA_FIELDS) if (!excludeFields.includes(field) && field in error && errorRecord[field] !== void 0) serialized[field] = errorRecord[field];
256
260
  const skip = new Set([
@@ -266,7 +270,7 @@ function _serializeError(error, includeStack, maxDepth, excludeFields, depth, se
266
270
  if (skip.has(key)) continue;
267
271
  if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
268
272
  try {
269
- serialized[key] = serializeValue(errorRecord[key], maxDepth - depth - 1);
273
+ serialized[key] = serializeValue(errorRecord[key], maxDepth - depth - 1, seen);
270
274
  } catch {
271
275
  serialized[key] = "[Unserializable]";
272
276
  }
@@ -275,20 +279,38 @@ function _serializeError(error, includeStack, maxDepth, excludeFields, depth, se
275
279
  }
276
280
  /**
277
281
  * Recursively serialize an arbitrary value to a JSON-safe representation.
282
+ *
283
+ * The `seen` guard is threaded through (not recreated) so cross-referencing
284
+ * errors nested inside plain objects/arrays are caught by the same circular
285
+ * check that protects the top-level error tree, rather than only relying on the
286
+ * depth limit.
278
287
  */
279
- function serializeValue(value, remainingDepth) {
288
+ function serializeValue(value, remainingDepth, seen) {
280
289
  if (remainingDepth <= 0) return "[Max Depth]";
281
290
  if (value === null || value === void 0) return value;
282
291
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
283
292
  if (value instanceof Date) return value.toISOString();
284
- if (value instanceof Error) return _serializeError(value, true, remainingDepth, [], 0, /* @__PURE__ */ new WeakSet());
285
- if (Array.isArray(value)) return value.map((item) => serializeValue(item, remainingDepth - 1));
293
+ if (value instanceof Error) {
294
+ if (seen.has(value)) return {
295
+ name: value.name,
296
+ message: value.message,
297
+ _circular: true
298
+ };
299
+ return _serializeError(value, true, remainingDepth, [], 0, seen);
300
+ }
301
+ if (Array.isArray(value)) {
302
+ if (seen.has(value)) return "[Circular]";
303
+ seen.add(value);
304
+ return value.map((item) => serializeValue(item, remainingDepth - 1, seen));
305
+ }
286
306
  if (typeof value === "object") {
307
+ if (seen.has(value)) return "[Circular]";
308
+ seen.add(value);
287
309
  const out = {};
288
310
  for (const [k, v] of Object.entries(value)) {
289
311
  if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
290
312
  try {
291
- out[k] = serializeValue(v, remainingDepth - 1);
313
+ out[k] = serializeValue(v, remainingDepth - 1, seen);
292
314
  } catch {
293
315
  out[k] = "[Unserializable]";
294
316
  }
@@ -343,17 +365,21 @@ function tryLoadOtelApi() {
343
365
  function getActiveOtelContext(opts = {}) {
344
366
  const api = tryLoadOtelApi();
345
367
  if (!api) return void 0;
346
- const ctx = api.context.active();
347
- const sc = api.trace.getSpanContext(ctx);
348
- if (!sc || !api.trace.isSpanContextValid(sc)) return void 0;
349
- const isSampled = (sc.traceFlags & api.trace.TraceFlags.SAMPLED) === api.trace.TraceFlags.SAMPLED;
350
- if (opts.sampledOnly && !isSampled) return void 0;
351
- return {
352
- traceId: sc.traceId,
353
- spanId: sc.spanId,
354
- traceFlags: sc.traceFlags,
355
- isSampled
356
- };
368
+ try {
369
+ const ctx = api.context.active();
370
+ const sc = api.trace.getSpanContext(ctx);
371
+ if (!sc || !api.trace.isSpanContextValid(sc)) return void 0;
372
+ const isSampled = (sc.traceFlags & api.trace.TraceFlags.SAMPLED) === api.trace.TraceFlags.SAMPLED;
373
+ if (opts.sampledOnly && !isSampled) return void 0;
374
+ return {
375
+ traceId: sc.traceId,
376
+ spanId: sc.spanId,
377
+ traceFlags: sc.traceFlags,
378
+ isSampled
379
+ };
380
+ } catch {
381
+ return;
382
+ }
357
383
  }
358
384
  /**
359
385
  * Returns a metadata object with OTel context fields ready to merge into a log call,
@@ -588,6 +614,21 @@ function redactObject(obj, config, _currentPath = "") {
588
614
  return result;
589
615
  }
590
616
  /**
617
+ * Apply pattern-based redaction to a single string (e.g. the log message).
618
+ *
619
+ * Path-based rules don't apply to a bare string — only the `patterns` are run.
620
+ * Returns the input unchanged when no patterns are configured, so the hot path
621
+ * stays allocation-free for the common no-redact case.
622
+ */
623
+ function applyRedactionToString(value, config) {
624
+ if (!config) return value;
625
+ const resolved = resolveConfig(config);
626
+ if (!resolved.patterns || resolved.patterns.length === 0) return value;
627
+ let redacted = value;
628
+ for (const pattern of resolved.patterns) redacted = redacted.replace(pattern, resolved.censor ?? DEFAULT_CENSOR);
629
+ return redacted;
630
+ }
631
+ /**
591
632
  * Apply redaction to a log payload (top-level call convenience wrapper).
592
633
  * Returns a new object — never mutates the input.
593
634
  */
@@ -612,6 +653,7 @@ var Sampler = class {
612
653
  constructor(config, onStats) {
613
654
  this.sampledTraces = /* @__PURE__ */ new Set();
614
655
  this.droppedTraces = /* @__PURE__ */ new Set();
656
+ this.maxTrackedTraces = 1e5;
615
657
  this._tokenBucket = 0;
616
658
  this._lastRefillMs = Date.now();
617
659
  this._stats = {
@@ -657,8 +699,8 @@ var Sampler = class {
657
699
  return false;
658
700
  }
659
701
  const emit = this._sampleByRate(lvl);
660
- if (emit) this.sampledTraces.add(traceId);
661
- else this.droppedTraces.add(traceId);
702
+ if (emit) this._rememberTrace(this.sampledTraces, traceId);
703
+ else this._rememberTrace(this.droppedTraces, traceId);
662
704
  if (emit) this._trackEmitted(lvl);
663
705
  else this._trackDropped(lvl);
664
706
  return emit;
@@ -703,6 +745,16 @@ var Sampler = class {
703
745
  if (rate <= 0) return false;
704
746
  return Math.random() < rate;
705
747
  }
748
+ /**
749
+ * Record a trace decision, bounding the Set so it can't grow without limit.
750
+ * If the Set has reached the cap, clear it before inserting — stale decisions
751
+ * are simply re-made on next sight, which keeps memory bounded at the cost of
752
+ * occasional re-sampling for very high-cardinality trace workloads.
753
+ */
754
+ _rememberTrace(set, traceId) {
755
+ if (set.size >= this.maxTrackedTraces) set.clear();
756
+ set.add(traceId);
757
+ }
706
758
  _consumeToken() {
707
759
  const now = Date.now();
708
760
  const elapsed = (now - this._lastRefillMs) / 1e3;
@@ -741,6 +793,10 @@ var Sampler = class {
741
793
  /** Module-level registry of all logger instances that have opted into graceful shutdown */
742
794
  const registry = /* @__PURE__ */ new Set();
743
795
  let shutdownHandlerRegistered = false;
796
+ /** Guards the handler against re-entrancy: a second signal (e.g. SIGINT after
797
+ * SIGTERM, or an impatient double Ctrl+C) must not start a second concurrent
798
+ * flush+exit, which could exit the process and truncate the first flush mid-write. */
799
+ let shutdownInProgress = false;
744
800
  /** Our own handler + the signals we attached it to, so reset() can detach exactly it. */
745
801
  let activeHandler;
746
802
  let activeSignals = [];
@@ -772,6 +828,8 @@ function flushOnExit(options = {}) {
772
828
  shutdownHandlerRegistered = true;
773
829
  const { timeout = 5e3, signals = ["SIGTERM", "SIGINT"], beforeFlush, afterFlush } = options;
774
830
  const handler = async (signal) => {
831
+ if (shutdownInProgress) return;
832
+ shutdownInProgress = true;
775
833
  const forceExitTimer = setTimeout(() => {
776
834
  process.stderr.write(`[logixia] Graceful shutdown timed out after ${timeout}ms on ${signal}. Force-exiting.\n`);
777
835
  process.exit(1);
@@ -799,6 +857,7 @@ function resetShutdownHandlers() {
799
857
  activeHandler = void 0;
800
858
  activeSignals = [];
801
859
  shutdownHandlerRegistered = false;
860
+ shutdownInProgress = false;
802
861
  }
803
862
 
804
863
  //#endregion
@@ -1104,7 +1163,11 @@ function createTraceMiddleware(config) {
1104
1163
  if (resolvedConfig.extractor) traceId = extractTraceId(req, resolvedConfig.extractor);
1105
1164
  if (!traceId) traceId = resolvedConfig.generator ? resolvedConfig.generator() : generateTraceId();
1106
1165
  req.traceId = traceId;
1107
- res.setHeader("X-Trace-Id", traceId);
1166
+ const resObj = res;
1167
+ if (!resObj.headersSent) try {
1168
+ if (typeof resObj.setHeader === "function") resObj.setHeader("X-Trace-Id", traceId);
1169
+ else if (typeof resObj.header === "function") resObj.header("X-Trace-Id", traceId);
1170
+ } catch {}
1108
1171
  runWithTraceId(traceId, () => next());
1109
1172
  };
1110
1173
  }
@@ -1586,12 +1649,13 @@ var LogixiaLogger = class LogixiaLogger {
1586
1649
  let payload;
1587
1650
  if (rawPayload !== void 0 && rawPayload !== null) payload = this._hasRedact ? applyRedaction(rawPayload, this.config.redact) ?? rawPayload : rawPayload;
1588
1651
  const traceId = this.config.traceId ? TraceContext.instance.getCurrentTraceId() ?? this.fallbackTraceId : void 0;
1652
+ const safeMessage = this._hasRedact ? applyRedactionToString(message, this.config.redact) : message;
1589
1653
  const entry = {
1590
1654
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1591
1655
  level,
1592
1656
  appName: this.config.appName ?? "App",
1593
1657
  environment: this.config.environment ?? "development",
1594
- message
1658
+ message: safeMessage
1595
1659
  };
1596
1660
  if (this.context) entry.context = this.context;
1597
1661
  if (payload !== void 0) entry.payload = payload;
@@ -1859,8 +1923,7 @@ let LogixiaLoggerService = _LogixiaLoggerService = class LogixiaLoggerService$1
1859
1923
  }
1860
1924
  formatMessage(message) {
1861
1925
  if (typeof message === "string") return message;
1862
- if (typeof message === "object") return JSON.stringify(message);
1863
- return String(message);
1926
+ return require_transport_manager.safeToString(message);
1864
1927
  }
1865
1928
  };
1866
1929
  LogixiaLoggerService = _LogixiaLoggerService = __decorate([(0, __nestjs_common.Injectable)({ scope: __nestjs_common.Scope.TRANSIENT }), __decorateMetadata("design:paramtypes", [Object])], LogixiaLoggerService);
@@ -1943,13 +2006,15 @@ let KafkaTraceInterceptor = class KafkaTraceInterceptor$1 {
1943
2006
  timestamp: rpcData === null || rpcData === void 0 ? void 0 : rpcData.timestamp
1944
2007
  };
1945
2008
  return new rxjs.Observable((subscriber) => {
2009
+ let inner;
1946
2010
  this.ctx.run(traceId, () => {
1947
- next.handle().subscribe({
2011
+ inner = next.handle().subscribe({
1948
2012
  next: (value) => subscriber.next(value),
1949
2013
  error: (err) => subscriber.error(err),
1950
2014
  complete: () => subscriber.complete()
1951
2015
  });
1952
2016
  }, kafkaContext);
2017
+ return () => inner === null || inner === void 0 ? void 0 : inner.unsubscribe();
1953
2018
  });
1954
2019
  }
1955
2020
  };
@@ -1977,6 +2042,20 @@ function resolveResponseHeader(config) {
1977
2042
  if ((config === null || config === void 0 ? void 0 : config.responseHeader) === false) return null;
1978
2043
  return (config === null || config === void 0 ? void 0 : config.responseHeader) ?? DEFAULT_TRACE_RESPONSE_HEADER;
1979
2044
  }
2045
+ /**
2046
+ * Echo the trace ID on the response, tolerating non-Express responses and
2047
+ * already-sent headers. NestJS can run on Fastify (reply.header() instead of
2048
+ * res.setHeader()), and on a closed/sent response setHeader throws — neither
2049
+ * should crash the request. The trace ID is still propagated via async context.
2050
+ */
2051
+ function writeTraceHeader(res, header, traceId) {
2052
+ const r = res;
2053
+ if (r.headersSent) return;
2054
+ try {
2055
+ if (typeof r.setHeader === "function") r.setHeader(header, traceId);
2056
+ else if (typeof r.header === "function") r.header(header, traceId);
2057
+ } catch {}
2058
+ }
1980
2059
  let TraceMiddleware = class TraceMiddleware$1 {
1981
2060
  constructor(config) {
1982
2061
  this.config = config;
@@ -1998,7 +2077,7 @@ let TraceMiddleware = class TraceMiddleware$1 {
1998
2077
  if (this.config.enabled) this.ctx.setContextKey(this.config.contextKey ?? "traceId");
1999
2078
  }
2000
2079
  use(req, res, next) {
2001
- var _this$config;
2080
+ var _this$config, _req$socket, _req$connection;
2002
2081
  if (!((_this$config = this.config) === null || _this$config === void 0 ? void 0 : _this$config.enabled)) return next();
2003
2082
  let traceId;
2004
2083
  if (this.config.extractor) traceId = extractTraceId(req, this.config.extractor);
@@ -2010,12 +2089,12 @@ let TraceMiddleware = class TraceMiddleware$1 {
2010
2089
  if (!traceId) traceId = this.ctx.generate();
2011
2090
  req.traceId = traceId;
2012
2091
  const header = resolveResponseHeader(this.config);
2013
- if (header) res.setHeader(header, traceId);
2092
+ if (header) writeTraceHeader(res, header, traceId);
2014
2093
  this.ctx.run(traceId, () => next(), {
2015
2094
  method: req.method,
2016
2095
  url: req.url,
2017
2096
  userAgent: req.get("User-Agent"),
2018
- ip: req.ip || req.connection.remoteAddress
2097
+ 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)
2019
2098
  });
2020
2099
  }
2021
2100
  };
@@ -2072,13 +2151,15 @@ let WebSocketTraceInterceptor = class WebSocketTraceInterceptor$1 {
2072
2151
  clientAddress: client === null || client === void 0 || (_client$handshake3 = client.handshake) === null || _client$handshake3 === void 0 ? void 0 : _client$handshake3.address
2073
2152
  };
2074
2153
  return new rxjs.Observable((observer) => {
2154
+ let inner;
2075
2155
  this.ctx.run(traceId, () => {
2076
- next.handle().subscribe({
2156
+ inner = next.handle().subscribe({
2077
2157
  next: (value) => observer.next(value),
2078
2158
  error: (err) => observer.error(err),
2079
2159
  complete: () => observer.complete()
2080
2160
  });
2081
2161
  }, wsContextData);
2162
+ return () => inner === null || inner === void 0 ? void 0 : inner.unsubscribe();
2082
2163
  });
2083
2164
  }
2084
2165
  };
@@ -2619,4 +2700,4 @@ Object.defineProperty(exports, 'usePlugin', {
2619
2700
  return usePlugin;
2620
2701
  }
2621
2702
  });
2622
- //# sourceMappingURL=logitron-logger.module-bJ1hGhaL.js.map
2703
+ //# sourceMappingURL=logitron-logger.module-Dlf5GwJ9.js.map