mirror of
https://github.com/ChromeDevTools/chrome-devtools-mcp.git
synced 2026-09-14 19:45:30 +08:00
refactor: change type of logger (#2165)
This change aligns most of our logger allowing them to be omitted when needed meaning code inside the args will not be executed unconditionally - example `logger?.(JSON.stringy(X))`. Currently only changes the type, while in a follow up I will remove the `debug` package and update the file logs.
This commit is contained in:
+10
-10
@@ -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<string, BrowserContext>();
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -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<DevToolsData> {
|
||||
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 {};
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ export class PageCollector<T> {
|
||||
}
|
||||
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<T> {
|
||||
}
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -262,7 +262,7 @@ export class TextSnapshot {
|
||||
collect(node);
|
||||
}
|
||||
} catch (e) {
|
||||
logger(
|
||||
logger?.(
|
||||
`Failed to collect descendants for backend node ${backendNodeId}`,
|
||||
e,
|
||||
);
|
||||
|
||||
+3
-3
@@ -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}`;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<void> {
|
||||
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));
|
||||
|
||||
+4
-4
@@ -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<void> {
|
||||
}
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -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<void>(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);
|
||||
});
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
@@ -176,7 +176,7 @@ export class ClearcutLogger {
|
||||
await this.#persistence.saveState(state);
|
||||
}
|
||||
} catch (err) {
|
||||
logger('Error in logDailyActiveIfNeeded:', err);
|
||||
logger?.('Error in logDailyActiveIfNeeded:', err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -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.`,
|
||||
{
|
||||
|
||||
+5
-5
@@ -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;
|
||||
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
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;
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -33,3 +33,5 @@ export interface EmulationSettings {
|
||||
viewport?: Viewport;
|
||||
extraHttpHeaders?: Record<string, string>;
|
||||
}
|
||||
|
||||
export type Logger = ((...args: unknown[]) => void) | undefined;
|
||||
|
||||
Reference in New Issue
Block a user