logixia 1.7.0 → 1.7.2

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.
@@ -807,51 +807,116 @@ var v4_default = v4;
807
807
 
808
808
  //#endregion
809
809
  //#region src/utils/trace.utils.ts
810
- /** The key under which the trace ID is stored in AsyncLocalStorage. */
810
+ /** Default key used to store the trace ID in AsyncLocalStorage. */
811
811
  const TRACE_CONTEXT_KEY = "traceId";
812
- const traceStorage = new AsyncLocalStorage();
813
812
  /**
814
- * Default trace ID generator using UUID v4
813
+ * Singleton that owns the AsyncLocalStorage and the active contextKey.
814
+ *
815
+ * Why a class instead of bare module-level variables?
816
+ * - The contextKey is user-configurable; encapsulating it here means there is
817
+ * exactly one place that reads and writes it.
818
+ * - All helpers (getCurrentTraceId, runWithTraceId, etc.) delegate to this
819
+ * instance, so the key is always in sync without hidden global state.
820
+ * - Easier to test: you can reset the singleton between test cases.
821
+ *
822
+ * Usage (advanced):
823
+ * import { TraceContext } from 'logixia';
824
+ * TraceContext.instance.contextKey // → 'traceId' (or custom)
825
+ * TraceContext.instance.getCurrentTraceId() // same as standalone fn
826
+ */
827
+ var TraceContext = class TraceContext {
828
+ static #_ = this._instance = null;
829
+ constructor() {
830
+ this.storage = new AsyncLocalStorage();
831
+ this._contextKey = TRACE_CONTEXT_KEY;
832
+ }
833
+ /** The process-wide singleton. */
834
+ static get instance() {
835
+ if (!TraceContext._instance) TraceContext._instance = new TraceContext();
836
+ return TraceContext._instance;
837
+ }
838
+ /** @internal Reset the singleton (useful in tests). */
839
+ static _reset() {
840
+ TraceContext._instance = null;
841
+ }
842
+ /** The AsyncLocalStorage key that holds the trace ID value. */
843
+ get contextKey() {
844
+ return this._contextKey;
845
+ }
846
+ /** @internal Called by TraceMiddleware when it boots to register the user's key. */
847
+ setContextKey(key) {
848
+ this._contextKey = key;
849
+ }
850
+ /** UUID v4 generator (default). */
851
+ generate() {
852
+ return v4_default();
853
+ }
854
+ /** Read the trace ID from the current async context. */
855
+ getCurrentTraceId() {
856
+ var _this$storage$getStor;
857
+ return (_this$storage$getStor = this.storage.getStore()) === null || _this$storage$getStor === void 0 ? void 0 : _this$storage$getStor[this._contextKey];
858
+ }
859
+ /**
860
+ * Mutate the CURRENT async context in-place.
861
+ *
862
+ * ⚠️ Uses `enterWith()` — prefer `run()` when you can wrap the operation
863
+ * in a callback, as it scopes the context to the callback only.
864
+ */
865
+ setTraceId(traceId, data) {
866
+ const current = this.storage.getStore() ?? {};
867
+ this.storage.enterWith({
868
+ ...current,
869
+ [this._contextKey]: traceId,
870
+ ...data
871
+ });
872
+ }
873
+ /** Run `fn` inside a new async context that carries `traceId`. */
874
+ run(traceId, fn, data) {
875
+ return this.storage.run({
876
+ [this._contextKey]: traceId,
877
+ ...data
878
+ }, fn);
879
+ }
880
+ };
881
+ /**
882
+ * The shared AsyncLocalStorage instance.
883
+ * @deprecated Prefer `TraceContext.instance.storage` for new code.
815
884
  */
885
+ const traceStorage = TraceContext.instance.storage;
886
+ /** Returns the key currently used to store the trace ID in AsyncLocalStorage. */
887
+ function getTraceContextKey() {
888
+ return TraceContext.instance.contextKey;
889
+ }
890
+ /** @internal Called by TraceMiddleware / traceMiddleware() when they boot. */
891
+ function _setActiveContextKey(key) {
892
+ TraceContext.instance.setContextKey(key);
893
+ }
894
+ /** Generate a UUID v4 trace ID. */
816
895
  function generateTraceId() {
817
- return v4_default();
896
+ return TraceContext.instance.generate();
818
897
  }
819
898
  /**
820
- * Get current trace ID from async context
899
+ * Get the current trace ID from async context.
900
+ * Returns `undefined` when called outside a traced request.
821
901
  */
822
902
  function getCurrentTraceId() {
823
- const store = traceStorage.getStore();
824
- return store === null || store === void 0 ? void 0 : store.traceId;
903
+ return TraceContext.instance.getCurrentTraceId();
825
904
  }
826
905
  /**
827
906
  * Set trace ID in the CURRENT async context without starting a new one.
828
907
  *
829
- * ⚠️ Uses `enterWith()` which mutates the context for the current async
830
- * execution and ALL futures spawned from it. Prefer `runWithTraceId()` when
831
- * you can wrap the operation in a callback, as it creates a properly-scoped
832
- * child context. Use `setTraceId()` only when you cannot use `runWithTraceId()`
833
- * (e.g., inside a class constructor or a non-callback async entry point).
908
+ * ⚠️ Uses `enterWith()` mutates the context for the current async execution
909
+ * and all futures spawned from it. Prefer `runWithTraceId()` when you can wrap
910
+ * the operation in a callback.
834
911
  */
835
912
  function setTraceId(traceId, data) {
836
- const currentStore = traceStorage.getStore() ?? {};
837
- traceStorage.enterWith({
838
- ...currentStore,
839
- traceId,
840
- ...data
841
- });
913
+ TraceContext.instance.setTraceId(traceId, data);
842
914
  }
843
- /**
844
- * Run function with trace ID context
845
- */
915
+ /** Run `fn` inside a new async context carrying `traceId`. */
846
916
  function runWithTraceId(traceId, fn, data) {
847
- return traceStorage.run({
848
- traceId,
849
- ...data
850
- }, fn);
917
+ return TraceContext.instance.run(traceId, fn, data);
851
918
  }
852
- /**
853
- * Extract trace ID from request using configuration
854
- */
919
+ /** Extract trace ID from request using configuration (header → query → body → params). */
855
920
  function extractTraceId(request, config) {
856
921
  const req = request;
857
922
  if (config.header) {
@@ -891,7 +956,7 @@ function extractTraceId(request, config) {
891
956
  * Default headers checked for incoming trace ID propagation, in priority order:
892
957
  * traceparent (W3C/OTel) → x-trace-id → x-request-id → x-correlation-id → trace-id
893
958
  */
894
- const DEFAULT_TRACE_HEADERS$1 = [
959
+ const DEFAULT_TRACE_HEADERS = [
895
960
  "traceparent",
896
961
  "x-trace-id",
897
962
  "x-request-id",
@@ -903,7 +968,7 @@ const DEFAULT_TRACE_HEADERS$1 = [
903
968
  */
904
969
  function createTraceMiddleware(config) {
905
970
  const defaultExtractor = {
906
- header: DEFAULT_TRACE_HEADERS$1,
971
+ header: DEFAULT_TRACE_HEADERS,
907
972
  query: ["traceId", "trace_id"]
908
973
  };
909
974
  const resolvedConfig = {
@@ -913,15 +978,14 @@ function createTraceMiddleware(config) {
913
978
  ...config.extractor
914
979
  } : defaultExtractor
915
980
  };
981
+ _setActiveContextKey(resolvedConfig.contextKey ?? TRACE_CONTEXT_KEY);
916
982
  return (req, res, next) => {
917
983
  let traceId;
918
984
  if (resolvedConfig.extractor) traceId = extractTraceId(req, resolvedConfig.extractor);
919
985
  if (!traceId) traceId = resolvedConfig.generator ? resolvedConfig.generator() : generateTraceId();
920
986
  req.traceId = traceId;
921
987
  res.setHeader("X-Trace-Id", traceId);
922
- runWithTraceId(traceId, () => {
923
- next();
924
- }, { requestId: req["id"] || req["requestId"] || generateTraceId() });
988
+ runWithTraceId(traceId, () => next(), { requestId: req["id"] || req["requestId"] || generateTraceId() });
925
989
  };
926
990
  }
927
991
 
@@ -980,7 +1044,7 @@ var LogixiaLogger = class LogixiaLogger {
980
1044
  this.timers = /* @__PURE__ */ new Map();
981
1045
  this.contextData = {};
982
1046
  this.fieldState = /* @__PURE__ */ new Map();
983
- this.fallbackTraceId = generateTraceId();
1047
+ this.fallbackTraceId = TraceContext.instance.generate();
984
1048
  this._levelValues = /* @__PURE__ */ new Map();
985
1049
  this._minLevelValue = 2;
986
1050
  this._colorMap = /* @__PURE__ */ new Map();
@@ -1366,7 +1430,7 @@ var LogixiaLogger = class LogixiaLogger {
1366
1430
  if (this.config.silent) return;
1367
1431
  if (!this.shouldLog(level)) return;
1368
1432
  if (this._sampler) {
1369
- const traceId$1 = this.config.traceId ? getCurrentTraceId() ?? this.fallbackTraceId : void 0;
1433
+ const traceId$1 = this.config.traceId ? TraceContext.instance.getCurrentTraceId() ?? this.fallbackTraceId : void 0;
1370
1434
  if (!this._sampler.shouldEmit(level, traceId$1)) return;
1371
1435
  }
1372
1436
  const alsContext = LogixiaContext.get();
@@ -1389,7 +1453,7 @@ var LogixiaLogger = class LogixiaLogger {
1389
1453
  } : mergedData;
1390
1454
  let payload;
1391
1455
  if (rawPayload !== void 0 && rawPayload !== null) payload = this._hasRedact ? applyRedaction(rawPayload, this.config.redact) ?? rawPayload : rawPayload;
1392
- const traceId = this.config.traceId ? getCurrentTraceId() ?? this.fallbackTraceId : void 0;
1456
+ const traceId = this.config.traceId ? TraceContext.instance.getCurrentTraceId() ?? this.fallbackTraceId : void 0;
1393
1457
  const entry = {
1394
1458
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1395
1459
  level,
@@ -1640,11 +1704,11 @@ let LogixiaLoggerService = _LogixiaLoggerService = class LogixiaLoggerService$1
1640
1704
  return childService;
1641
1705
  }
1642
1706
  getCurrentTraceId() {
1643
- return getCurrentTraceId();
1707
+ return TraceContext.instance.getCurrentTraceId();
1644
1708
  }
1645
- /** The AsyncLocalStorage key under which the trace ID is stored ('traceId'). */
1709
+ /** Returns the AsyncLocalStorage key currently used to store the trace ID. */
1646
1710
  get traceContextKey() {
1647
- return TRACE_CONTEXT_KEY;
1711
+ return getTraceContextKey();
1648
1712
  }
1649
1713
  async close() {
1650
1714
  return this.logger.close();
@@ -10528,6 +10592,7 @@ var import_cjs$1 = /* @__PURE__ */ __toESM(require_cjs());
10528
10592
  let KafkaTraceInterceptor = class KafkaTraceInterceptor$1 {
10529
10593
  constructor(config) {
10530
10594
  this.config = config;
10595
+ this.ctx = TraceContext.instance;
10531
10596
  this.config = {
10532
10597
  enabled: true,
10533
10598
  contextKey: "traceId",
@@ -10555,7 +10620,7 @@ let KafkaTraceInterceptor = class KafkaTraceInterceptor$1 {
10555
10620
  query: {},
10556
10621
  params: {}
10557
10622
  }, this.config.extractor);
10558
- if (!traceId) traceId = getCurrentTraceId();
10623
+ if (!traceId) traceId = this.ctx.getCurrentTraceId();
10559
10624
  if (!traceId) return next.handle();
10560
10625
  const kafkaContext = {
10561
10626
  messageType: "kafka",
@@ -10566,7 +10631,7 @@ let KafkaTraceInterceptor = class KafkaTraceInterceptor$1 {
10566
10631
  timestamp: rpcData === null || rpcData === void 0 ? void 0 : rpcData.timestamp
10567
10632
  };
10568
10633
  return new import_cjs$1.Observable((subscriber) => {
10569
- runWithTraceId(traceId, () => {
10634
+ this.ctx.run(traceId, () => {
10570
10635
  next.handle().subscribe({
10571
10636
  next: (value) => subscriber.next(value),
10572
10637
  error: (err) => subscriber.error(err),
@@ -10588,36 +10653,17 @@ function __decorateParam(paramIndex, decorator) {
10588
10653
 
10589
10654
  //#endregion
10590
10655
  //#region src/core/trace.middleware.ts
10591
- /**
10592
- * Default headers checked when extracting an incoming trace ID, in priority order:
10593
- * 1. traceparent — W3C Trace Context (OpenTelemetry standard)
10594
- * 2. x-trace-id — common custom header
10595
- * 3. x-request-id — used by AWS ALB, NGINX, etc.
10596
- * 4. x-correlation-id — used by Azure / enterprise ESBs
10597
- * 5. trace-id — legacy shorthand
10598
- *
10599
- * NOTE: for `traceparent` the format is `00-<traceId>-<spanId>-<flags>`.
10600
- * We store the full header value as-is so downstream systems can forward it
10601
- * unmodified. If you need only the 32-char traceId segment, configure a
10602
- * custom extractor via TraceIdConfig.extractor.
10603
- */
10604
- const DEFAULT_TRACE_HEADERS = [
10605
- "traceparent",
10606
- "x-trace-id",
10607
- "x-request-id",
10608
- "x-correlation-id",
10609
- "trace-id"
10610
- ];
10611
10656
  let TraceMiddleware = class TraceMiddleware$1 {
10612
10657
  constructor(config) {
10613
10658
  this.config = config;
10659
+ this.ctx = TraceContext.instance;
10614
10660
  const defaultExtractor = {
10615
10661
  header: DEFAULT_TRACE_HEADERS,
10616
10662
  query: ["traceId", "trace_id"]
10617
10663
  };
10618
10664
  this.config = {
10619
10665
  enabled: true,
10620
- generator: generateTraceId,
10666
+ generator: () => this.ctx.generate(),
10621
10667
  contextKey: "traceId",
10622
10668
  ...config,
10623
10669
  extractor: (config === null || config === void 0 ? void 0 : config.extractor) ? {
@@ -10625,20 +10671,19 @@ let TraceMiddleware = class TraceMiddleware$1 {
10625
10671
  ...config.extractor
10626
10672
  } : defaultExtractor
10627
10673
  };
10674
+ this.ctx.setContextKey(this.config.contextKey ?? "traceId");
10628
10675
  }
10629
10676
  use(req, res, next) {
10630
10677
  var _this$config;
10631
10678
  if (!((_this$config = this.config) === null || _this$config === void 0 ? void 0 : _this$config.enabled)) return next();
10632
10679
  let traceId;
10633
10680
  if (this.config.extractor) traceId = extractTraceId(req, this.config.extractor);
10634
- if (!traceId) traceId = this.config.generator ? this.config.generator() : generateTraceId();
10681
+ if (!traceId) traceId = this.config.generator ? this.config.generator() : this.ctx.generate();
10635
10682
  req.traceId = traceId;
10636
- req.requestId = req.requestId || generateTraceId();
10683
+ req.requestId = req.requestId || this.ctx.generate();
10637
10684
  res.setHeader("X-Trace-Id", traceId);
10638
10685
  res.setHeader("X-Request-Id", req.requestId);
10639
- runWithTraceId(traceId, () => {
10640
- next();
10641
- }, {
10686
+ this.ctx.run(traceId, () => next(), {
10642
10687
  requestId: req.requestId,
10643
10688
  method: req.method,
10644
10689
  url: req.url,
@@ -10659,6 +10704,7 @@ var import_cjs = /* @__PURE__ */ __toESM(require_cjs());
10659
10704
  let WebSocketTraceInterceptor = class WebSocketTraceInterceptor$1 {
10660
10705
  constructor(config) {
10661
10706
  this.config = config;
10707
+ this.ctx = TraceContext.instance;
10662
10708
  this.config = {
10663
10709
  enabled: true,
10664
10710
  contextKey: "traceId",
@@ -10690,7 +10736,7 @@ let WebSocketTraceInterceptor = class WebSocketTraceInterceptor$1 {
10690
10736
  params: {}
10691
10737
  }, this.config.extractor);
10692
10738
  }
10693
- if (!traceId) traceId = getCurrentTraceId();
10739
+ if (!traceId) traceId = this.ctx.getCurrentTraceId();
10694
10740
  if (!traceId) return next.handle();
10695
10741
  const wsContextData = {
10696
10742
  messageType: "websocket",
@@ -10700,7 +10746,7 @@ let WebSocketTraceInterceptor = class WebSocketTraceInterceptor$1 {
10700
10746
  clientAddress: client === null || client === void 0 || (_client$handshake3 = client.handshake) === null || _client$handshake3 === void 0 ? void 0 : _client$handshake3.address
10701
10747
  };
10702
10748
  return new import_cjs.Observable((observer) => {
10703
- runWithTraceId(traceId, () => {
10749
+ this.ctx.run(traceId, () => {
10704
10750
  next.handle().subscribe({
10705
10751
  next: (value) => observer.next(value),
10706
10752
  error: (err) => observer.error(err),
@@ -10737,7 +10783,7 @@ let LogixiaLoggerModule = class LogixiaLoggerModule$1 {
10737
10783
  else if (_LogixiaLoggerModule.loggerConfig.traceId === true) traceConfig = {
10738
10784
  enabled: true,
10739
10785
  contextKey: "traceId",
10740
- generator: () => generateTraceId()
10786
+ generator: () => TraceContext.instance.generate()
10741
10787
  };
10742
10788
  else traceConfig = void 0;
10743
10789
  return new TraceMiddleware(traceConfig).use(req, res, next);
@@ -10924,5 +10970,5 @@ let LogixiaLoggerModule = class LogixiaLoggerModule$1 {
10924
10970
  LogixiaLoggerModule = _LogixiaLoggerModule = __decorate([Module({})], LogixiaLoggerModule);
10925
10971
 
10926
10972
  //#endregion
10927
- export { getOtelMetaFields as A, usePlugin as B, flushOnExit as C, redactObject as D, applyRedaction as E, DEFAULT_LOG_COLORS as F, createExpressContextMiddleware as H, DEFAULT_LOG_LEVELS as I, LogLevel as L, isError as M, normalizeError as N, disableOtelBridge as O, serializeError as P, PluginRegistry as R, deregisterFromShutdown as S, resetShutdownHandlers as T, createFastifyContextHook as U, LogixiaContext as V, generateTraceId as _, TraceMiddleware as a, setTraceId as b, LogixiaLoggerService as c, LogixiaLogger as d, createLogger as f, extractTraceId as g, createTraceMiddleware as h, WebSocketTraceInterceptor as i, initOtelBridge as j, getActiveOtelContext as k, __decorate as l, TRACE_CONTEXT_KEY as m, LOGIXIA_LOGGER_PREFIX as n, __decorateParam as o, DEFAULT_TRACE_HEADERS$1 as p, LogixiaLoggerModule as r, KafkaTraceInterceptor as s, LOGIXIA_LOGGER_CONFIG as t, __decorateMetadata as u, getCurrentTraceId as v, registerForShutdown as w, traceStorage as x, runWithTraceId as y, globalPluginRegistry as z };
10928
- //# sourceMappingURL=logitron-logger.module-DnRTDcoz.mjs.map
10973
+ 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, createTraceMiddleware as _, TraceMiddleware as a, getCurrentTraceId as b, LogixiaLoggerService as c, LogixiaLogger as d, createLogger as f, _setActiveContextKey as g, TraceContext as h, WebSocketTraceInterceptor as i, disableOtelBridge as j, applyRedaction as k, __decorate as l, TRACE_CONTEXT_KEY as m, LOGIXIA_LOGGER_PREFIX as n, __decorateParam as o, DEFAULT_TRACE_HEADERS as p, LogixiaLoggerModule as r, KafkaTraceInterceptor as s, LOGIXIA_LOGGER_CONFIG as t, __decorateMetadata as u, extractTraceId as v, traceStorage as w, getTraceContextKey as x, generateTraceId as y, DEFAULT_LOG_LEVELS as z };
10974
+ //# sourceMappingURL=logitron-logger.module-BvWgddzf.mjs.map