diff --git a/playwriter/CHANGELOG.md b/playwriter/CHANGELOG.md index 12c79fd..55fc5ba 100644 --- a/playwriter/CHANGELOG.md +++ b/playwriter/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.0.20 + +### Patch Changes + +- **Timestamped log files**: Log files now include timestamps in filename (`relay-server-{timestamp}.log`) instead of overwriting a single file +- **Automatic log cleanup**: Keeps only the 10 most recent log files, deleting older ones automatically +- **Async log writes**: Logger now uses a queue for async file writes instead of blocking sync writes + ## 0.0.19 ### Patch Changes diff --git a/playwriter/package.json b/playwriter/package.json index 9a14855..a09725c 100644 --- a/playwriter/package.json +++ b/playwriter/package.json @@ -1,7 +1,7 @@ { "name": "playwriter", "description": "", - "version": "0.0.19", + "version": "0.0.20", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/playwriter/src/create-logger.ts b/playwriter/src/create-logger.ts index c5d5d5d..cc7c40c 100644 --- a/playwriter/src/create-logger.ts +++ b/playwriter/src/create-logger.ts @@ -1,26 +1,56 @@ import fs from 'node:fs' +import path from 'node:path' import util from 'node:util' -import { ensureDataDir, getLogFilePath } from './utils.js' +import { LOG_FILE_PATH } from './utils.js' export type Logger = { - log(...args: unknown[]): void - error(...args: unknown[]): void + log(...args: unknown[]): Promise + error(...args: unknown[]): Promise + logFilePath: string +} + +function cleanupOldLogs(logsDir: string): void { + if (!fs.existsSync(logsDir)) { + return + } + const files = fs.readdirSync(logsDir) + .filter((f) => f.startsWith('relay-server-') && f.endsWith('.log')) + .map((f) => ({ + name: f, + path: path.join(logsDir, f), + mtime: fs.statSync(path.join(logsDir, f)).mtime.getTime(), + })) + .sort((a, b) => b.mtime - a.mtime) + + if (files.length > 10) { + files.slice(10).forEach((f) => { + fs.unlinkSync(f.path) + }) + } } export function createFileLogger({ logFilePath }: { logFilePath?: string } = {}): Logger { - const resolvedLogFilePath = logFilePath || getLogFilePath() - ensureDataDir() + const resolvedLogFilePath = logFilePath || LOG_FILE_PATH + const logDir = path.dirname(resolvedLogFilePath) + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }) + } + cleanupOldLogs(logDir) fs.writeFileSync(resolvedLogFilePath, '') - const log = (...args: unknown[]) => { + let queue: Promise = Promise.resolve() + + const log = (...args: unknown[]): Promise => { const message = args.map(arg => typeof arg === 'string' ? arg : util.inspect(arg, { depth: null, colors: false }) ).join(' ') - fs.appendFileSync(resolvedLogFilePath, message + '\n') + queue = queue.then(() => fs.promises.appendFile(resolvedLogFilePath, message + '\n')) + return queue } return { log, - error: log + error: log, + logFilePath: resolvedLogFilePath, } } diff --git a/playwriter/src/mcp.test.ts b/playwriter/src/mcp.test.ts index 5fcf6d7..ec377f2 100644 --- a/playwriter/src/mcp.test.ts +++ b/playwriter/src/mcp.test.ts @@ -1914,19 +1914,13 @@ describe('CDP Session Tests', () => { sampleFunctionNames: functionNames, }).toMatchInlineSnapshot(` { - "durationMicroseconds": 8646, + "durationMicroseconds": 11057, "hasNodes": true, - "nodeCount": 20, + "nodeCount": 4, "sampleFunctionNames": [ "(root)", "(program)", "(idle)", - "evaluate", - "fibonacci", - "fibonacci", - "fibonacci", - "fibonacci", - "fibonacci", "fibonacci", ], } diff --git a/playwriter/src/mcp.ts b/playwriter/src/mcp.ts index d4c79e2..8a19685 100644 --- a/playwriter/src/mcp.ts +++ b/playwriter/src/mcp.ts @@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url' import vm from 'node:vm' import dedent from 'string-dedent' import { createPatch } from 'diff' -import { getCdpUrl, getLogFilePath } from './utils.js' +import { getCdpUrl, LOG_FILE_PATH } from './utils.js' import { waitForPageLoad, WaitForPageLoadOptions, WaitForPageLoadResult } from './wait-for-page-load.js' import { getCDPSessionForPage, CDPSession } from './cdp-session.js' @@ -92,7 +92,6 @@ const lastSnapshots: WeakMap = new WeakMap() const cdpSessionCache: WeakMap = new WeakMap() const RELAY_PORT = 19988 -const LOG_FILE_PATH = getLogFilePath() const NO_TABS_ERROR = `No browser tabs are connected. Please install and enable the Playwriter extension on at least one tab: https://chromewebstore.google.com/detail/playwriter-mcp/jfeammnjpkecdekppnclgkkffahnhfhe` diff --git a/playwriter/src/start-relay-server.ts b/playwriter/src/start-relay-server.ts index 84c085c..e02b61c 100644 --- a/playwriter/src/start-relay-server.ts +++ b/playwriter/src/start-relay-server.ts @@ -1,11 +1,9 @@ import { startPlayWriterCDPRelayServer } from './extension/cdp-relay.js' import { createFileLogger } from './create-logger.js' -import { getLogFilePath } from './utils.js' -const logFilePath = getLogFilePath() process.title = 'playwriter-ws-server' -const logger = createFileLogger({ logFilePath }) +const logger = createFileLogger() process.on('uncaughtException', async (err) => { await logger.error('Uncaught Exception:', err); @@ -26,7 +24,7 @@ export async function startServer({ port = 19988 }: { port?: number } = {}) { const server = await startPlayWriterCDPRelayServer({ port, logger }) console.log('CDP Relay Server running. Press Ctrl+C to stop.') - console.log('Logs are being written to:', logFilePath) + console.log('Logs are being written to:', logger.logFilePath) process.on('SIGINT', () => { console.log('\nShutting down...') diff --git a/playwriter/src/utils.ts b/playwriter/src/utils.ts index 38e7471..6e5879a 100644 --- a/playwriter/src/utils.ts +++ b/playwriter/src/utils.ts @@ -19,10 +19,20 @@ export function ensureDataDir(): string { return dataDir } -export function getLogFilePath(): string { - return process.env.PLAYWRITER_LOG_PATH || path.join(getDataDir(), 'relay-server.log') +function getLogsDir(): string { + return path.join(getDataDir(), 'logs') } +function getLogFilePath(): string { + if (process.env.PLAYWRITER_LOG_PATH) { + return process.env.PLAYWRITER_LOG_PATH + } + const timestamp = new Date().toISOString().replace(/[:.]/g, '-') + return path.join(getLogsDir(), `relay-server-${timestamp}.log`) +} + +export const LOG_FILE_PATH = getLogFilePath() + // export function getDidPromptReviewPath(): string { // return path.join(getDataDir(), 'did-prompt-review') // }