@analog-tools/logger
Version:
Logging utility for AnalogJS applications
1,241 lines (1,240 loc) • 36.8 kB
JavaScript
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 a = e.stack.split(`
`)[0] || "";
return `${e.name}:${e.message}:${a}:${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: a = !1
} = t;
if (e instanceof Error) {
const c = this.getCacheKey(e, i, r, a);
if (c) {
const h = this.serializationCache.get(c);
if (h)
return h;
}
const l = this.serializeError(e, i, r, a, /* @__PURE__ */ new WeakSet());
return c && this.addToCache(c, l), l;
}
if (typeof e == "string")
return e;
const n = this.safeStringify(e, r, /* @__PURE__ */ new WeakSet());
if (typeof n == "string")
return n;
try {
return JSON.stringify(n, null, 2);
} catch {
return String(n);
}
}
/**
* Serialize a standard Error object
* @private
*/
static serializeError(e, t, i, r, a = /* @__PURE__ */ new WeakSet()) {
if (a.has(e))
return { message: e.message, name: e.name, [Symbol.for("circular")]: this.CIRCULAR_REF_PLACEHOLDER };
a.add(e);
const n = {
message: e.message,
name: e.name
};
return t && e.stack && (n.stack = e.stack), "cause" in e && e.cause !== void 0 && (e.cause instanceof Error ? n.cause = this.serializeError(e.cause, t, i - 1, r, a) : n.cause = this.safeStringify(e.cause, i - 1, a)), Object.keys(e).forEach((c) => {
if (!(c in n))
try {
const l = e;
n[c] = this.safeStringify(l[c], i - 1, a);
} catch {
n[c] = this.UNABLE_TO_SERIALIZE;
}
}), r && Object.getOwnPropertyNames(e).forEach((c) => {
if (!(c in n) && c !== "stack" && c !== "message" && c !== "name")
try {
const l = Object.getOwnPropertyDescriptor(e, c);
if (l && l.enumerable === !1) {
const h = e;
n[c] = this.safeStringify(h[c], i - 1, a);
}
} catch {
n[c] = this.UNABLE_TO_SERIALIZE;
}
}), n;
}
/**
* 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 a = e.map((n) => this.safeStringify(n, t - 1, i));
return i.delete(e), a;
}
const r = {};
for (const [a, n] of Object.entries(e))
try {
r[a] = this.safeStringify(n, t - 1, i);
} catch {
r[a] = 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, a) {
const n = r ? `[${i}:${r}]` : `[${i}]`;
if (this.useColors) {
let c = this.getColorForLevel(e);
return a && (c = a), `${c}${n} ${t}${f.Reset}`;
} else
return `${n} ${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, a) {
let n = t, c = this.getColorForLevel(e);
if (r != null && r.style) {
const l = this.applyStyle(r.style, i, a);
l && (c = l);
}
return r != null && r.icon && (n = `${this.resolveIcon(r.icon, i, a)} ${t}`), this.formatMessage(e, n, i, a, 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 a = this.getSemanticStyleColor(e, t, i);
return this.setStyleCache(e, a), a;
}
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 a = this.constructStyleCode(e);
return this.setStyleCache(e, a), a;
}
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 n = r.color.toString();
return r.bold && (n += f.Bold), r.underline && (n += f.Underline), this.styleCache.set(e, n), n;
}
const a = i ? `${t}:${i}` : t;
console.warn(
`[${a}] 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"
], a = r.find((n) => n === e);
return a && this.globalIcons[a] ? this.globalIcons[a] : (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), a = this.entries.get(r);
return a ? a.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) {
var a, n;
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 ?? ((a = process.env.LOG_DISABLED_CONTEXTS) == null ? void 0 : a.split(",")) ?? []
), this.styleEngine = t || new M({
useColors: e.useColors,
styles: { ...A, ...e.styles },
icons: { ...E, ...e.icons }
}), (n = e.deduplication) != null && n.enabled) {
const c = {
enabled: !0,
windowMs: e.deduplication.windowMs ?? S.windowMs,
flushOnCritical: e.deduplication.flushOnCritical ?? S.flushOnCritical
}, l = (h, p, g) => this.styleEngine.formatMessage(h, p, this.name, g);
this.deduplicator = new D(c, l);
}
}
}
/**
* 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) {
var r;
if (!this.isContextEnabled()) return;
(((r = this.parentLogger) == null ? void 0 : r.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) {
var i;
if (!this.isContextEnabled()) return;
const t = ((i = this.parentLogger) == null ? void 0 : i.activeGroups) || this.activeGroups;
if (e) {
const r = t.lastIndexOf(e);
if (r !== -1) {
const a = t.splice(r);
for (let n = 0; n < a.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: a } = this.styleEngine.parseMetadataParameter(
t,
i
);
if (this.handleDeduplication(o.trace, e, r, a))
if (r) {
const n = this.styleEngine.formatMessageWithMetadata(
o.trace,
e,
this.name,
r,
this.context
);
console.trace(n, ...a || []);
} else {
const n = this.styleEngine.formatMessage(
o.trace,
e,
this.name,
this.context
);
console.trace(n, ...a || []);
}
}
debug(e, t, ...i) {
if (!this.isContextEnabled() || this.logLevel > o.debug) return;
const { metadata: r, restData: a } = this.styleEngine.parseMetadataParameter(
t,
i
);
if (this.handleDeduplication(o.debug, e, r, a))
if (r) {
const n = this.styleEngine.formatMessageWithMetadata(
o.debug,
e,
this.name,
r,
this.context
);
console.debug(n, ...a || []);
} else {
const n = this.styleEngine.formatMessage(
o.debug,
e,
this.name,
this.context
);
console.debug(n, ...a || []);
}
}
info(e, t, ...i) {
if (!this.isContextEnabled() || this.logLevel > o.info) return;
const { metadata: r, restData: a } = this.styleEngine.parseMetadataParameter(
t,
i
);
if (this.handleDeduplication(o.info, e, r, a))
if (r) {
const n = this.styleEngine.formatMessageWithMetadata(
o.info,
e,
this.name,
r,
this.context
);
console.info(n, ...a || []);
} else {
const n = this.styleEngine.formatMessage(
o.info,
e,
this.name,
this.context
);
console.info(n, ...a || []);
}
}
warn(e, t, ...i) {
if (!this.isContextEnabled() || this.logLevel > o.warn) return;
const { metadata: r, restData: a } = this.styleEngine.parseMetadataParameter(
t,
i
);
if (this.handleDeduplication(o.warn, e, r, a))
if (r) {
const n = this.styleEngine.formatMessageWithMetadata(
o.warn,
e,
this.name,
r,
this.context
);
console.warn(n, ...a || []);
} else {
const n = this.styleEngine.formatMessage(
o.warn,
e,
this.name,
this.context
);
console.warn(n, ...a || []);
}
}
error(e, t, i, ...r) {
if (!this.isContextEnabled() || this.logLevel > o.error) return;
const { message: a, serializedError: n, 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,
a,
this.name,
p,
this.context
)];
n && y.push(n), 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: a, serializedError: n, 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: ${a}`,
this.name,
p,
this.context
)];
n && y.push(n), 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 a = typeof e == "string" ? e : "Unknown error", n = t ? m.serialize(t) : void 0, c = i !== void 0 ? [i, ...r] : r;
return { message: a, serializedError: n, 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 = "", a = "") {
try {
return this.styleEngine.formatMessage(e, t, i, r, a);
} catch (n) {
throw this.error("Style engine failure", n), 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 (a) {
return console.error("Logger deduplication error:", a), !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 a of t.onRequest)
if (await a(s), s.handled)
return;
}
const r = { body: await e(s) };
if (t.onBeforeResponse)
for (const a of t.onBeforeResponse)
await a(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 (a) => {
const n = C(w).forContext(t), c = Date.now();
try {
const l = await s(a), 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 n[g] == "function" && n[g](
`Request completed in ${h}ms`,
{
method: a.method,
path: a.path,
duration: h,
...r && l ? { response: l } : {}
}
), l;
} catch (l) {
const h = Date.now() - c;
throw n.error(`Request failed after ${h}ms`, l, {
method: a.method,
path: a.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
};