logixia 2.0.8 → 2.0.9

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 CHANGED
@@ -35,7 +35,7 @@ Most Node loggers give you a fast `info()` and leave the rest to you. logixia ke
35
35
  - **Transports for real destinations.** Console, rotating files, PostgreSQL, MySQL, MongoDB, SQLite, CloudWatch, Google Cloud Logging, Azure Monitor, Datadog, OTLP, Slack, Discord, and a worker-thread wrapper. Several can run on one log call.
36
36
  - **OpenTelemetry-native.** Read the active span for trace/log correlation, or export logs as OTLP/HTTP records. The OTLP transport builds the JSON itself, so no `@opentelemetry/*` SDK is required to emit logs.
37
37
  - **Runs outside Node too.** `logixia/browser` has no Node built-ins and is written for browsers and edge runtimes such as Cloudflare Workers.
38
- - **Small footprint.** Three runtime dependencies (`commander`, `fast-json-stringify`, `picocolors`), no install scripts, ESM and CommonJS builds with types.
38
+ - **Small footprint.** Two runtime dependencies (`commander`, `picocolors`), no install scripts, ESM and CommonJS builds with types.
39
39
 
40
40
  Log calls are `async` and transport failures are caught internally, so a full disk or a flaky database does not crash your process.
41
41
 
@@ -6,7 +6,6 @@ import { n as internalLog, r as internalWarn, t as internalError } from "./inter
6
6
  import { t as safeToString } from "./coerce.utils-B_rEudCn.mjs";
7
7
  import { i as stringifyForLog } from "./safe-stringify--7gbe95n.mjs";
8
8
  import { c as TransportManager, n as _getOtelPayloadIfEnabled, r as _isOtelBridgeEnabled, v as stripControlChars } from "./otel-DOuwGPCd.mjs";
9
- import buildFastStringify from "fast-json-stringify";
10
9
  import { constants } from "node:os";
11
10
  // src/types/index.ts
12
11
  const LogLevel = {
@@ -194,6 +193,16 @@ const EXTRA_FIELDS = [
194
193
  "port",
195
194
  "type"
196
195
  ];
196
+ const SKIPPED_FIELDS = /* @__PURE__ */ new Set([
197
+ "name",
198
+ "message",
199
+ "stack",
200
+ "cause",
201
+ "errors",
202
+ ...EXTRA_FIELDS
203
+ ]);
204
+ const NO_OPTIONS = Object.freeze({});
205
+ const NO_FIELDS = [];
197
206
  /**
198
207
  * Serialize an Error into a plain JSON-safe object, including:
199
208
  * - `name`, `message`, `stack`
@@ -202,8 +211,8 @@ const EXTRA_FIELDS = [
202
211
  * - standard Node.js error fields (`code`, `statusCode`, `errno`, …)
203
212
  * - any other enumerable own properties
204
213
  */
205
- function serializeError(error, options = {}) {
206
- const { includeStack = true, maxDepth = 5, excludeFields = [] } = options;
214
+ function serializeError(error, options = NO_OPTIONS) {
215
+ const { includeStack = true, maxDepth = 5, excludeFields = NO_FIELDS } = options;
207
216
  return _serializeError(error, includeStack, maxDepth, excludeFields, 0, /* @__PURE__ */ new WeakSet());
208
217
  }
209
218
  function _serializeError(error, includeStack, maxDepth, excludeFields, depth, seen) {
@@ -231,16 +240,8 @@ function _serializeError(error, includeStack, maxDepth, excludeFields, depth, se
231
240
  const aggregateError = error;
232
241
  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));
233
242
  const errorRecord = error;
234
- for (const field of EXTRA_FIELDS) if (!excludeFields.includes(field) && field in error && errorRecord[field] !== void 0) serialized[field] = errorRecord[field];
235
- const skip = /* @__PURE__ */ new Set([
236
- "name",
237
- "message",
238
- "stack",
239
- "cause",
240
- "errors",
241
- ...EXTRA_FIELDS,
242
- ...excludeFields
243
- ]);
243
+ for (const field of EXTRA_FIELDS) if (!excludeFields.includes(field) && errorRecord[field] !== void 0) serialized[field] = errorRecord[field];
244
+ const skip = excludeFields.length === 0 ? SKIPPED_FIELDS : /* @__PURE__ */ new Set([...SKIPPED_FIELDS, ...excludeFields]);
244
245
  for (const key of Object.getOwnPropertyNames(error)) {
245
246
  if (skip.has(key)) continue;
246
247
  try {
@@ -1033,23 +1034,6 @@ function warnOnReservedLevelNames(levels) {
1033
1034
  * - Feature 4: Cause-chain error serialization (handled in error.utils)
1034
1035
  * - Feature 5: Adaptive log level based on NODE_ENV / CI detection
1035
1036
  */
1036
- const _fastStringifyEntry = buildFastStringify({
1037
- type: "object",
1038
- properties: {
1039
- timestamp: { type: "string" },
1040
- level: { type: "string" },
1041
- appName: { type: "string" },
1042
- environment: { type: "string" },
1043
- message: { type: "string" },
1044
- context: { type: "string" },
1045
- traceId: { type: "string" },
1046
- payload: {
1047
- type: "object",
1048
- additionalProperties: true
1049
- }
1050
- },
1051
- additionalProperties: true
1052
- });
1053
1037
  let _tsCacheMs = 0;
1054
1038
  let _tsCacheStr = "";
1055
1039
  function nowIso() {
@@ -1060,6 +1044,39 @@ function nowIso() {
1060
1044
  }
1061
1045
  return _tsCacheStr;
1062
1046
  }
1047
+ const RESOLVED = Promise.resolve();
1048
+ const JSON_STRING_FIELDS = [
1049
+ "timestamp",
1050
+ "level",
1051
+ "appName",
1052
+ "environment",
1053
+ "message",
1054
+ "context",
1055
+ "traceId"
1056
+ ];
1057
+ function toJsonString(value) {
1058
+ if (value === null) return "";
1059
+ if (value instanceof Date) return value.toISOString();
1060
+ if (value instanceof RegExp) return value.source;
1061
+ return String(value);
1062
+ }
1063
+ function toJsonEntry(entry) {
1064
+ const record = entry;
1065
+ let copy;
1066
+ for (const field of JSON_STRING_FIELDS) {
1067
+ const value = record[field];
1068
+ if (value !== void 0 && typeof value !== "string") {
1069
+ copy ??= { ...record };
1070
+ copy[field] = toJsonString(value);
1071
+ }
1072
+ }
1073
+ const payload = entry.payload;
1074
+ if (payload !== void 0 && (payload === null || typeof payload !== "object" || Array.isArray(payload))) {
1075
+ copy ??= { ...record };
1076
+ copy["payload"] = Object.assign({}, payload);
1077
+ }
1078
+ return copy ?? entry;
1079
+ }
1063
1080
  function namespacePatternToRegex(pattern) {
1064
1081
  const escaped = pattern.split(".").map((s) => s === "*" ? "[^.]+" : s.replace(/[$()*+.?[\\\]^{|}]/g, "\\$&")).join("\\.");
1065
1082
  return new RegExp(`^${escaped}$`);
@@ -1141,6 +1158,11 @@ var LogixiaLogger = class LogixiaLogger {
1141
1158
  this._hasContextData = false;
1142
1159
  this._formattedLevels = /* @__PURE__ */ new Map();
1143
1160
  this._formattedAppName = "";
1161
+ this._jsonHeads = /* @__PURE__ */ new Map();
1162
+ this._jsonHeadsApp = "";
1163
+ this._jsonHeadsEnv = "";
1164
+ this._jsonContextKey = void 0;
1165
+ this._jsonContext = "";
1144
1166
  this._hasRedact = false;
1145
1167
  this._nsSeenVersion = 0;
1146
1168
  this._parent = parent;
@@ -1241,9 +1263,7 @@ var LogixiaLogger = class LogixiaLogger {
1241
1263
  var _this$config$levelOpt3, _this$config$levelOpt4;
1242
1264
  warnOnReservedLevelNames((_this$config$levelOpt3 = this.config.levelOptions) === null || _this$config$levelOpt3 === void 0 ? void 0 : _this$config$levelOpt3.levels);
1243
1265
  if ((_this$config$levelOpt4 = this.config.levelOptions) === null || _this$config$levelOpt4 === void 0 ? void 0 : _this$config$levelOpt4.levels) {
1244
- for (const levelName of Object.keys(this.config.levelOptions.levels)) if (!this[levelName.toLowerCase()]) this[levelName.toLowerCase()] = async (message, data) => {
1245
- await this.logLevel(levelName.toLowerCase(), message, data);
1246
- };
1266
+ for (const levelName of Object.keys(this.config.levelOptions.levels)) if (!this[levelName.toLowerCase()]) this[levelName.toLowerCase()] = (message, data) => this.logLevel(levelName.toLowerCase(), message, data);
1247
1267
  }
1248
1268
  }
1249
1269
  /**
@@ -1345,40 +1365,53 @@ var LogixiaLogger = class LogixiaLogger {
1345
1365
  * (name, message, stack) under `data.error`; a string is logged as-is.
1346
1366
  * `data` is merged into the entry payload.
1347
1367
  */
1348
- async error(messageOrError, data) {
1349
- if (isError(messageOrError)) await this.log("error", messageOrError.message, {
1350
- ...data,
1351
- error: serializeError(messageOrError)
1352
- });
1353
- else await this.log("error", messageOrError, data);
1368
+ error(messageOrError, data) {
1369
+ try {
1370
+ return isError(messageOrError) ? this._run("error", messageOrError.message, {
1371
+ ...data,
1372
+ error: serializeError(messageOrError)
1373
+ }) : this._run("error", messageOrError, data);
1374
+ } catch (error) {
1375
+ return Promise.reject(error);
1376
+ }
1354
1377
  }
1355
1378
  /** Log `message` at `warn` level; `data` is merged into the entry payload. */
1356
- async warn(message, data) {
1357
- await this.log("warn", message, data);
1379
+ warn(message, data) {
1380
+ return this._run("warn", message, data);
1358
1381
  }
1359
1382
  /** Log `message` at `info` level; `data` is merged into the entry payload. */
1360
- async info(message, data) {
1361
- await this.log("info", message, data);
1383
+ info(message, data) {
1384
+ return this._run("info", message, data);
1362
1385
  }
1363
1386
  /** Log `message` at `debug` level; `data` is merged into the entry payload. */
1364
- async debug(message, data) {
1365
- await this.log("debug", message, data);
1387
+ debug(message, data) {
1388
+ return this._run("debug", message, data);
1366
1389
  }
1367
1390
  /** Log `message` at `trace` level; `data` is merged into the entry payload. */
1368
- async trace(message, data) {
1369
- await this.log("trace", message, data);
1391
+ trace(message, data) {
1392
+ return this._run("trace", message, data);
1370
1393
  }
1371
1394
  /** Log `message` at `verbose` level; `data` is merged into the entry payload. */
1372
- async verbose(message, data) {
1373
- await this.log("verbose", message, data);
1395
+ verbose(message, data) {
1396
+ return this._run("verbose", message, data);
1374
1397
  }
1375
1398
  /**
1376
1399
  * Log at an arbitrary level name, including custom levels. Levels below the
1377
1400
  * active threshold are dropped, as are level names that are not registered
1378
1401
  * in `levelOptions.levels` (and are not built-in).
1379
1402
  */
1380
- async logLevel(level, message, data) {
1381
- await this.log(level, message, data);
1403
+ logLevel(level, message, data) {
1404
+ return this._run(level, message, data);
1405
+ }
1406
+ _run(level, message, data) {
1407
+ try {
1408
+ return this._settle(this.log(level, message, data));
1409
+ } catch (error) {
1410
+ return Promise.reject(error);
1411
+ }
1412
+ }
1413
+ _settle(out) {
1414
+ return out ?? RESOLVED;
1382
1415
  }
1383
1416
  /** Start a named timer; finish it with {@link timeEnd}. Reusing a label restarts it. */
1384
1417
  time(label) {
@@ -1684,13 +1717,15 @@ var LogixiaLogger = class LogixiaLogger {
1684
1717
  await this._pluginRegistry.runOnShutdown();
1685
1718
  deregisterFromShutdown(this);
1686
1719
  }
1687
- async log(level, message, rawData) {
1688
- if (this.config.silent) return;
1720
+ /** Returns a Promise only when a plugin or a transport is asynchronous. */
1721
+ log(level, message, rawData) {
1722
+ const config = this.config;
1723
+ if (config.silent) return;
1689
1724
  if (!this.shouldLog(level)) return;
1690
1725
  const data = rawData instanceof Error ? { error: serializeError(rawData) } : rawData;
1691
1726
  const alsContext = LogixiaContext.get();
1692
1727
  let activeTraceId;
1693
- if (this.config.traceId) {
1728
+ if (config.traceId) {
1694
1729
  const ctxTraceId = alsContext === null || alsContext === void 0 ? void 0 : alsContext.traceId;
1695
1730
  activeTraceId = TraceContext.instance.getCurrentTraceId() ?? (typeof ctxTraceId === "string" ? ctxTraceId : void 0);
1696
1731
  }
@@ -1698,7 +1733,7 @@ var LogixiaLogger = class LogixiaLogger {
1698
1733
  const hasAls = alsContext !== void 0 && Object.keys(alsContext).length > 0;
1699
1734
  const otelFields = _isOtelBridgeEnabled() ? _getOtelPayloadIfEnabled() : void 0;
1700
1735
  const hasOtel = otelFields !== void 0 && Object.keys(otelFields).length > 0;
1701
- const traceId = this.config.traceId ? activeTraceId ?? this.fallbackTraceId : void 0;
1736
+ const traceId = config.traceId ? activeTraceId ?? this.fallbackTraceId : void 0;
1702
1737
  let mergedData;
1703
1738
  if (hasAls) {
1704
1739
  const als = { ...alsContext };
@@ -1725,7 +1760,7 @@ var LogixiaLogger = class LogixiaLogger {
1725
1760
  let payload;
1726
1761
  if (rawPayload !== void 0 && rawPayload !== null) {
1727
1762
  if (this._hasRedact) try {
1728
- payload = applyRedaction(rawPayload, this.config.redact) ?? rawPayload;
1763
+ payload = applyRedaction(rawPayload, config.redact) ?? rawPayload;
1729
1764
  } catch (error) {
1730
1765
  internalError("Redaction failed", error);
1731
1766
  payload = { redaction: REDACTION_FAILED };
@@ -1734,7 +1769,7 @@ var LogixiaLogger = class LogixiaLogger {
1734
1769
  }
1735
1770
  let safeMessage = message;
1736
1771
  if (this._hasRedact) try {
1737
- safeMessage = applyRedactionToString(message, this.config.redact);
1772
+ safeMessage = applyRedactionToString(message, config.redact);
1738
1773
  } catch (error) {
1739
1774
  internalError("Redaction failed", error);
1740
1775
  safeMessage = REDACTION_FAILED;
@@ -1742,20 +1777,20 @@ var LogixiaLogger = class LogixiaLogger {
1742
1777
  const entry = {
1743
1778
  timestamp: nowIso(),
1744
1779
  level,
1745
- appName: this.config.appName ?? "App",
1746
- environment: this.config.environment ?? "development",
1780
+ appName: config.appName ?? "App",
1781
+ environment: config.environment ?? "development",
1747
1782
  message: safeMessage
1748
1783
  };
1749
1784
  if (this.context) entry.context = this.context;
1750
- if (payload !== void 0) entry.payload = payload;
1751
1785
  if (traceId !== void 0) entry.traceId = traceId;
1752
- let finalEntry = entry;
1753
- if (this._pluginRegistry.size > 0) {
1754
- finalEntry = await this._pluginRegistry.runOnLog(entry);
1755
- if (finalEntry === null) return;
1756
- }
1757
- const out = this.output(finalEntry, level);
1758
- if (out !== void 0) await out;
1786
+ if (payload !== void 0) entry.payload = payload;
1787
+ if (this._pluginRegistry.size > 0) return this._logThroughPlugins(entry, level);
1788
+ return this.output(entry, level);
1789
+ }
1790
+ async _logThroughPlugins(entry, level) {
1791
+ const finalEntry = await this._pluginRegistry.runOnLog(entry);
1792
+ if (finalEntry === null) return;
1793
+ await this.output(finalEntry, level);
1759
1794
  }
1760
1795
  /**
1761
1796
  * Hot-path level check: a single Map lookup + integer compare.
@@ -1797,19 +1832,27 @@ var LogixiaLogger = class LogixiaLogger {
1797
1832
  return this.getLevel();
1798
1833
  }
1799
1834
  formatLog(entry) {
1800
- var _this$config$format2, _this$config$format3, _this$config$format4;
1801
- if ((_this$config$format2 = this.config.format) === null || _this$config$format2 === void 0 ? void 0 : _this$config$format2.json) try {
1802
- return _fastStringifyEntry(entry);
1803
- } catch {
1804
- return stringifyForLog(entry);
1835
+ const format = this.config.format;
1836
+ if (format === null || format === void 0 ? void 0 : format.json) {
1837
+ if (this._pluginRegistry.size === 0) try {
1838
+ const line = this._buildJsonLine(entry);
1839
+ if (line !== void 0) return line;
1840
+ } catch {}
1841
+ let jsonEntry = entry;
1842
+ try {
1843
+ jsonEntry = toJsonEntry(entry);
1844
+ return JSON.stringify(jsonEntry);
1845
+ } catch {
1846
+ return stringifyForLog(jsonEntry);
1847
+ }
1805
1848
  }
1806
1849
  let formatted = "";
1807
- const doColorize = ((_this$config$format3 = this.config.format) === null || _this$config$format3 === void 0 ? void 0 : _this$config$format3.colorize) ?? true;
1850
+ const doColorize = (format === null || format === void 0 ? void 0 : format.colorize) ?? true;
1808
1851
  const gray = doColorize ? this._colorMap.get("gray") : "";
1809
1852
  const cyan = doColorize ? this._colorMap.get("cyan") : "";
1810
1853
  const yellow = doColorize ? this._colorMap.get("yellow") : "";
1811
1854
  const reset = doColorize ? this._colorMap.get("reset") : "";
1812
- if (((_this$config$format4 = this.config.format) === null || _this$config$format4 === void 0 ? void 0 : _this$config$format4.timestamp) !== false && this._fieldCache.get("timestamp") !== false) formatted += `${gray}[${entry.timestamp}]${reset} `;
1855
+ if ((format === null || format === void 0 ? void 0 : format.timestamp) !== false && this._fieldCache.get("timestamp") !== false) formatted += `${gray}[${entry.timestamp}]${reset} `;
1813
1856
  if (this._fieldCache.get("level") !== false) formatted += this._formattedLevels.get(entry.level) ?? `[${entry.level.toUpperCase()}] `;
1814
1857
  formatted += this._formattedAppName;
1815
1858
  if (entry.traceId && this._fieldCache.get("traceId") !== false) formatted += `${cyan}[${stripControlChars(String(entry.traceId))}]${reset} `;
@@ -1818,9 +1861,46 @@ var LogixiaLogger = class LogixiaLogger {
1818
1861
  if (entry.payload !== void 0 && this._fieldCache.get("payload") !== false) formatted += ` ${stringifyForLog(entry.payload)}`;
1819
1862
  return formatted;
1820
1863
  }
1864
+ /**
1865
+ * The same bytes JSON.stringify(entry) gives for an entry log() built, without
1866
+ * walking the fixed fields. Returns undefined when a field has an unexpected type.
1867
+ */
1868
+ _buildJsonLine(entry) {
1869
+ const { timestamp, level, appName, environment, message, context, traceId, payload } = entry;
1870
+ if (typeof timestamp !== "string" || typeof message !== "string" || typeof appName !== "string" || typeof environment !== "string") return;
1871
+ if (appName !== this._jsonHeadsApp || environment !== this._jsonHeadsEnv) {
1872
+ this._jsonHeads.clear();
1873
+ this._jsonHeadsApp = appName;
1874
+ this._jsonHeadsEnv = environment;
1875
+ }
1876
+ let head = this._jsonHeads.get(level);
1877
+ if (head === void 0) {
1878
+ if (typeof level !== "string") return void 0;
1879
+ head = `,"level":${JSON.stringify(level)},"appName":${JSON.stringify(appName)},"environment":${JSON.stringify(environment)},"message":`;
1880
+ this._jsonHeads.set(level, head);
1881
+ }
1882
+ let line = `{"timestamp":"${timestamp}"${head}${JSON.stringify(message)}`;
1883
+ if (context !== void 0) {
1884
+ if (typeof context !== "string") return void 0;
1885
+ if (context !== this._jsonContextKey) {
1886
+ this._jsonContextKey = context;
1887
+ this._jsonContext = `,"context":${JSON.stringify(context)}`;
1888
+ }
1889
+ line += this._jsonContext;
1890
+ }
1891
+ if (traceId !== void 0) {
1892
+ if (typeof traceId !== "string") return void 0;
1893
+ line += `,"traceId":${JSON.stringify(traceId)}`;
1894
+ }
1895
+ if (payload !== void 0) {
1896
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload) || typeof payload.toJSON === "function") return;
1897
+ line += `,"payload":${JSON.stringify(payload)}`;
1898
+ }
1899
+ return `${line}}`;
1900
+ }
1821
1901
  colorize(text, color) {
1822
- var _this$config$format5;
1823
- if (!((_this$config$format5 = this.config.format) === null || _this$config$format5 === void 0 ? void 0 : _this$config$format5.colorize)) return text;
1902
+ var _this$config$format2;
1903
+ if (!((_this$config$format2 = this.config.format) === null || _this$config$format2 === void 0 ? void 0 : _this$config$format2.colorize)) return text;
1824
1904
  return `${this._colorMap.get(color.toLowerCase()) ?? this._colorMap.get("white")}${text}${this._colorMap.get("reset")}`;
1825
1905
  }
1826
1906
  /**
@@ -1872,9 +1952,7 @@ function createLogger(config, context) {
1872
1952
  const logger = new LogixiaLogger(config, context);
1873
1953
  const mutableLogger = logger;
1874
1954
  if ((_config$levelOptions3 = config.levelOptions) === null || _config$levelOptions3 === void 0 ? void 0 : _config$levelOptions3.levels) {
1875
- for (const levelName of Object.keys(config.levelOptions.levels)) if (!mutableLogger[levelName]) mutableLogger[levelName] = async (message, data) => {
1876
- await logger.logLevel(levelName, message, data);
1877
- };
1955
+ for (const levelName of Object.keys(config.levelOptions.levels)) if (!mutableLogger[levelName]) mutableLogger[levelName] = (message, data) => logger.logLevel(levelName, message, data);
1878
1956
  }
1879
1957
  return logger;
1880
1958
  }
@@ -2103,4 +2181,4 @@ var ErrorResponseBuilder = class {
2103
2181
  };
2104
2182
  export { DEFAULT_LOG_COLORS as C, DEFAULT_CONFIG as S, LogLevel as T, serializeError as _, isLogixiaException as a, globalPluginRegistry as b, _shutdownRegistrySize as c, registerForShutdown as d, resetShutdownHandlers as f, normalizeError as g, isError as h, LogixiaException as i, deregisterFromShutdown as l, redactObject as m, generateRequestId as n, LogixiaLogger as o, applyRedaction as p, generateTraceId as r, createLogger as s, ErrorResponseBuilder as t, flushOnExit as u, serializeNestedErrors as v, DEFAULT_LOG_LEVELS as w, usePlugin as x, PluginRegistry as y };
2105
2183
 
2106
- //# sourceMappingURL=builder-DKL7WcIN.mjs.map
2184
+ //# sourceMappingURL=builder-BlIJNIuk.mjs.map