Files
flxn-app/src/lib/logger/index.ts
2026-02-14 12:59:01 -06:00

179 lines
4.6 KiB
TypeScript

type LogLevel = "info" | "success" | "warn" | "error";
interface LoggerOptions {
enabled?: boolean;
showTimestamp?: boolean;
collapsed?: boolean;
colors?: boolean;
}
let cachedTimestamp = "";
let lastTimestampUpdate = 0;
function getTimestamp(): string {
const now = Date.now();
if (now - lastTimestampUpdate > 1000) {
const date = new Date(now);
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
cachedTimestamp = `${month}/${day} ${hours}:${minutes}:${seconds}`;
lastTimestampUpdate = now;
}
return cachedTimestamp;
}
function getLevelStyle(level: LogLevel): { color: string; label: string } {
const styles = {
info: { color: "#f5f5f5", label: "INFO" },
success: { color: "#10B981", label: "SUCCESS" },
warn: { color: "#F59E0B", label: "WARN" },
error: { color: "#EF4444", label: "ERROR" },
};
return styles[level] || styles.info;
}
class Logger {
private options: LoggerOptions;
private context?: string;
constructor(context?: string, options: LoggerOptions = {}) {
this.context = context;
this.options = {
enabled: true,
showTimestamp: true,
collapsed: true,
colors: true,
...options,
};
}
child(context: string, options?: LoggerOptions): Logger {
const childContext = this.context
? `${this.context} > ${context}`
: context;
return new Logger(childContext, { ...this.options, ...options });
}
private log(
level: LogLevel,
label: string,
data?: any,
...rest: any[]
): void {
if (!this.options.enabled) return;
const style = getLevelStyle(level);
const timestamp = this.options.showTimestamp ? `${getTimestamp()}` : "";
const context = this.context ? `${this.context}` : "";
const groupLabel = `${timestamp}${style.label}${context}${label}`;
// In server environment (no window), use simple console.log instead of groups
const isServer = typeof window === "undefined";
if (isServer) {
// Server-side: Simple formatted output (no console.group in Node.js)
console.log(groupLabel);
if (data !== undefined) {
console.log(JSON.stringify(data, null, 2));
}
if (rest.length > 0) {
for (const item of rest) {
console.log(JSON.stringify(item, null, 2));
}
}
} else {
// Browser: Use console.group with colors
const group = this.options.collapsed
? console.groupCollapsed
: console.group;
if (this.options.colors) {
group(`%c${groupLabel}`, `color: ${style.color}; font-weight: bold;`);
} else {
group(groupLabel);
}
if (data !== undefined) {
console.log(data);
}
if (rest.length > 0) {
for (const item of rest) {
console.log(item);
}
}
console.groupEnd();
}
}
info(label: string, data?: any, ...rest: any[]): void {
this.log("info", label, data, ...rest);
}
success(label: string, data?: any, ...rest: any[]): void {
this.log("success", label, data, ...rest);
}
warn(label: string, data?: any, ...rest: any[]): void {
this.log("warn", label, data, ...rest);
}
error(label: string, data?: any, ...rest: any[]): void {
this.log("error", label, data, ...rest);
}
simple(message: string): void {
if (!this.options.enabled) return;
const style = getLevelStyle("info");
const timestamp = this.options.showTimestamp ? `${getTimestamp()}` : "";
const context = this.context ? `${this.context}` : "";
const logMessage = `${timestamp}${style.label}${context}${message}`;
if (this.options.colors && typeof window !== "undefined") {
console.log(`%c${logMessage}`, `color: ${style.color};`);
} else {
console.log(logMessage);
}
}
async measure<T>(label: string, fn: () => Promise<T>): Promise<T> {
const start = performance.now();
try {
const result = await fn();
const duration = (performance.now() - start).toFixed(2);
this.success(`${label} completed`, {
duration: `${duration}ms`,
result,
});
return result;
} catch (error) {
const duration = (performance.now() - start).toFixed(2);
this.error(`${label} failed`, {
duration: `${duration}ms`,
error,
});
throw error;
}
}
}
export const logger = new Logger();
export { Logger };