Hi I tested it from the market place, great work thank you!
I was working on something similar.
What I miss in your extension, are full fledged params from the logs, that are expanded. This definitely helped many times to debug a situation.
I also created a logger that was piping output into a file and this on each fresh requests. Find attached my logger that was actually doing the right job in that terms.
// disable all eslint rules for this file
/* eslint-disable */
const CDP = require("chrome-remote-interface");
const fs = require("fs");
const path = require("path");
interface LogEntry {
timestamp: number;
level: string;
text: string;
}
interface ConsoleMessage {
text: string;
type?: string;
args?: any[];
stackTrace?: {
url: string;
lineNumber: number;
columnNumber: number;
}[];
}
interface CallFrame {
url: string;
functionName: string;
lineNumber: number;
columnNumber: number;
}
interface StackTrace {
callFrames: CallFrame[];
}
interface ConsoleAPICall {
type: string;
args: Array<{
type: string;
value?: string;
description?: string;
}>;
stackTrace?: StackTrace;
}
interface ExceptionDetails {
exceptionId: number;
text: string;
lineNumber: number;
columnNumber: number;
scriptId: string;
url: string;
stackTrace: StackTrace;
exception: {
className: string;
description: string;
};
}
const getLogPath = (): string => {
const providedPath = process.argv[2];
if (!providedPath) return "./browserConsole.log";
return path.isAbsolute(providedPath) ? providedPath : path.resolve(process.cwd(), providedPath);
};
const captureConsoleLogs = async ({
outputPath = getLogPath(),
clearOnRefresh = true,
}: {
outputPath?: string;
clearOnRefresh?: boolean;
} = {}) => {
try {
const client = await CDP();
const { Log, Console, Runtime, Page } = client;
let writeStream = fs.createWriteStream(outputPath, {
flags: clearOnRefresh ? "w" : "a",
});
console.log(`Logging to: ${outputPath}${clearOnRefresh ? " (clearing file)" : ""}`);
await Promise.all([Log.enable(), Console.enable(), Runtime.enable(), Page.enable()]);
// Add handler for page refresh
Page.frameNavigated(() => {
if (clearOnRefresh) {
// Close existing stream and create a new one
writeStream.end();
writeStream = fs.createWriteStream(outputPath, { flags: "w" });
console.log(`Page refreshed - Clearing log file: ${outputPath}`);
}
});
Log.entryAdded(({ entry }: { entry: LogEntry }) => {
writeStream.write(`${entry.timestamp} ${entry.level}: ${entry.text}\n`);
});
const formatValue = (arg: any): string => {
if (!arg) return String(arg);
if (arg.type === "object" && arg.preview) {
// Extract just the properties into a clean object
const cleanObject = arg.preview.properties?.reduce((obj: any, prop: any) => {
obj[prop.name] = prop.value;
return obj;
}, {});
return JSON.stringify(cleanObject, null, 2);
}
return arg.value || arg.description || JSON.stringify(arg, null, 2);
};
// Only handle errors in Console.messageAdded
Console.messageAdded(({ message }: { message: ConsoleMessage }) => {
if (message.type === "error" && message.args?.[0]?.description) {
writeStream.write(
`${new Date().toISOString()} error: ${message.args[0].description}\n` +
`${message.stackTrace?.map((frame) => ` at ${frame.url}:${frame.lineNumber}:${frame.columnNumber}`).join("\n") || ""}\n`
);
}
});
// Handle all console API calls here
Runtime.consoleAPICalled(({ type, args, stackTrace }: ConsoleAPICall) => {
const timestamp = new Date().toISOString();
const location = stackTrace?.callFrames?.[0];
const locationStr = location ? ` (${location.url}:${location.lineNumber}:${location.columnNumber})` : "";
if (type === "error") {
const errorMessage = formatValue(args?.[0]) || "Unknown error";
writeStream.write(
`${timestamp} error: ${errorMessage}\n` +
`${stackTrace?.callFrames?.map((frame) => ` at ${frame.url}:${frame.lineNumber}:${frame.columnNumber}`).join("\n") || ""}\n`
);
} else {
const formattedArgs = args?.map(formatValue).join(" ") || "No message";
writeStream.write(`${timestamp} ${type}${locationStr}: ${formattedArgs}\n`);
}
});
Runtime.exceptionThrown(({ exceptionDetails }: { exceptionDetails: ExceptionDetails }) => {
const errorMessage = exceptionDetails.exception?.description || exceptionDetails.text;
const stackTrace =
exceptionDetails.stackTrace?.callFrames
?.map((frame) => ` at ${frame.url}:${frame.lineNumber}:${frame.columnNumber}`)
.join("\n") || "";
writeStream.write(`${new Date().toISOString()} error: ${errorMessage}\n` + `Stack trace:\n${stackTrace}\n`);
});
process.on("SIGINT", () => {
writeStream.end();
client.close();
process.exit();
});
} catch (error) {
console.error("Failed to connect to Chrome:", error);
process.exit(1);
}
};
// Usage:
captureConsoleLogs().catch(console.error);
{
"name": "logspiper",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"start:piper": "ts-node piper.ts",
"start:browser": "\"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\" --remote-debugging-port=9222"
},
"dependencies": {
"@types/chrome-remote-interface": "^0.31.14",
"@types/node": "^22.10.7",
"chrome-remote-interface": "^0.33.2",
"ts-node": "^10.9.2"
}
}