import { describe, expect, it, vi } from 'vitest'; import { LogFileBackend, LogFileInfo, LogLevel, createLogger } from '../src/utils/logger'; class MemoryLogBackend implements LogFileBackend { public files: Map = new Map(); async appendLog(dateStr: string, logLine: string): Promise { const existing = this.files.get(dateStr) ?? ''; this.files.set(dateStr, existing + logLine); } async readLogFile(dateStr: string): Promise { return this.files.get(dateStr) ?? ''; } async listLogFiles(): Promise { const res: LogFileInfo[] = []; for (const [date, content] of this.files.entries()) { res.push({ name: `app-${date}.log`, path: `/mock/logs/app-${date}.log`, size: Buffer.byteLength(content, 'utf8'), date, }); } return res.sort((a, b) => b.date.localeCompare(a.date)); } async deleteLogFile(dateStr: string): Promise { this.files.delete(dateStr); } async clearAllLogFiles(): Promise { this.files.clear(); } } describe('LoggerImpl 持久化与高级功能测试', () => { it('应当能正确初始化 LogFileBackend 并将日志追加写入内存文件', async () => { const logger = createLogger(); logger.consoleOutput = false; const backend = new MemoryLogBackend(); await logger.initFileBackend(backend); logger.info('testTag', '测试消息1', { foo: 'bar' }); logger.error('testTag', '崩溃报错消息'); await logger.flushQueue(); const todayStr = new Date().toISOString().slice(0, 10); const fileContent = await backend.readLogFile(todayStr); expect(fileContent).toContain('测试消息1'); expect(fileContent).toContain('崩溃报错消息'); expect(fileContent).toContain('"foo":"bar"'); }); it('应当能正确按日期读取文件中的历史日志条目', async () => { const logger = createLogger(); logger.consoleOutput = false; const backend = new MemoryLogBackend(); const historicalDate = '2026-07-20'; const logLine1 = JSON.stringify({ timestamp: '2026-07-20T10:00:00.000Z', level: LogLevel.WARN, tag: 'network', message: '网络超时', }); const logLine2 = JSON.stringify({ timestamp: '2026-07-20T10:05:00.000Z', level: LogLevel.ERROR, tag: 'db', message: '数据库锁死', }); await backend.appendLog(historicalDate, `${logLine1}\n${logLine2}\n`); await logger.initFileBackend(backend); const loadedLogs = await logger.loadLogsFromFile(historicalDate); expect(loadedLogs).toHaveLength(2); expect(loadedLogs[0].tag).toBe('network'); expect(loadedLogs[0].level).toBe(LogLevel.WARN); expect(loadedLogs[1].message).toBe('数据库锁死'); }); it('应当能自动清理超过指定保留天数(如 7 天)的历史日志文件', async () => { const logger = createLogger(); logger.consoleOutput = false; const backend = new MemoryLogBackend(); // 假设今天 2026-07-22,注入 10 天前与 2 天前的日志文件 await backend.appendLog('2026-07-01', 'old logs\n'); // 超过 7 天 await backend.appendLog('2026-07-21', 'recent logs\n'); // 保留 await logger.initFileBackend(backend, { maxDays: 7 }); const files = await logger.getLogFiles(); const dates = files.map(f => f.date); expect(dates).not.toContain('2026-07-01'); expect(dates).toContain('2026-07-21'); }); it('应当能格式化导出人类可读文本字符串', () => { const logger = createLogger(); logger.consoleOutput = false; logger.info('billPipeline', '接收到微信账单', { amount: 15.5 }); logger.warn('dedup', '发现重复交易', { id: 'tx-123' }); const exportText = logger.exportAsFormattedText(); expect(exportText).toContain('[INFO] [billPipeline] 接收到微信账单'); expect(exportText).toContain('[WARN] [dedup] 发现重复交易'); expect(exportText).toContain('"amount":15.5'); }); it('query 方法应当支持按关键词过滤日志', () => { const logger = createLogger(); logger.consoleOutput = false; logger.info('tagA', '普通记账事件'); logger.error('tagB', '支付接口网络超时', { status: 504 }); const result = logger.query({ keyword: '超时' }); expect(result).toHaveLength(1); expect(result[0].tag).toBe('tagB'); }); it('clearAllLogs 应当同时清空内存与磁盘中的全部日志', async () => { const logger = createLogger(); logger.consoleOutput = false; const backend = new MemoryLogBackend(); await logger.initFileBackend(backend); logger.info('tag', '一条日志'); await logger.flushQueue(); expect(logger.getLogs()).toHaveLength(1); expect((await backend.listLogFiles()).length).toBeGreaterThan(0); await logger.clearAllLogs(); expect(logger.getLogs()).toHaveLength(0); expect((await backend.listLogFiles()).length).toBe(0); }); it('getTodayLogs 应当能正确合并磁盘上的历史 session 日志与当前内存中的新日志(自动去重)', async () => { const logger = createLogger(); logger.consoleOutput = false; const backend = new MemoryLogBackend(); await logger.initFileBackend(backend); const todayStr = new Date().toISOString().slice(0, 10); const earlierSessionLog = JSON.stringify({ timestamp: `${todayStr}T08:00:00.000Z`, level: LogLevel.INFO, tag: 'layout', message: '早前 Session 启动日志', }); await backend.appendLog(todayStr, `${earlierSessionLog}\n`); // 当前 Session 新记录的内存日志 logger.info('billPipeline', '新 Session 产生的日志'); const todayLogs = await logger.getTodayLogs(); expect(todayLogs.some(e => e.message === '早前 Session 启动日志')).toBe(true); expect(todayLogs.some(e => e.message === '新 Session 产生的日志')).toBe(true); }); });