diff --git a/src/McpContext.ts b/src/McpContext.ts index 3855c02f4..1d15b3421 100644 --- a/src/McpContext.ts +++ b/src/McpContext.ts @@ -27,7 +27,6 @@ import { type Browser, type BrowserContext, type ConsoleMessage, - type Debugger, type HTTPRequest, type Page, type ScreenRecorder, @@ -41,6 +40,7 @@ import {listPages} from './tools/pages.js'; import {CLOSE_PAGE_ERROR} from './tools/ToolDefinition.js'; import type {Context, SupportedExtensions} from './tools/ToolDefinition.js'; import type {TraceResult} from './trace-processing/parse.js'; +import type {Logger} from './types.js'; import type { EmulationSettings, GeolocationOptions, @@ -67,7 +67,7 @@ const NAVIGATION_TIMEOUT = 10_000; export class McpContext implements Context { browser: Browser; - logger: Debugger; + logger: Logger; // Maps LLM-provided isolatedContext name → Puppeteer BrowserContext. #isolatedContexts = new Map(); @@ -102,7 +102,7 @@ export class McpContext implements Context { private constructor( browser: Browser, - logger: Debugger, + logger: Logger, options: McpContextOptions, locatorClass: typeof Locator, ) { @@ -153,7 +153,7 @@ export class McpContext implements Context { static async from( browser: Browser, - logger: Debugger, + logger: Logger, opts: McpContextOptions, /* Let tests use unbundled Locator class to avoid overly strict checks within puppeteer that fail when mixing bundled and unbundled class instances */ locatorClass: typeof Locator = Locator, @@ -236,7 +236,7 @@ export class McpContext implements Context { resolveCdpRequestId(page: McpPage, cdpRequestId: string): number | undefined { if (!cdpRequestId) { - this.logger('no network request'); + this.logger?.('no network request'); return; } const request = this.#networkCollector.find(page.pptrPage, request => { @@ -244,7 +244,7 @@ export class McpContext implements Context { return request.id === cdpRequestId; }); if (!request) { - this.logger('no network request for ' + cdpRequestId); + this.logger?.('no network request for ' + cdpRequestId); return; } return this.#networkCollector.getIdForResource(request); @@ -591,7 +591,7 @@ export class McpContext implements Context { this.#mcpPages.set(page, mcpPage); // We emulate a focused page for all pages to support multi-agent workflows. void page.emulateFocusedPage(true).catch(error => { - this.logger('Error turning on focused page emulation', error); + this.logger?.('Error turning on focused page emulation', error); }); } mcpPage.isolatedContextName = isolatedContextNames.get(page); @@ -655,7 +655,7 @@ export class McpContext implements Context { page = await target.asPage(); this.#extensionPages.set(target, page); } catch (e) { - this.logger('Failed to get page for extension target', e); + this.logger?.('Failed to get page for extension target', e); } } } @@ -696,7 +696,7 @@ export class McpContext implements Context { } async detectOpenDevToolsWindows() { - this.logger('Detecting open DevTools windows'); + this.logger?.('Detecting open DevTools windows'); const {pages} = await this.#getAllPages(); await Promise.all( @@ -769,7 +769,7 @@ export class McpContext implements Context { await fs.writeFile(filePath, data); return {filename: filePath}; } catch (err) { - this.logger(err); + this.logger?.(err); throw new Error('Could not save a file', {cause: err}); } } diff --git a/src/McpPage.ts b/src/McpPage.ts index cfa71604f..0a32c2de8 100644 --- a/src/McpPage.ts +++ b/src/McpPage.ts @@ -283,7 +283,7 @@ export class McpPage implements ContextPage { for (const handle of oldHandles) { await handle .dispose() - .catch(e => logger('Failed to dispose old handle', e)); + .catch(e => logger?.('Failed to dispose old handle', e)); } } @@ -291,14 +291,14 @@ export class McpPage implements ContextPage { elementHandles.map(async (elementHandle, index) => { const backendNodeId = await elementHandle.backendNodeId(); if (!backendNodeId) { - logger( + logger?.( `No backendNodeId for stashed DOM element with index ${index}`, ); return `stashed-${index}`; } const cdpElementId = this.resolveCdpElementId(backendNodeId); if (!cdpElementId) { - logger( + logger?.( `Could not get cdpElementId for backend node ${backendNodeId}`, ); return `stashed-${index}`; @@ -371,12 +371,12 @@ export class McpPage implements ContextPage { resolveCdpElementId(cdpBackendNodeId: number): string | undefined { if (!cdpBackendNodeId) { - logger('no cdpBackendNodeId'); + logger?.('no cdpBackendNodeId'); return; } const snapshot = this.textSnapshot; if (!snapshot) { - logger('no text snapshot'); + logger?.('no text snapshot'); return; } // TODO: index by backendNodeId instead. @@ -395,10 +395,10 @@ export class McpPage implements ContextPage { async getDevToolsData(): Promise { try { - logger('Getting DevTools UI data'); + logger?.('Getting DevTools UI data'); const devtoolsPage = this.devToolsPage; if (!devtoolsPage) { - logger('No DevTools page detected'); + logger?.('No DevTools page detected'); return {}; } const {cdpRequestId, cdpBackendNodeId} = await devtoolsPage.evaluate( @@ -421,7 +421,7 @@ export class McpPage implements ContextPage { ); return {cdpBackendNodeId, cdpRequestId}; } catch (err) { - logger('error getting devtools data', err); + logger?.('error getting devtools data', err); } return {}; } diff --git a/src/PageCollector.ts b/src/PageCollector.ts index 962026491..e54ca70d3 100644 --- a/src/PageCollector.ts +++ b/src/PageCollector.ts @@ -92,7 +92,7 @@ export class PageCollector { } this.addPage(page); } catch (err) { - logger('Error getting a page for a target onTargetCreated', err); + logger?.('Error getting a page for a target onTargetCreated', err); } }; @@ -104,7 +104,7 @@ export class PageCollector { } this.cleanupPageDestroyed(page); } catch (err) { - logger('Error getting a page for a target onTargetDestroyed', err); + logger?.('Error getting a page for a target onTargetDestroyed', err); } }; @@ -335,7 +335,7 @@ class PageEventSubscriber { inspectorIssue, )[0]; if (!issue) { - logger('No issue mapping for for the issue: ', inspectorIssue.code); + logger?.('No issue mapping for for the issue: ', inspectorIssue.code); return; } @@ -353,7 +353,7 @@ class PageEventSubscriber { }, ); } catch (error) { - logger('Error creating a new issue', error); + logger?.('Error creating a new issue', error); } }; } diff --git a/src/TextSnapshot.ts b/src/TextSnapshot.ts index eac0cb3ee..26bd68455 100644 --- a/src/TextSnapshot.ts +++ b/src/TextSnapshot.ts @@ -262,7 +262,7 @@ export class TextSnapshot { collect(node); } } catch (e) { - logger( + logger?.( `Failed to collect descendants for backend node ${backendNodeId}`, e, ); diff --git a/src/ToolHandler.ts b/src/ToolHandler.ts index 01e07d2a5..042d6701f 100644 --- a/src/ToolHandler.ts +++ b/src/ToolHandler.ts @@ -208,11 +208,11 @@ export class ToolHandler { const startTime = Date.now(); let success = false; try { - logger( + logger?.( `${this.tool.name} request: ${JSON.stringify(params, null, ' ')}`, ); const context = await this.getContext(); - logger(`${this.tool.name} context: resolved`); + logger?.(`${this.tool.name} context: resolved`); await context.detectOpenDevToolsWindows(); const response = this.serverArgs.slim ? new SlimMcpResponse(this.serverArgs) @@ -277,7 +277,7 @@ export class ToolHandler { } return result; } catch (err) { - logger(`${this.tool.name} error:`, err, err?.stack); + logger?.(`${this.tool.name} error:`, err, err?.stack); let errorText = err && 'message' in err ? err.message : String(err); if ('cause' in err && err.cause) { errorText += `\nCause: ${err.cause.message}`; diff --git a/src/WaitForHelper.ts b/src/WaitForHelper.ts index 7e0b7fce3..eeb6252ee 100644 --- a/src/WaitForHelper.ts +++ b/src/WaitForHelper.ts @@ -162,7 +162,7 @@ export class WaitForHelper { } return; }) - .catch(error => logger(error)); + .catch(error => logger?.(error)); try { await action(); @@ -183,7 +183,7 @@ export class WaitForHelper { // the correct context await this.waitForStableDom(); } catch (error) { - logger(error); + logger?.(error); } finally { this.#abortController.abort(); } diff --git a/src/bin/chrome-devtools-mcp-main.ts b/src/bin/chrome-devtools-mcp-main.ts index 25fb69b19..1ceb9ec22 100644 --- a/src/bin/chrome-devtools-mcp-main.ts +++ b/src/bin/chrome-devtools-mcp-main.ts @@ -29,7 +29,7 @@ const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined; if (process.env['CHROME_DEVTOOLS_MCP_CRASH_ON_UNCAUGHT'] !== 'true') { process.on('unhandledRejection', (reason, promise) => { - logger('Unhandled promise rejection', promise, reason); + logger?.('Unhandled promise rejection', promise, reason); }); } @@ -43,13 +43,13 @@ async function shutdown(reason: string): Promise { return; } shuttingDown = true; - logger(`Shutting down (${reason})`); + logger?.(`Shutting down (${reason})`); // Backstop in case browser teardown hangs (e.g. unresponsive Chrome, // slow beforeunload handlers, many tabs). Exits 0 because we still // honored the shutdown request; the log line preserves observability. // Unref'd so it doesn't keep the loop alive on the clean path. setTimeout(() => { - logger('Shutdown timeout exceeded, forcing exit'); + logger?.('Shutdown timeout exceeded, forcing exit'); process.exit(0); }, 10000).unref(); await closeBrowser(); @@ -71,13 +71,13 @@ process.on('SIGHUP', () => { void shutdown('SIGHUP'); }); -logger(`Starting Chrome DevTools MCP Server v${VERSION}`); +logger?.(`Starting Chrome DevTools MCP Server v${VERSION}`); const {server} = await createMcpServer(args, { logFile, }); const transport = new StdioServerTransport(); await server.connect(transport); -logger('Chrome DevTools MCP Server connected'); +logger?.('Chrome DevTools MCP Server connected'); logDisclaimers(args); void ClearcutLogger.get()?.logDailyActiveIfNeeded(); void ClearcutLogger.get()?.logServerStart(computeFlagUsage(args, cliOptions)); diff --git a/src/browser.ts b/src/browser.ts index 20f254865..fbdf3ed6b 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -119,7 +119,7 @@ export async function ensureBrowserConnected(options: { ); } - logger('Connecting Puppeteer to ', JSON.stringify(connectOptions)); + logger?.('Connecting Puppeteer to ', JSON.stringify(connectOptions)); try { // Assign mode before browser so a concurrent closeBrowser() never sees // `browser` set with `browserMode` still undefined (would fall through @@ -135,7 +135,7 @@ export async function ensureBrowserConnected(options: { }, ); } - logger('Connected Puppeteer'); + logger?.('Connected Puppeteer'); return browser; } @@ -296,12 +296,12 @@ export async function closeBrowser(): Promise { } if (mode === 'launched') { await b.close().catch(err => { - logger('Failed to close browser', err); + logger?.('Failed to close browser', err); }); return; } await b.disconnect().catch(err => { - logger('Failed to disconnect from browser', err); + logger?.('Failed to disconnect from browser', err); }); } diff --git a/src/daemon/client.ts b/src/daemon/client.ts index 40ddf932c..a32253a1e 100644 --- a/src/daemon/client.ts +++ b/src/daemon/client.ts @@ -69,7 +69,7 @@ function waitForFile(filePath: string, removed = false) { export async function startDaemon(mcpArgs: string[] = [], sessionId: string) { if (isDaemonRunning(sessionId)) { - logger('Daemon is already running'); + logger?.('Daemon is already running'); return; } @@ -79,7 +79,7 @@ export async function startDaemon(mcpArgs: string[] = [], sessionId: string) { fs.unlinkSync(pidFilePath); } - logger('Starting daemon...', ...mcpArgs); + logger?.('Starting daemon...', ...mcpArgs); const child = spawn(process.execPath, [DAEMON_SCRIPT_PATH, ...mcpArgs], { detached: true, stdio: 'ignore', @@ -116,27 +116,27 @@ export async function sendCommand( const transport = new PipeTransport(socket, socket); transport.onmessage = async (message: string) => { clearTimeout(timer); - logger('onmessage', message); + logger?.('onmessage', message); resolve(JSON.parse(message)); }; socket.on('error', error => { clearTimeout(timer); - logger('Socket error:', error); + logger?.('Socket error:', error); reject(error); }); socket.on('close', () => { clearTimeout(timer); - logger('Socket closed:'); + logger?.('Socket closed:'); reject(new Error('Socket closed')); }); - logger('Sending message', command); + logger?.('Sending message', command); transport.send(JSON.stringify(command)); }); } export async function stopDaemon(sessionId: string) { if (!isDaemonRunning(sessionId)) { - logger('Daemon is not running'); + logger?.('Daemon is not running'); return; } diff --git a/src/daemon/daemon.ts b/src/daemon/daemon.ts index 6708f31f5..a818edc71 100644 --- a/src/daemon/daemon.ts +++ b/src/daemon/daemon.ts @@ -31,9 +31,9 @@ import { } from './utils.js'; const sessionId = process.env.CHROME_DEVTOOLS_MCP_SESSION_ID || ''; -logger(`Daemon sessionId: ${sessionId}`); +logger?.(`Daemon sessionId: ${sessionId}`); if (isDaemonRunning(sessionId)) { - logger('Another daemon process is running.'); + logger?.('Another daemon process is running.'); process.exit(1); } const pidFilePath = getPidFilePath(sessionId); @@ -113,7 +113,7 @@ try { } } } -logger(`Writing ${process.pid.toString()} to ${pidFilePath}`); +logger?.(`Writing ${process.pid.toString()} to ${pidFilePath}`); const socketPath = getSocketPath(sessionId); @@ -225,13 +225,13 @@ async function startSocketServer() { server = createServer(socket => { const transport = new PipeTransport(socket, socket); transport.onmessage = async (message: string) => { - logger('onmessage', message); + logger?.('onmessage', message); const response = await handleRequest(JSON.parse(message)); transport.send(JSON.stringify(response)); socket.end(); }; socket.on('error', error => { - logger('Socket error:', error); + logger?.('Socket error:', error); }); }); @@ -255,7 +255,7 @@ async function startSocketServer() { ); server.on('error', error => { - logger('Server error:', error); + logger?.('Server error:', error); reject(error); }); }); @@ -267,12 +267,12 @@ async function cleanup() { try { await mcpClient?.close(); } catch (error) { - logger('Error closing MCP client:', error); + logger?.('Error closing MCP client:', error); } try { await mcpTransport?.close(); } catch (error) { - logger('Error closing MCP transport:', error); + logger?.('Error closing MCP transport:', error); } if (server) { await new Promise(resolve => { @@ -286,7 +286,7 @@ async function cleanup() { // ignore errors } } - logger(`unlinking ${pidFilePath}`); + logger?.(`unlinking ${pidFilePath}`); if (fs.existsSync(pidFilePath)) { fs.unlinkSync(pidFilePath); } @@ -306,14 +306,14 @@ process.on('SIGHUP', () => { // Handle uncaught errors process.on('uncaughtException', error => { - logger('Uncaught exception:', error); + logger?.('Uncaught exception:', error); }); process.on('unhandledRejection', error => { - logger('Unhandled rejection:', error); + logger?.('Unhandled rejection:', error); }); // Start the server const started = startSocketServer().catch(error => { - logger('Failed to start daemon server:', error); + logger?.('Failed to start daemon server:', error); process.exit(1); }); diff --git a/src/daemon/utils.ts b/src/daemon/utils.ts index 83f69d265..d5f7ee47e 100644 --- a/src/daemon/utils.ts +++ b/src/daemon/utils.ts @@ -77,13 +77,13 @@ export function getPidFilePath(sessionId: string) { export function getDaemonPid(sessionId: string) { try { const pidFile = getPidFilePath(sessionId); - logger(`Daemon pid file ${pidFile} sessionId=${sessionId}`); + logger?.(`Daemon pid file ${pidFile} sessionId=${sessionId}`); if (!fs.existsSync(pidFile)) { return null; } const pidContent = fs.readFileSync(pidFile, 'utf-8'); const pid = parseInt(pidContent.trim(), 10); - logger(`Daemon pid: ${pid}`); + logger?.(`Daemon pid: ${pid}`); if (isNaN(pid)) { return null; } diff --git a/src/formatters/IssueFormatter.ts b/src/formatters/IssueFormatter.ts index 4f1668c31..0c31447cd 100644 --- a/src/formatters/IssueFormatter.ts +++ b/src/formatters/IssueFormatter.ts @@ -156,7 +156,7 @@ export class IssueFormatter { const markdownDescription = this.#issue.getDescription(); const filename = markdownDescription?.file; if (!filename) { - logger(`no description found for issue:` + this.#issue.code()); + logger?.(`no description found for issue:` + this.#issue.code()); return undefined; } @@ -166,7 +166,7 @@ export class IssueFormatter { const rawMarkdown = ISSUE_UTILS.getIssueDescription(filename); if (!rawMarkdown) { - logger(`no markdown ${filename} found for issue:` + this.#issue.code()); + logger?.(`no markdown ${filename} found for issue:` + this.#issue.code()); return undefined; } @@ -180,12 +180,12 @@ export class IssueFormatter { const title = DevTools.MarkdownIssueDescription.findTitleFromMarkdownAst(markdownAst); if (!title) { - logger('cannot read issue title from ' + filename); + logger?.('cannot read issue title from ' + filename); return undefined; } return title; } catch { - logger('error parsing markdown for issue ' + this.#issue.code()); + logger?.('error parsing markdown for issue ' + this.#issue.code()); return undefined; } } diff --git a/src/index.ts b/src/index.ts index d3c072fa4..fdd5790c1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -69,7 +69,7 @@ export async function createMcpServer( ); context?.setRoots(roots.roots); } catch (e) { - logger('Failed to list roots', e); + logger?.('Failed to list roots', e); } }; diff --git a/src/logger.ts b/src/logger.ts index e86af2efc..d54038930 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -7,6 +7,7 @@ import fs from 'node:fs'; import {debug} from './third_party/index.js'; +import type {Logger} from './types.js'; const mcpDebugNamespace = 'mcp:log'; @@ -44,4 +45,4 @@ export function flushLogs( }); } -export const logger = debug(mcpDebugNamespace); +export const logger: Logger = debug(mcpDebugNamespace) as Logger; diff --git a/src/telemetry/ClearcutLogger.ts b/src/telemetry/ClearcutLogger.ts index 8586533e7..daa964603 100644 --- a/src/telemetry/ClearcutLogger.ts +++ b/src/telemetry/ClearcutLogger.ts @@ -176,7 +176,7 @@ export class ClearcutLogger { await this.#persistence.saveState(state); } } catch (err) { - logger('Error in logDailyActiveIfNeeded:', err); + logger?.('Error in logDailyActiveIfNeeded:', err); } } diff --git a/src/telemetry/WatchdogClient.ts b/src/telemetry/WatchdogClient.ts index 36090541f..46b196cf7 100644 --- a/src/telemetry/WatchdogClient.ts +++ b/src/telemetry/WatchdogClient.ts @@ -59,10 +59,10 @@ export class WatchdogClient { }); this.#childProcess.unref(); this.#childProcess.on('error', err => { - logger('Watchdog process error:', err); + logger?.('Watchdog process error:', err); }); this.#childProcess.on('exit', (code, signal) => { - logger(`Watchdog exited with code ${code} and signal ${signal}`); + logger?.(`Watchdog exited with code ${code} and signal ${signal}`); }); } @@ -76,10 +76,10 @@ export class WatchdogClient { const line = JSON.stringify(message) + '\n'; this.#childProcess.stdin.write(line); } catch (err) { - logger('Failed to write to watchdog stdin', err); + logger?.('Failed to write to watchdog stdin', err); } } else { - logger('Watchdog stdin not available, dropping message'); + logger?.('Watchdog stdin not available, dropping message'); } } } diff --git a/src/telemetry/persistence.ts b/src/telemetry/persistence.ts index ba28a2a3c..e0c81610a 100644 --- a/src/telemetry/persistence.ts +++ b/src/telemetry/persistence.ts @@ -67,7 +67,7 @@ export class FilePersistence implements Persistence { const content = await fs.readFile(filePath, 'utf-8'); return JSON.parse(content) as LocalState; } catch (error) { - logger(`Failed to read telemetry state from ${filePath}:`, error); + logger?.(`Failed to read telemetry state from ${filePath}:`, error); void ClearcutLogger.get()?.logServerError({ errorCode: ErrorCode.ERROR_CODE_PERSISTENCE_FILE_READ_FAILED, }); @@ -84,7 +84,7 @@ export class FilePersistence implements Persistence { await fs.writeFile(filePath, JSON.stringify(state, null, 2), 'utf-8'); } catch (error) { // Ignore errors during state saving to avoid crashing the server - logger(`Failed to save telemetry state to ${filePath}:`, error); + logger?.(`Failed to save telemetry state to ${filePath}:`, error); void ClearcutLogger.get()?.logServerError({ errorCode: ErrorCode.ERROR_CODE_PERSISTENCE_FILE_SAVE_FAILED, }); diff --git a/src/telemetry/watchdog/ClearcutSender.ts b/src/telemetry/watchdog/ClearcutSender.ts index 0a3678e02..6bf31745a 100644 --- a/src/telemetry/watchdog/ClearcutSender.ts +++ b/src/telemetry/watchdog/ClearcutSender.ts @@ -76,7 +76,7 @@ export class ClearcutSender { app_version: this.#appVersion, os_type: this.#osType, }; - logger('Enqueing telemetry event', JSON.stringify(eventToSend, null, 2)); + logger?.('Enqueing telemetry event', JSON.stringify(eventToSend, null, 2)); this.#addToBuffer(eventToSend); if (!this.#timerStarted) { @@ -101,9 +101,9 @@ export class ClearcutSender { this.#finalFlush(), new Promise(resolve => setTimeout(resolve, SHUTDOWN_TIMEOUT_MS)), ]); - logger('Final flush completed'); + logger?.('Final flush completed'); } catch (error) { - logger('Final flush failed:', error); + logger?.('Final flush failed:', error); } } @@ -136,7 +136,7 @@ export class ClearcutSender { ); } } else if (result.isPermanentError) { - logger( + logger?.( 'Permanent error, dropped batch of', eventsToSend.length, 'events', @@ -149,7 +149,7 @@ export class ClearcutSender { } catch (error) { // Safety catch for unexpected errors, requeue events this.#buffer = [...eventsToSend, ...this.#buffer]; - logger('Flush failed unexpectedly:', error); + logger?.('Flush failed unexpectedly:', error); } finally { this.#isFlushing = false; this.#scheduleFlush(nextDelayMs); @@ -159,7 +159,7 @@ export class ClearcutSender { #addToBuffer(event: ChromeDevToolsMcpExtension): void { if (this.#buffer.length >= MAX_BUFFER_SIZE) { this.#buffer.shift(); - logger('Telemetry buffer overflow: dropped oldest event'); + logger?.('Telemetry buffer overflow: dropped oldest event'); } this.#buffer.push({ event, @@ -168,13 +168,13 @@ export class ClearcutSender { } #scheduleFlush(delayMs: number): void { - logger(`Scheduling flush in ${delayMs}`); + logger?.(`Scheduling flush in ${delayMs}`); if (this.#flushTimer) { clearTimeout(this.#flushTimer); } this.#flushTimer = setTimeout(() => { this.#flush().catch(err => { - logger('Flush error:', err); + logger?.('Flush error:', err); }); }, delayMs); } @@ -184,7 +184,7 @@ export class ClearcutSender { isPermanentError?: boolean; nextRequestWaitMs?: number; }> { - logger(`Sending batch of ${events.length}`); + logger?.(`Sending batch of ${events.length}`); const requestBody: LogRequest = { log_source: LOG_SOURCE, request_time_ms: Date.now().toString(), @@ -227,7 +227,7 @@ export class ClearcutSender { return {success: false}; } - logger('Telemetry permanent error:', status); + logger?.('Telemetry permanent error:', status); return {success: false, isPermanentError: true}; } catch { clearTimeout(timeoutId); diff --git a/src/telemetry/watchdog/main.ts b/src/telemetry/watchdog/main.ts index 21fbe8c36..97c38b1ef 100644 --- a/src/telemetry/watchdog/main.ts +++ b/src/telemetry/watchdog/main.ts @@ -100,7 +100,7 @@ function main() { }); }; - logger( + logger?.( 'Watchdog started', JSON.stringify( { @@ -129,15 +129,15 @@ function main() { } isShuttingDown = true; - logger(`Parent death detected (${reason}). Sending shutdown event...`); + logger?.(`Parent death detected (${reason}). Sending shutdown event...`); sender .sendShutdownEvent() .then(() => { - logger('Shutdown event sent. Exiting.'); + logger?.('Shutdown event sent. Exiting.'); exit(0); }) .catch(err => { - logger('Failed to send shutdown event', err); + logger?.('Failed to send shutdown event', err); exit(1); }); } @@ -162,7 +162,7 @@ function main() { sender.enqueueEvent(msg.payload); } } catch (err) { - logger('Failed to parse IPC message', err); + logger?.('Failed to parse IPC message', err); } }); } diff --git a/src/tools/input.ts b/src/tools/input.ts index ee4973437..248585634 100644 --- a/src/tools/input.ts +++ b/src/tools/input.ts @@ -34,7 +34,7 @@ const submitKeySchema = zod ); function handleActionError(error: unknown, uid: string) { - logger('failed to act using a locator', error); + logger?.('failed to act using a locator', error); throw new Error( `Failed to interact with the element with uid ${uid}. The element did not become interactive within the configured timeout.`, { diff --git a/src/tools/pages.ts b/src/tools/pages.ts index 9c501414f..0a74aa228 100644 --- a/src/tools/pages.ts +++ b/src/tools/pages.ts @@ -40,7 +40,7 @@ async function navigateWithInterception( if (isAllowed) { void interceptedRequest.continue(); } else { - logger(`Blocking request to: ${requestUrl}`); + logger?.(`Blocking request to: ${requestUrl}`); void interceptedRequest.abort('blockedbyclient'); } }; @@ -49,7 +49,7 @@ async function navigateWithInterception( if (allowList) { page.pptrPage.off('request', requestHandler); await page.pptrPage.setRequestInterception(false).catch(error => { - logger(`Failed to disable request interception`, error); + logger?.(`Failed to disable request interception`, error); }); } }; @@ -372,7 +372,7 @@ export const navigatePage = definePageTool(args => { await page.pptrPage .removeScriptToEvaluateOnNewDocument(initScriptId) .catch(error => { - logger(`Failed to remove init script`, error); + logger?.(`Failed to remove init script`, error); }); } } @@ -456,7 +456,7 @@ export const handleDialog = definePageTool({ await dialog.accept(request.params.promptText); } catch (err) { // Likely already handled by the user outside of MCP. - logger(err); + logger?.(err); } response.appendResponseLine('Successfully accepted the dialog'); break; @@ -466,7 +466,7 @@ export const handleDialog = definePageTool({ await dialog.dismiss(); } catch (err) { // Likely already handled. - logger(err); + logger?.(err); } response.appendResponseLine('Successfully dismissed the dialog'); break; diff --git a/src/tools/performance.ts b/src/tools/performance.ts index 3526fae96..1d3c527fb 100644 --- a/src/tools/performance.ts +++ b/src/tools/performance.ts @@ -234,7 +234,7 @@ async function stopTracingAndAppendOutput( /** We tell CrUXManager to fetch data so it's available when DevTools.PerformanceTraceFormatter is invoked */ async function populateCruxData(result: TraceResult): Promise { - logger('populateCruxData called'); + logger?.('populateCruxData called'); const cruxManager = DevTools.CrUXManager.CrUXManager.instance(); // go/jtfbx. Yes, we're aware this API key is public. ;) cruxManager.setEndpointForTesting( @@ -254,17 +254,17 @@ async function populateCruxData(result: TraceResult): Promise { const urlSet = new Set(urls); if (urlSet.size === 0) { - logger('No URLs found for CrUX data'); + logger?.('No URLs found for CrUX data'); return; } - logger( + logger?.( `Fetching CrUX data for ${urlSet.size} URLs: ${Array.from(urlSet).join(', ')}`, ); const cruxData = await Promise.all( Array.from(urlSet).map(async url => { const data = await cruxManager.getFieldDataForPage(url); - logger(`CrUX data for ${url}: ${data ? 'found' : 'not found'}`); + logger?.(`CrUX data for ${url}: ${data ? 'found' : 'not found'}`); return data; }), ); diff --git a/src/trace-processing/parse.ts b/src/trace-processing/parse.ts index 277f4103b..6d864cce7 100644 --- a/src/trace-processing/parse.ts +++ b/src/trace-processing/parse.ts @@ -67,7 +67,7 @@ export async function parseRawTraceBuffer( }; } catch (e) { const errorText = e instanceof Error ? e.message : JSON.stringify(e); - logger(`Unexpected error parsing trace: ${errorText}`); + logger?.(`Unexpected error parsing trace: ${errorText}`); return { error: errorText, }; diff --git a/src/types.ts b/src/types.ts index 2107b558f..f2ce8ad54 100644 --- a/src/types.ts +++ b/src/types.ts @@ -33,3 +33,5 @@ export interface EmulationSettings { viewport?: Viewport; extraHttpHeaders?: Record; } + +export type Logger = ((...args: unknown[]) => void) | undefined;