Files
everything-is-suitable/everything-is-suitable-test/scripts/utils/logger.ts
T
张翔 08ea5fbe98 feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
2026-03-28 14:37:29 +08:00

165 lines
4.6 KiB
TypeScript

import * as fs from 'fs';
import * as path from 'path';
export enum LogLevel {
DEBUG = 'debug',
INFO = 'info',
WARN = 'warn',
ERROR = 'error'
}
interface LogEntry {
timestamp: string;
level: LogLevel;
message: string;
context?: Record<string, unknown>;
}
export class Logger {
private static instance: Logger;
private logDir: string;
private logFile: string;
private logLevel: LogLevel;
private context: Record<string, unknown>;
private constructor(logDir: string = 'test-results/logs') {
this.logDir = logDir;
this.logFile = path.join(logDir, `test-run-${new Date().toISOString().replace(/[:.]/g, '-')}.log`);
this.logLevel = this.parseLogLevel(process.env.LOG_LEVEL || 'info');
this.context = {};
this.ensureLogDir();
}
static getInstance(logDir?: string): Logger {
if (!Logger.instance) {
Logger.instance = new Logger(logDir);
}
return Logger.instance;
}
private parseLogLevel(level: string): LogLevel {
switch (level.toLowerCase()) {
case 'debug': return LogLevel.DEBUG;
case 'info': return LogLevel.INFO;
case 'warn': return LogLevel.WARN;
case 'error': return LogLevel.ERROR;
default: return LogLevel.INFO;
}
}
private ensureLogDir(): void {
if (!fs.existsSync(this.logDir)) {
fs.mkdirSync(this.logDir, { recursive: true });
}
}
setContext(context: Record<string, unknown>): this {
this.context = { ...this.context, ...context };
return this;
}
clearContext(): this {
this.context = {};
return this;
}
private formatTimestamp(): string {
return new Date().toISOString();
}
private formatMessage(level: LogLevel, message: string): string {
const timestamp = this.formatTimestamp();
const levelStr = level.toUpperCase().padEnd(5);
const contextStr = Object.keys(this.context).length > 0
? ` [${JSON.stringify(this.context)}]`
: '';
return `${timestamp} | ${levelStr} |${contextStr} ${message}`;
}
private shouldLog(level: LogLevel): boolean {
const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR];
return levels.indexOf(level) >= levels.indexOf(this.logLevel);
}
private writeToFile(entry: LogEntry): void {
try {
this.ensureLogDir();
const logLine = JSON.stringify(entry) + '\n';
fs.appendFileSync(this.logFile, logLine, 'utf-8');
} catch {
// Ignore file write errors
}
}
private log(level: LogLevel, message: string, context?: Record<string, unknown>): void {
if (!this.shouldLog(level)) return;
const entry: LogEntry = {
timestamp: this.formatTimestamp(),
level,
message,
context: { ...this.context, ...context }
};
this.writeToFile(entry);
const formattedMessage = this.formatMessage(level, message);
const colors: Record<LogLevel, string> = {
[LogLevel.DEBUG]: '\x1b[36m',
[LogLevel.INFO]: '\x1b[32m',
[LogLevel.WARN]: '\x1b[33m',
[LogLevel.ERROR]: '\x1b[31m'
};
const reset = '\x1b[0m';
console.log(`${colors[level]}${formattedMessage}${reset}`);
}
debug(message: string, context?: Record<string, unknown>): void {
this.log(LogLevel.DEBUG, message, context);
}
info(message: string, context?: Record<string, unknown>): void {
this.log(LogLevel.INFO, message, context);
}
warn(message: string, context?: Record<string, unknown>): void {
this.log(LogLevel.WARN, message, context);
}
error(message: string, error?: Error | unknown, context?: Record<string, unknown>): void {
const errorContext = error instanceof Error
? { errorMessage: error.message, stack: error.stack, ...context }
: { error, ...context };
this.log(LogLevel.ERROR, message, errorContext);
}
section(title: string): void {
const separator = '='.repeat(60);
console.log(`\n${separator}`);
console.log(` ${title}`);
console.log(`${separator}\n`);
this.info(`=== ${title} ===`);
}
progress(current: number, total: number, message: string): void {
const percentage = Math.round((current / total) * 100);
const bar = this.createProgressBar(percentage);
process.stdout.write(`\r${bar} ${percentage}% - ${message}`);
if (current === total) {
process.stdout.write('\n');
}
}
private createProgressBar(percentage: number, width: number = 30): string {
const filled = Math.round((percentage / 100) * width);
const empty = width - filled;
return `[${'█'.repeat(filled)}${'░'.repeat(empty)}]`;
}
getLogFile(): string {
return this.logFile;
}
}
export const logger = Logger.getInstance();