179 lines
4.6 KiB
TypeScript
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 };
|