| | import fs from 'fs' |
| | import path from 'path' |
| | import os from 'os' |
| | import { getFileLogger, test__resetFileLogger } from './file-logger' |
| |
|
| | describe('FileLogger', () => { |
| | let tempDir: string |
| | let fileLogger: ReturnType<typeof getFileLogger> |
| |
|
| | beforeEach(() => { |
| | |
| | tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'next-file-logger-test-')) |
| | test__resetFileLogger() |
| | fileLogger = getFileLogger() |
| | fileLogger.initialize(tempDir, true) |
| | }) |
| |
|
| | afterEach(() => { |
| | |
| | if (fs.existsSync(tempDir)) { |
| | fs.rmSync(tempDir, { recursive: true, force: true }) |
| | } |
| | }) |
| |
|
| | it('should create log file on first log', () => { |
| | |
| | fileLogger.logBrowser('LOG', 'Test message') |
| |
|
| | |
| | fileLogger.forceFlush() |
| |
|
| | |
| | const logsDir = path.join(tempDir, 'logs') |
| | expect(fs.existsSync(logsDir)).toBe(true) |
| |
|
| | const logFiles = fs.readdirSync(logsDir) |
| | expect(logFiles.length).toBe(1) |
| | expect(logFiles[0]).toBe('next-development.log') |
| | }) |
| |
|
| | it('should format log entries correctly', () => { |
| | fileLogger.logBrowser('LOG', 'Test message') |
| | fileLogger.logServer('ERROR', 'Server error') |
| |
|
| | |
| | fileLogger.forceFlush() |
| |
|
| | |
| | const logsDir = path.join(tempDir, 'logs') |
| | const logFilePath = path.join(logsDir, 'next-development.log') |
| |
|
| | const logContent = fs.readFileSync(logFilePath, 'utf-8') |
| | const lines = logContent.trim().split('\n') |
| |
|
| | expect(lines).toHaveLength(2) |
| |
|
| | |
| | expect(lines[0]).toMatch( |
| | /^\[\d{2}:\d{2}:\d{2}\.\d{3}\] Browser LOG {5}Test message$/ |
| | ) |
| | expect(lines[1]).toMatch( |
| | /^\[\d{2}:\d{2}:\d{2}\.\d{3}\] Server {2}ERROR {3}Server error$/ |
| | ) |
| | }) |
| |
|
| | it('should append multiple log entries', () => { |
| | fileLogger.logBrowser('LOG', 'First message') |
| | fileLogger.logBrowser('WARN', 'Second message') |
| | fileLogger.logServer('INFO', 'Third message') |
| |
|
| | |
| | fileLogger.forceFlush() |
| |
|
| | |
| | const logsDir = path.join(tempDir, 'logs') |
| | const logFilePath = path.join(logsDir, 'next-development.log') |
| |
|
| | const logContent = fs.readFileSync(logFilePath, 'utf-8') |
| | const lines = logContent.trim().split('\n') |
| |
|
| | expect(lines).toHaveLength(3) |
| | expect(lines[0]).toContain('First message') |
| | expect(lines[1]).toContain('Second message') |
| | expect(lines[2]).toContain('Third message') |
| | }) |
| |
|
| | it('should handle special characters in messages', () => { |
| | fileLogger.logBrowser('LOG', 'Message with "quotes" and \n newlines') |
| |
|
| | |
| | fileLogger.forceFlush() |
| |
|
| | |
| | const logsDir = path.join(tempDir, 'logs') |
| | const logFilePath = path.join(logsDir, 'next-development.log') |
| |
|
| | const logContent = fs.readFileSync(logFilePath, 'utf-8') |
| | expect(logContent).toContain('Message with "quotes" and') |
| | expect(logContent).toContain('newlines') |
| | }) |
| |
|
| | it('should pad log levels correctly', () => { |
| | fileLogger.logBrowser('LOG', 'Short level') |
| | fileLogger.logBrowser('WARN', 'Medium level') |
| | fileLogger.logBrowser('ERROR', 'Long level') |
| |
|
| | |
| | fileLogger.forceFlush() |
| |
|
| | |
| | const logsDir = path.join(tempDir, 'logs') |
| | const logFilePath = path.join(logsDir, 'next-development.log') |
| |
|
| | const logContent = fs.readFileSync(logFilePath, 'utf-8') |
| | const lines = logContent.trim().split('\n') |
| |
|
| | |
| | expect(lines[0]).toMatch(/Browser LOG {5}Short level/) |
| | expect(lines[1]).toMatch(/Browser WARN {4}Medium level/) |
| | expect(lines[2]).toMatch(/Browser ERROR {3}Long level/) |
| | }) |
| |
|
| | it('should not create log file when mcpServer is disabled', () => { |
| | |
| | const logsDir = path.join(tempDir, 'logs') |
| | const logsDirExistedBefore = fs.existsSync(logsDir) |
| |
|
| | |
| | const disabledLogger = getFileLogger() |
| | disabledLogger.initialize(tempDir, false) |
| |
|
| | |
| | disabledLogger.logBrowser('LOG', 'This should not be logged') |
| | disabledLogger.logServer('ERROR', 'This should also not be logged') |
| |
|
| | |
| | disabledLogger.forceFlush() |
| |
|
| | |
| | const logsDirExistsAfter = fs.existsSync(logsDir) |
| | expect(logsDirExistsAfter).toBe(logsDirExistedBefore) |
| | }) |
| |
|
| | describe('batching behavior', () => { |
| | it('should batch multiple logs and flush them together', async () => { |
| | |
| | fileLogger.logBrowser('LOG', 'First batched message') |
| | fileLogger.logBrowser('WARN', 'Second batched message') |
| | fileLogger.logServer('INFO', 'Third batched message') |
| |
|
| | |
| | const logsDir = path.join(tempDir, 'logs') |
| | const logFilePath = path.join(logsDir, 'next-development.log') |
| |
|
| | if (fs.existsSync(logFilePath)) { |
| | const initialContent = fs.readFileSync(logFilePath, 'utf-8') |
| | expect(initialContent.trim()).toBe('') |
| | } |
| |
|
| | |
| | fileLogger.forceFlush() |
| |
|
| | const logContent = fs.readFileSync(logFilePath, 'utf-8') |
| | const lines = logContent.trim().split('\n') |
| |
|
| | expect(lines).toHaveLength(3) |
| | expect(lines[0]).toContain('First batched message') |
| | expect(lines[1]).toContain('Second batched message') |
| | expect(lines[2]).toContain('Third batched message') |
| | }) |
| |
|
| | it('should flush automatically after flush interval', async () => { |
| | |
| | fileLogger.logBrowser('LOG', 'Auto-flush test message') |
| |
|
| | |
| | const logsDir = path.join(tempDir, 'logs') |
| | const logFilePath = path.join(logsDir, 'next-development.log') |
| |
|
| | if (fs.existsSync(logFilePath)) { |
| | const initialContent = fs.readFileSync(logFilePath, 'utf-8') |
| | expect(initialContent.trim()).toBe('') |
| | } |
| |
|
| | |
| | await new Promise((resolve) => setTimeout(resolve, 1100)) |
| |
|
| | |
| | const logContent = fs.readFileSync(logFilePath, 'utf-8') |
| | const lines = logContent.trim().split('\n') |
| |
|
| | expect(lines).toHaveLength(1) |
| | expect(lines[0]).toContain('Auto-flush test message') |
| | }) |
| |
|
| | it('should flush immediately when queue reaches max size', () => { |
| | |
| | for (let i = 0; i < 100; i++) { |
| | fileLogger.logBrowser('LOG', `Message ${i}`) |
| | } |
| |
|
| | |
| | fileLogger.forceFlush() |
| |
|
| | const logsDir = path.join(tempDir, 'logs') |
| | const logFilePath = path.join(logsDir, 'next-development.log') |
| |
|
| | const logContent = fs.readFileSync(logFilePath, 'utf-8') |
| | const lines = logContent.trim().split('\n') |
| |
|
| | expect(lines).toHaveLength(100) |
| | expect(lines[0]).toContain('Message 0') |
| | expect(lines[99]).toContain('Message 99') |
| | }) |
| |
|
| | it('should handle forceFlush correctly', () => { |
| | fileLogger.logBrowser('LOG', 'Before force flush') |
| | fileLogger.logServer('ERROR', 'Another before force flush') |
| |
|
| | |
| | fileLogger.forceFlush() |
| |
|
| | const logsDir = path.join(tempDir, 'logs') |
| | const logFilePath = path.join(logsDir, 'next-development.log') |
| |
|
| | const logContent = fs.readFileSync(logFilePath, 'utf-8') |
| | const lines = logContent.trim().split('\n') |
| |
|
| | expect(lines).toHaveLength(2) |
| | expect(lines[0]).toContain('Before force flush') |
| | expect(lines[1]).toContain('Another before force flush') |
| |
|
| | |
| | fileLogger.logBrowser('WARN', 'After force flush') |
| |
|
| | |
| | const logContentAfter = fs.readFileSync(logFilePath, 'utf-8') |
| | const linesAfter = logContentAfter.trim().split('\n') |
| | expect(linesAfter).toHaveLength(2) |
| |
|
| | |
| | fileLogger.forceFlush() |
| |
|
| | const logContentFinal = fs.readFileSync(logFilePath, 'utf-8') |
| | const linesFinal = logContentFinal.trim().split('\n') |
| | expect(linesFinal).toHaveLength(3) |
| | expect(linesFinal[2]).toContain('After force flush') |
| | }) |
| | }) |
| | }) |
| |
|