UNPKG

@analog-tools/logger

Version:

Logging utility for AnalogJS applications

1,238 lines (1,237 loc) 36.6 kB
var f = /* @__PURE__ */ ((s) => (s.SkyBlue = "\x1B[94m", s.OceanBlue = "\x1B[34m", s.MidnightBlue = "\x1B[38;5;17m", s.SkyBlueBg = "\x1B[104m", s.OceanBlueBg = "\x1B[44m", s.MidnightBlueBg = "\x1B[48;5;17m", s.MintGreen = "\x1B[92m", s.ForestGreen = "\x1B[32m", s.EmeraldGreen = "\x1B[38;5;28m", s.MintGreenBg = "\x1B[102m", s.ForestGreenBg = "\x1B[42m", s.EmeraldGreenBg = "\x1B[48;5;28m", s.LemonYellow = "\x1B[93m", s.SunflowerYellow = "\x1B[33m", s.GoldYellow = "\x1B[38;5;220m", s.LemonYellowBg = "\x1B[103m", s.SunflowerYellowBg = "\x1B[43m", s.GoldYellowBg = "\x1B[48;5;220m", s.RoseRed = "\x1B[91m", s.FireRed = "\x1B[31m", s.BurgundyRed = "\x1B[38;5;88m", s.RoseRedBg = "\x1B[101m", s.FireRedBg = "\x1B[41m", s.BurgundyRedBg = "\x1B[48;5;88m", s.LavenderPurple = "\x1B[95m", s.RoyalPurple = "\x1B[38;5;93m", s.DeepPurple = "\x1B[38;5;54m", s.LavenderPurpleBg = "\x1B[105m", s.RoyalPurpleBg = "\x1B[48;5;93m", s.DeepPurpleBg = "\x1B[48;5;54m", s.PeachOrange = "\x1B[38;5;215m", s.TangerineOrange = "\x1B[38;5;208m", s.AmberOrange = "\x1B[38;5;214m", s.PeachOrangeBg = "\x1B[48;5;215m", s.TangerineOrangeBg = "\x1B[48;5;208m", s.AmberOrangeBg = "\x1B[48;5;214m", s.SilverGray = "\x1B[37m", s.SlateGray = "\x1B[90m", s.CharcoalGray = "\x1B[38;5;238m", s.SilverGrayBg = "\x1B[47m", s.SlateGrayBg = "\x1B[100m", s.CharcoalGrayBg = "\x1B[48;5;238m", s.PureBlack = "\x1B[30m", s.PureWhite = "\x1B[97m", s.PureBlackBg = "\x1B[40m", s.PureWhiteBg = "\x1B[107m", s.Cyan = "\x1B[36m", s.Reset = "\x1B[0m", s.Bold = "\x1B[1m", s.Dim = "\x1B[2m", s.Underline = "\x1B[4m", s.Blink = "\x1B[5m", s.Reverse = "\x1B[7m", s.Hidden = "\x1B[8m", s))(f || {}), o = /* @__PURE__ */ ((s) => (s[s.trace = 0] = "trace", s[s.debug = 1] = "debug", s[s.info = 2] = "info", s[s.warn = 3] = "warn", s[s.error = 4] = "error", s[s.fatal = 5] = "fatal", s[s.silent = 6] = "silent", s))(o || {}); function x(s) { return ["trace", "debug", "info", "warn", "error", "fatal", "silent"].includes(s); } const u = class u { /** * Generate a cache key for memoization (includes serialization options) * @private */ static getCacheKey(e, t, i, r) { if (!e.stack) return null; const n = e.stack.split(` `)[0] || ""; return `${e.name}:${e.message}:${n}:${t}:${i}:${r}`; } /** * Add to cache with size limit enforcement * @private */ static addToCache(e, t) { if (this.serializationCache.size >= this.MAX_CACHE_SIZE) { const i = this.serializationCache.keys().next().value; i && this.serializationCache.delete(i); } this.serializationCache.set(e, t); } /** * Safely serialize any value (Error objects, plain objects, primitives) for logging * * @param error - The value to serialize (Error, object, string, etc.) * @param options - Configuration options for serialization behavior * @returns Serialized representation - StructuredError for Error objects, string for others * * @example * ```typescript * // Serialize an Error object * const error = new Error('Failed to connect'); * const result = ErrorSerializer.serialize(error); * // Returns: { message: 'Failed to connect', name: 'Error', stack: '...' } * * // Serialize a plain object * const obj = { userId: '123', action: 'login' }; * const result = ErrorSerializer.serialize(obj); * // Returns: '{\n "userId": "123",\n "action": "login"\n}' * * // Serialize with options * const result = ErrorSerializer.serialize(error, { includeStack: false }); * ``` */ static serialize(e, t = {}) { const { includeStack: i = !0, maxDepth: r = u.DEFAULT_MAX_DEPTH, includeNonEnumerable: n = !1 } = t; if (e instanceof Error) { const c = this.getCacheKey(e, i, r, n); if (c) { const h = this.serializationCache.get(c); if (h) return h; } const l = this.serializeError(e, i, r, n, /* @__PURE__ */ new WeakSet()); return c && this.addToCache(c, l), l; } if (typeof e == "string") return e; const a = this.safeStringify(e, r, /* @__PURE__ */ new WeakSet()); if (typeof a == "string") return a; try { return JSON.stringify(a, null, 2); } catch { return String(a); } } /** * Serialize a standard Error object * @private */ static serializeError(e, t, i, r, n = /* @__PURE__ */ new WeakSet()) { if (n.has(e)) return { message: e.message, name: e.name, [Symbol.for("circular")]: this.CIRCULAR_REF_PLACEHOLDER }; n.add(e); const a = { message: e.message, name: e.name }; return t && e.stack && (a.stack = e.stack), "cause" in e && e.cause !== void 0 && (e.cause instanceof Error ? a.cause = this.serializeError(e.cause, t, i - 1, r, n) : a.cause = this.safeStringify(e.cause, i - 1, n)), Object.keys(e).forEach((c) => { if (!(c in a)) try { const l = e; a[c] = this.safeStringify(l[c], i - 1, n); } catch { a[c] = this.UNABLE_TO_SERIALIZE; } }), r && Object.getOwnPropertyNames(e).forEach((c) => { if (!(c in a) && c !== "stack" && c !== "message" && c !== "name") try { const l = Object.getOwnPropertyDescriptor(e, c); if (l && l.enumerable === !1) { const h = e; a[c] = this.safeStringify(h[c], i - 1, n); } } catch { a[c] = this.UNABLE_TO_SERIALIZE; } }), a; } /** * Safely stringify any object with circular reference detection * @private */ static safeStringify(e, t, i = /* @__PURE__ */ new WeakSet()) { if (t <= 0) return this.MAX_DEPTH_PLACEHOLDER; if (e === null || typeof e != "object") return e; if (i.has(e)) return this.CIRCULAR_REF_PLACEHOLDER; i.add(e); try { if (Array.isArray(e)) { const n = e.map((a) => this.safeStringify(a, t - 1, i)); return i.delete(e), n; } const r = {}; for (const [n, a] of Object.entries(e)) try { r[n] = this.safeStringify(a, t - 1, i); } catch { r[n] = this.UNABLE_TO_SERIALIZE; } return i.delete(e), r; } catch { return i.delete(e), this.UNABLE_TO_SERIALIZE; } } }; u.DEFAULT_MAX_DEPTH = 10, u.CIRCULAR_REF_PLACEHOLDER = "[Circular Reference]", u.MAX_DEPTH_PLACEHOLDER = "[Max Depth Reached]", u.UNABLE_TO_SERIALIZE = "[Unable to serialize]", u.serializationCache = /* @__PURE__ */ new Map(), u.MAX_CACHE_SIZE = 100; let m = u; const A = { highlight: { color: f.LemonYellow, bold: !0 }, accent: { color: f.SkyBlue }, attention: { color: f.RoyalPurple, bold: !0 }, success: { color: f.ForestGreen }, warning: { color: f.TangerineOrange }, error: { color: f.FireRed }, info: { color: f.OceanBlue }, debug: { color: f.SlateGray } }, E = { success: "✅", warning: "⚠️", error: "❌", info: "ℹ️", debug: "🐞" }, $ = { trace: f.SlateGray, // Gray for trace (lowest level) debug: f.Cyan, // Cyan for debug info info: f.ForestGreen, // Green for general info warn: f.SunflowerYellow, // Standard yellow for warnings error: f.FireRed, // Red for errors fatal: f.FireRed, // Bold + standard red for fatal errors silent: f.Reset // Reset for silent }, L = class L { /** * Create a new LoggerStyleEngine * @param config Configuration for the style engine */ constructor(e = {}) { this.styleCache = /* @__PURE__ */ new Map(); const t = process.env.NODE_ENV === "test" || process.env.VITEST === "true"; this.useColors = e.useColors !== void 0 ? e.useColors : !t, this.globalStyles = { ...A, ...e.styles }, this.globalIcons = { ...E, ...e.icons }; } /** * Public method to resolve a style definition and return the ANSI color code (with caching) * @param style Style definition (semantic name or custom config) * @returns ANSI color code or undefined if invalid */ resolveStyle(e, t = "test", i) { return this.applyStyle(e, t, i); } /** * Enable or disable colored output * @param enabled Whether colors should be enabled */ setUseColors(e) { this.useColors = e; } /** * Check if colors are enabled * @returns Whether colors are enabled */ getUseColors() { return this.useColors; } /** * Update style and icon configuration * @param styles Partial style configuration to merge * @param icons Partial icon configuration to merge */ updateStyleConfig(e, t) { this.globalStyles = { ...this.globalStyles, ...e }, this.globalIcons = { ...this.globalIcons, ...t }; } /** * Format a log message with color and proper prefix * @param level Log level for the message * @param message The message to format * @param loggerName The name of the logger * @param context Optional context for the logger * @param overrideColor Optional color override for the message * @returns Formatted message with color */ formatMessage(e, t, i, r, n) { const a = r ? `[${i}:${r}]` : `[${i}]`; if (this.useColors) { let c = this.getColorForLevel(e); return n && (c = n), `${c}${a} ${t}${f.Reset}`; } else return `${a} ${t}`; } /** * Format message with metadata-based styling and icons * @param level Log level * @param message Message to format * @param loggerName The name of the logger * @param styling Optional metadata for styling * @param context Optional context for the logger * @returns Formatted message with styling and icons */ formatMessageWithMetadata(e, t, i, r, n) { let a = t, c = this.getColorForLevel(e); if (r?.style) { const l = this.applyStyle(r.style, i, n); l && (c = l); } return r?.icon && (a = `${this.resolveIcon(r.icon, i, n)} ${t}`), this.formatMessage(e, a, i, n, c); } /** * Parse metadata parameter to separate metadata from additional data * @param metadataOrData Could be LogStyling or additional data * @param data Additional data parameters * @returns Parsed metadata and remaining data */ parseMetadataParameter(e, t = []) { if (e && typeof e == "object" && !Array.isArray(e) && ("style" in e || "icon" in e)) return { metadata: e, restData: t }; const i = e !== void 0 ? [e, ...t] : t; if (i.length > 0) { const r = i[i.length - 1]; if (r && typeof r == "object" && !Array.isArray(r) && ("style" in r || "icon" in r)) return { metadata: r, restData: i.slice(0, -1) // Return all but the last parameter }; } return { metadata: void 0, restData: i }; } /** * Get color for a specific log level * @param level The log level * @returns ANSI color code for the log level * @private */ getColorForLevel(e) { const t = o[e], i = $[t]; return e === o.fatal ? `${f.Bold}${i}` : i || f.Reset; } /** * Apply style configuration and return ANSI color code * @param style Style configuration (semantic name or custom config) * @param loggerName Logger name for error messages * @param context Optional context for error messages * @returns ANSI color code or undefined if invalid * @private */ applyStyle(e, t, i) { const r = this.getStyleCacheValue(e); if (r !== void 0) return r; if (typeof e == "string") { const n = this.getSemanticStyleColor(e, t, i); return this.setStyleCache(e, n), n; } if (typeof e == "object" && "color" in e) { if (!this.isValidColor(e.color)) { this.setStyleCache(e, void 0), this.logWarning("Invalid color provided. Only predefined ColorEnum values are allowed.", t, i); return; } const n = this.constructStyleCode(e); return this.setStyleCache(e, n), n; } this.setStyleCache(e, void 0), this.logWarning("Unknown style configuration provided. Only semantic style names or valid ColorEnum objects are allowed.", t, i); } // Helper: Validate color isValidColor(e) { return Object.values(f).includes(e); } // Helper: Validate icon isValidIcon(e) { return this.isEmojiIcon(e); } // Helper: Memoization get/set getStyleCacheValue(e) { return this.styleCache.has(e) ? this.styleCache.get(e) : void 0; } setStyleCache(e, t) { this.styleCache.set(e, t); } // Helper: Error logging logWarning(e, t, i) { const r = i ? `${t}:${i}` : t; console.warn(`[${r}] ${e}`); } // Helper: Style code construction constructStyleCode(e) { let t = e.color.toString(); return e.bold && (t += f.Bold), e.underline && (t += f.Underline), t; } /** * Get ANSI color code for semantic style names * @param styleName Semantic style name * @param loggerName Logger name for error messages * @param context Optional context for error messages * @returns ANSI color code or undefined if unknown * @private */ getSemanticStyleColor(e, t, i) { if (this.styleCache.has(e)) return this.styleCache.get(e); const r = this.globalStyles[e]; if (r) { let a = r.color.toString(); return r.bold && (a += f.Bold), r.underline && (a += f.Underline), this.styleCache.set(e, a), a; } const n = i ? `${t}:${i}` : t; console.warn( `[${n}] Unknown semantic style: ${e}. Falling back to default.` ), this.styleCache.set(e, void 0); } /** * Expose style cache for diagnostics/testing */ getStyleCache() { return this.styleCache; } /** * Resolve icon to emoji character * @param icon Icon name or custom string * @param loggerName Logger name for error messages * @param context Optional context for error messages * @returns Emoji character * @private */ resolveIcon(e, t, i) { if (this.isValidIcon(e)) return e; const r = [ "success", "warning", "error", "info", "debug" ], n = r.find((a) => a === e); return n && this.globalIcons[n] ? this.globalIcons[n] : (r.includes(e) ? this.logWarning(`Unknown icon: ${e}. Expected a valid emoji or semantic icon name.`, t, i) : this.logWarning(`Invalid icon: ${e}. Expected a valid emoji or semantic icon name.`, t, i), e); } /** * Check if the provided icon is a valid emoji from our Icon type * @param icon Icon to check * @returns True if it's a valid emoji icon * @private */ isEmojiIcon(e) { return [ "✅", "⚠️", "❌", "ℹ️", "🐞", "⭐️", "🚀", "🔥", "✔️", "✖️", "❓", "🔒", "🔓", "⏳", "🕒", "⬆️", "⬇️", "➡️", "⬅️", "📁", "📄", "👤", "👥", "✏️", "➕", "➖", "🔔", "⚡️", "🎁", "🐛", "🌟", "❤️", "👀", "⚙️", "🔧", "🔨", "🔑", "🎉", "📝", "🚨", "📅", "💡", "🔍", "🔗", "🔖", "📌", "📎", "✉️", "📞", "🌍", "☁️", "🌈", "🌙", "☀️", "❄️", "✨", "🎵", "📷", "🎥", "🎤", "🔊", "🔋", "🗑️", "💰", "💳", "🎂", "🏅", "🏆", "👑", "🛸", "🛡️", "🛑", "▶️", "⏸️", "⏺️", "⏪", "⏩", "🔁", "🔀", "🎲", "🎈", "🍪", "☕️", "🍵", "🍺", "🍷", "🍕", "🍔", "🍟", "🍎", "🍌", "🍒", "🍋", "🥕", "🌽", "🥦", "🥚", "🧀", "🍞", "🍰", "🍦", "🍫", "🍿", "🥓", "🍤", "🐟", "🦀", "🐙", "🐋", "🐬", "🐧", "🐸", "🐢", "🐍", "🐉", "🦄", "🐱", "🐶", "🐭", "🐰", "🐻", "🐼", "🐨", "🐯", "🦁", "🐒", "🐘", "🐎", "🐄", "🐖", "🐑", "🐔", "🦆", "🦢", "🦉", "🦅", "🦜", "🦚", "🦩", "🦋", "🐝", "🐜", "🐞", "🕷️", "🦂", "🐌", "🪱", "🐛", "🦗", "🦟", "🪰", "🪳", "🪲" ].includes(e); } }; L.INJECTABLE = !0; let M = L; class B extends Error { constructor(e) { super(e), this.name = "LoggerError"; } } class W extends Error { constructor(e) { super(e), this.name = "LoggerContextError"; } } const S = { enabled: !1, windowMs: 5e3, // 5 seconds flushOnCritical: !0 }, P = [o.error, o.fatal]; class D { constructor(e, t) { this.config = e, this.formatMessage = t, this.entries = /* @__PURE__ */ new Map(); } /** * Add a message to the deduplicator */ addMessage(e, t, i = "") { if (!this.config.enabled || this.config.flushOnCritical && P.includes(e)) return !0; const r = this.generateFingerprint(t, e, i), n = this.entries.get(r); return n ? n.count++ : this.entries.set(r, { message: t, level: e, context: i, firstSeen: Date.now(), count: 1 }), this.scheduleFlush(), !1; } /** * Flush all pending messages immediately */ flush() { if (this.entries.size !== 0) { this.flushTimer && (clearTimeout(this.flushTimer), this.flushTimer = void 0); for (const e of this.entries.values()) this.outputMessage(e); this.entries.clear(); } } /** * Destroy the deduplicator and clean up resources */ destroy() { this.flushTimer && (clearTimeout(this.flushTimer), this.flushTimer = void 0), this.entries.clear(); } /** * Generate a fingerprint for message deduplication */ generateFingerprint(e, t, i) { return `${t}:${i}:${e}`; } /** * Schedule a flush after the configured window */ scheduleFlush() { this.flushTimer || (this.flushTimer = setTimeout(() => { this.flush(); }, this.config.windowMs)); } /** * Output a batched message with count if needed */ outputMessage(e) { let t = e.message; e.count > 1 && (t = `${t} (×${e.count})`); const i = this.formatMessage(e.level, t, e.context); switch (e.level) { case o.trace: console.trace(i); break; case o.debug: console.debug(i); break; case o.info: console.info(i); break; case o.warn: console.warn(i); break; case o.error: console.error(i); break; case o.fatal: console.error(i); break; } } } const v = class v { /** * Create a new LoggerService * @param config The logger configuration * @param styleEngine Optional style engine (will be created if not provided) * @param context Optional context for child logger * @param parent Optional parent logger for child logger */ constructor(e = {}, t, i, r) { if (this.config = e, this.childLoggers = {}, this.disabledContexts = [], this.activeGroups = [], r) this.parentLogger = r, this.name = r.name, this.context = i, this.logLevel = r.getLogLevel(), this.styleEngine = r.styleEngine; else { if (typeof e.level == "string" && !Object.keys(o).includes(e.level)) throw new B(`Invalid log level: ${e.level}`); if (this.logLevel = this.castLoglevel( e.level || process.env.LOG_LEVEL || "info" ), this.name = e.name || "analog-tools", this.setDisabledContexts( e.disabledContexts ?? process.env.LOG_DISABLED_CONTEXTS?.split(",") ?? [] ), this.styleEngine = t || new M({ useColors: e.useColors, styles: { ...A, ...e.styles }, icons: { ...E, ...e.icons } }), e.deduplication?.enabled) { const n = { enabled: !0, windowMs: e.deduplication.windowMs ?? S.windowMs, flushOnCritical: e.deduplication.flushOnCritical ?? S.flushOnCritical }, a = (c, l, h) => this.styleEngine.formatMessage(c, l, this.name, h); this.deduplicator = new D(n, a); } } } /** * Check if this logger's context is enabled * Always returns true for root logger */ isContextEnabled() { return this.context ? !(this.parentLogger || this).disabledContexts.includes(this.context) : !0; } /** * Set disabled contexts for this logger * @param contexts Array of context names to disable */ setDisabledContexts(e) { this.disabledContexts = e; } /** * Get the current log level * @returns The current log level */ getLogLevel() { return this.logLevel; } /** * Get disabled contexts * @returns Array of disabled context names */ getDisabledContexts() { return (this.parentLogger || this).disabledContexts || []; } /** * Enable or disable colored output * @param enabled Whether colors should be enabled */ setUseColors(e) { (this.parentLogger || this).styleEngine.setUseColors(e); } /** * Check if colors are enabled * @returns Whether colors are enabled */ getUseColors() { return (this.parentLogger || this).styleEngine.getUseColors(); } /** * Create a child logger with a specific context * @param context The context for the child logger * @returns A new logger instance with the given context */ forContext(e) { return this.childLoggers[e] || (this.childLoggers[e] = new v({}, void 0, e, this)), this.childLoggers[e]; } /** * Start a new log group with the given name * All subsequent log messages will be indented * @param groupName The name of the group */ group(e) { if (!this.isContextEnabled()) return; (this.parentLogger?.activeGroups || this.activeGroups).push(e); const i = this.styleEngine.formatMessage( o.info, `Group: ${e}`, this.name, this.context ); console.group(`${i} ▼`); } /** * End a log group with the given name * Subsequent log messages will no longer be indented * @param groupName The name of the group (optional, will end the most recent group if not provided) */ groupEnd(e) { if (!this.isContextEnabled()) return; const t = this.parentLogger?.activeGroups || this.activeGroups; if (e) { const i = t.lastIndexOf(e); if (i !== -1) { const r = t.splice(i); for (let n = 0; n < r.length; n++) console.groupEnd(), console.log(""); } } else t.length > 0 && (t.pop(), console.groupEnd(), console.log("")); } trace(e, t, ...i) { if (!this.isContextEnabled() || this.logLevel > o.trace) return; const { metadata: r, restData: n } = this.styleEngine.parseMetadataParameter( t, i ); if (this.handleDeduplication(o.trace, e, r, n)) if (r) { const a = this.styleEngine.formatMessageWithMetadata( o.trace, e, this.name, r, this.context ); console.trace(a, ...n || []); } else { const a = this.styleEngine.formatMessage( o.trace, e, this.name, this.context ); console.trace(a, ...n || []); } } debug(e, t, ...i) { if (!this.isContextEnabled() || this.logLevel > o.debug) return; const { metadata: r, restData: n } = this.styleEngine.parseMetadataParameter( t, i ); if (this.handleDeduplication(o.debug, e, r, n)) if (r) { const a = this.styleEngine.formatMessageWithMetadata( o.debug, e, this.name, r, this.context ); console.debug(a, ...n || []); } else { const a = this.styleEngine.formatMessage( o.debug, e, this.name, this.context ); console.debug(a, ...n || []); } } info(e, t, ...i) { if (!this.isContextEnabled() || this.logLevel > o.info) return; const { metadata: r, restData: n } = this.styleEngine.parseMetadataParameter( t, i ); if (this.handleDeduplication(o.info, e, r, n)) if (r) { const a = this.styleEngine.formatMessageWithMetadata( o.info, e, this.name, r, this.context ); console.info(a, ...n || []); } else { const a = this.styleEngine.formatMessage( o.info, e, this.name, this.context ); console.info(a, ...n || []); } } warn(e, t, ...i) { if (!this.isContextEnabled() || this.logLevel > o.warn) return; const { metadata: r, restData: n } = this.styleEngine.parseMetadataParameter( t, i ); if (this.handleDeduplication(o.warn, e, r, n)) if (r) { const a = this.styleEngine.formatMessageWithMetadata( o.warn, e, this.name, r, this.context ); console.warn(a, ...n || []); } else { const a = this.styleEngine.formatMessage( o.warn, e, this.name, this.context ); console.warn(a, ...n || []); } } error(e, t, i, ...r) { if (!this.isContextEnabled() || this.logLevel > o.error) return; const { message: n, serializedError: a, context: c, data: l } = this.parseErrorParameters( e, t, i, r ), h = l || []; let p, g = h; if (h.length > 0) { const d = h[h.length - 1]; d && typeof d == "object" && !Array.isArray(d) && ("style" in d || "icon" in d) && (p = d, g = h.slice(0, -1)); } const y = [this.styleEngine.formatMessageWithMetadata( o.error, n, this.name, p, this.context )]; a && y.push(a), c && y.push(c), console.error(...y, ...g); } fatal(e, t, i, ...r) { if (!this.isContextEnabled() || this.logLevel > o.fatal) return; if (typeof e == "string" && t && typeof t == "object" && !Array.isArray(t) && ("style" in t || "icon" in t)) { const d = t, T = this.styleEngine.formatMessageWithMetadata( o.fatal, `FATAL: ${e}`, this.name, d, this.context ); console.error(T); return; } const { message: n, serializedError: a, context: c, data: l } = this.parseErrorParameters( e, t, i, r ), h = l || []; let p, g = h; if (h.length > 0) { const d = h[h.length - 1]; d && typeof d == "object" && !Array.isArray(d) && ("style" in d || "icon" in d) && (p = d, g = h.slice(0, -1)); } const y = [this.styleEngine.formatMessageWithMetadata( o.fatal, `FATAL: ${n}`, this.name, p, this.context )]; a && y.push(a), c && y.push(c), console.error(...y, ...g); } /** * Parse and normalize error method parameters to support multiple overloads * * Handles various parameter combinations: * - error(Error) -> extracts message and serializes error * - error(message) -> uses message as-is * - error(message, Error) -> uses message and serializes error * - error(message, LogContext) -> uses message and context * - error(message, Error, LogContext) -> uses all three with proper typing * - error(message, ...data) -> backwards compatibility mode * * @private * @param messageOrError - String message or Error object * @param errorOrContext - Error object, LogContext, or additional data * @param contextOrData - LogContext or additional data * @param additionalData - Extra data for backwards compatibility * @returns Parsed parameters with normalized structure */ parseErrorParameters(e, t, i, r = []) { if (e instanceof Error && t === void 0) return { message: e.message, serializedError: m.serialize(e), data: [] }; if (typeof e == "string" && t === void 0) return { message: e, data: [] }; if (typeof e == "string" && t instanceof Error && i === void 0) return { message: e, serializedError: m.serialize(t), data: [] }; if (typeof e == "string" && this.isLogContext(t) && i === void 0) return { message: e, context: t, data: [] }; if (typeof e == "string" && t instanceof Error && this.isLogContext(i)) return { message: e, serializedError: m.serialize(t), context: i, data: r }; const n = typeof e == "string" ? e : "Unknown error", a = t ? m.serialize(t) : void 0, c = i !== void 0 ? [i, ...r] : r; return { message: n, serializedError: a, data: c }; } /** * Type guard to check if an object is LogMetadata * * LogMetadata is defined as a plain object that is not: * - null or undefined * - An Array * - An Error instance * - A Date instance * - A RegExp instance * - A Function * * @private * @param obj - Object to check * @returns True if object matches LogMetadata interface */ isLogContext(e) { return typeof e == "object" && e !== null && !Array.isArray(e) && !(e instanceof Error) && !(e instanceof Date) && !(e instanceof RegExp) && typeof e != "function"; } /** * Cast a string to a LogLevel, with runtime validation and warning for invalid levels * @param level String representation of log level * @returns Numeric LogLevel * @private */ castLoglevel(e) { if (x(e)) switch (e) { case "trace": return o.trace; case "debug": return o.debug; case "info": return o.info; case "warn": return o.warn; case "error": return o.error; case "fatal": return o.fatal; case "silent": return o.silent; } const t = e.toLowerCase(); throw x(t) ? new B( `Invalid log level: ${e}. Log levels are case-sensitive. Valid levels: trace, debug, info, warn, error, fatal, silent.` ) : new B( `Invalid log level: ${e}. Valid levels: trace, debug, info, warn, error, fatal, silent.` ); } /** * Safely format a message using the style engine * Catches and logs any errors from the style engine * @param args Arguments for the style engine formatMessage method * @returns The formatted message * @private */ safeFormatMessage(e, t, i, r = "", n = "") { try { return this.styleEngine.formatMessage(e, t, i, r, n); } catch (a) { throw this.error("Style engine failure", a), new B("Style engine failure"); } } /** * Handle deduplication for a log message * @param level Log level * @param message Message text * @returns True if message should be logged immediately, false if batched * @private */ shouldLogImmediately(e, t) { const r = (this.parentLogger || this).deduplicator; if (!r) return !0; try { return r.addMessage(e, t, this.context); } catch (n) { return console.error("Logger deduplication error:", n), !0; } } /** * Deduplication handler for all log methods * Returns true if the message should be logged immediately, false if batched */ handleDeduplication(e, t, i, r) { return !i && r.length === 0 ? this.shouldLogImmediately(e, t) : !0; } }; v.INJECTABLE = !0; let w = v; function R(s) { if (typeof s == "function") return s.__is_handler__ = !0, s; const e = { onRequest: I(s.onRequest), onBeforeResponse: I(s.onBeforeResponse) }, t = (i) => F(i, s.handler, e); return t.__is_handler__ = !0, t.__resolve__ = s.handler.__resolve__, t.__websocket__ = s.websocket, t; } function I(s) { return s ? Array.isArray(s) ? s : [s] : void 0; } async function F(s, e, t) { if (t.onRequest) { for (const n of t.onRequest) if (await n(s), s.handled) return; } const r = { body: await e(s) }; if (t.onBeforeResponse) for (const n of t.onBeforeResponse) await n(s, r); return r.body; } let b = null; function U() { return b || (b = new j()), b; } class j { constructor() { this.serviceMap = /* @__PURE__ */ new Map(); } /** * Register a service with a token * @param token - The injection token for the service * @param properties - The constructor parameters for the service class */ register(e, ...t) { if (this.isServiceInjectable(e) && (!this.hasService(e) || this.getService(e) === void 0)) { if (t === void 0 || t.length === 0) { this.serviceMap.set(this.getInjcectableName(e), new e()); return; } this.serviceMap.set( this.getInjcectableName(e), new e(...t) ); } } registerAsUndefined(e) { if (this.isServiceInjectable(e)) this.serviceMap.set(this.getInjcectableName(e), void 0); else throw new Error( `Service with token ${this.getInjcectableName( e )} is not injectable. Ensure it has the INJECTABLE static property set to true.` ); } registerCustomServiceInstance(e, t) { if (this.isServiceInjectable(e)) this.serviceMap.set(this.getInjcectableName(e), t); else throw new Error( `Service with token ${this.getInjcectableName( e )} is not injectable. Ensure it has the INJECTABLE static property set to true.` ); } /** * Get a service by its token * @param token - The injection token for the service * @returns The requested service or undefined if not found */ getService(e) { if (this.isServiceInjectable(e)) return this.hasService(e) || this.register(e), this.serviceMap.get(this.getInjcectableName(e)); } /** * Check if a service is registered * @param token - The injection token for the service * @returns True if the service is registered */ hasService(e) { return this.serviceMap.has(this.getInjcectableName(e)); } /** * Check if a service class is injectable * @param token - The injection token for the service * @returns True if the service has the INJECTABLE static property set to true */ isServiceInjectable(e) { const t = e.INJECTABLE; return t !== void 0 && t !== !1; } getInjcectableName(e) { if (this.isServiceInjectable(e)) { const t = e.INJECTABLE; return typeof t != "string" ? e.name : t; } throw new Error( `Service with token ${e.name} is not injectable. Ensure it has the INJECTABLE static property set to true.` ); } /** * Clean up all services when the game is being destroyed */ destroy() { this.serviceMap.clear(); } } function G(s, e, t = {}) { const { required: i = !0 } = t, r = s.getService(e); if (!r && i) throw new Error( `Service with token ${e || "unknown"} not found in registry` ); return r; } function C(s, e = {}) { return G(U(), s, e); } function z(s = "api") { return R((e) => { const t = C(w).forContext(s); e.context.logger = t, t.debug("Request received", { method: e.method, path: e.path, requestId: e.context.id }); }); } function H(s, e = {}) { const { namespace: t = "api", level: i = "debug", logResponse: r = !1 } = e; return R(async (n) => { const a = C(w).forContext(t), c = Date.now(); try { const l = await s(n), h = Date.now() - c, g = { trace: "trace", debug: "debug", info: "info", warn: "warn", error: "error", fatal: "fatal", silent: void 0 // do not log for silent }[i]; return g && typeof a[g] == "function" && a[g]( `Request completed in ${h}ms`, { method: n.method, path: n.path, duration: h, ...r && l ? { response: l } : {} } ), l; } catch (l) { const h = Date.now() - c; throw a.error(`Request failed after ${h}ms`, l, { method: n.method, path: n.path, duration: h }), l; } }); } export { P as CRITICAL_LEVELS, f as ColorEnum, S as DEFAULT_DEDUPLICATION_CONFIG, m as ErrorSerializer, D as LogDeduplicator, o as LogLevelEnum, W as LoggerContextError, B as LoggerError, w as LoggerService, M as LoggerStyleEngine, z as createLoggerMiddleware, x as isValidLogLevel, H as withLogging };