add cdp jsonl logging and update log docs

This commit is contained in:
Tommy D. Rossi
2026-01-26 13:28:18 +01:00
parent 4d2606253b
commit a64fc2d8a0
9 changed files with 156 additions and 7 deletions
+65
View File
@@ -0,0 +1,65 @@
import fs from 'node:fs'
import path from 'node:path'
import { LOG_CDP_FILE_PATH } from './utils.js'
export type CdpLogEntry = {
timestamp: string
direction: 'from-playwright' | 'to-playwright' | 'from-extension' | 'to-extension'
clientId?: string
source?: 'extension' | 'server'
message: unknown
}
export type CdpLogger = {
log(entry: CdpLogEntry): void
logFilePath: string
}
const DEFAULT_MAX_STRING_LENGTH = Number(process.env.PLAYWRITER_CDP_LOG_MAX_STRING_LENGTH || 2000)
function truncateString(value: string, maxLength: number): string {
if (value.length <= maxLength) {
return value
}
const truncatedCount = value.length - maxLength
return `${value.slice(0, maxLength)}…[truncated ${truncatedCount} chars]`
}
function createTruncatingReplacer({ maxStringLength }: { maxStringLength: number }) {
const seen = new WeakSet<object>()
return (_key: string, value: unknown) => {
if (typeof value === 'string') {
return truncateString(value, maxStringLength)
}
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[Circular]'
}
seen.add(value)
}
return value
}
}
export function createCdpLogger({ logFilePath, maxStringLength }: { logFilePath?: string; maxStringLength?: number } = {}): CdpLogger {
const resolvedLogFilePath = logFilePath || LOG_CDP_FILE_PATH
const logDir = path.dirname(resolvedLogFilePath)
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true })
}
fs.writeFileSync(resolvedLogFilePath, '')
let queue: Promise<void> = Promise.resolve()
const maxLength = maxStringLength ?? DEFAULT_MAX_STRING_LENGTH
const log = (entry: CdpLogEntry): void => {
const replacer = createTruncatingReplacer({ maxStringLength: maxLength })
const line = JSON.stringify(entry, replacer)
queue = queue.then(() => fs.promises.appendFile(resolvedLogFilePath, `${line}\n`))
}
return {
log,
logFilePath: resolvedLogFilePath,
}
}
+52 -1
View File
@@ -10,6 +10,7 @@ import type { ExtensionMessage, ExtensionEventMessage } from './protocol.js'
import pc from 'picocolors'
import { EventEmitter } from 'node:events'
import { VERSION } from './utils.js'
import { createCdpLogger, type CdpLogEntry, type CdpLogger } from './cdp-log.js'
type ConnectedTarget = {
sessionId: string
@@ -69,10 +70,27 @@ export type RelayServer = {
off<K extends keyof RelayServerEvents>(event: K, listener: RelayServerEvents[K]): void
}
export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.0.0.1', token, logger }: { port?: number; host?: string; token?: string; logger?: { log(...args: any[]): void; error(...args: any[]): void } } = {}): Promise<RelayServer> {
export async function startPlayWriterCDPRelayServer({
port = 19988,
host = '127.0.0.1',
token,
logger,
cdpLogger,
}: {
port?: number
host?: string
token?: string
logger?: { log(...args: any[]): void; error(...args: any[]): void }
cdpLogger?: CdpLogger
} = {}): Promise<RelayServer> {
const emitter = new EventEmitter()
const connectedTargets = new Map<string, ConnectedTarget>()
const resolvedCdpLogger = cdpLogger || createCdpLogger()
const logCdpJson = (entry: CdpLogEntry) => {
resolvedCdpLogger.log(entry)
}
const playwrightClients = new Map<string, PlaywrightClient>()
let extensionWs: WSContext | null = null
@@ -179,6 +197,14 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
? { ...message, __serverGenerated: true }
: message
logCdpJson({
timestamp: new Date().toISOString(),
direction: 'to-playwright',
clientId,
source,
message: messageToSend,
})
if ('method' in message) {
logCdpMessage({
direction: 'to-playwright',
@@ -212,6 +238,18 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
const id = ++extensionMessageId
const message = { id, method, params }
if (method === 'forwardCDPCommand' && params?.method) {
logCdpJson({
timestamp: new Date().toISOString(),
direction: 'to-extension',
message: {
method: params.method,
sessionId: params.sessionId,
params: params.params,
},
})
}
extensionWs.send(JSON.stringify(message))
return new Promise((resolve, reject) => {
@@ -584,6 +622,13 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
const { id, sessionId, method, params, source } = message
logCdpJson({
timestamp: new Date().toISOString(),
direction: 'from-playwright',
clientId,
message,
})
logCdpMessage({
direction: 'from-playwright',
clientId,
@@ -820,6 +865,12 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
const { method, params, sessionId } = extensionEvent.params
logCdpJson({
timestamp: new Date().toISOString(),
direction: 'from-extension',
message: { method, params, sessionId },
})
logCdpMessage({
direction: 'from-extension',
method,
+4 -2
View File
@@ -4,7 +4,7 @@ import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { cac } from '@xmorse/cac'
import { VERSION, LOG_FILE_PATH } from './utils.js'
import { VERSION, LOG_FILE_PATH, LOG_CDP_FILE_PATH } from './utils.js'
import { ensureRelayServer, RELAY_PORT, waitForExtension } from './relay-client.js'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
@@ -299,6 +299,7 @@ cli
console.log(` Port: ${RELAY_PORT}`)
console.log(` Token: ${token ? '(configured)' : '(none)'}`)
console.log(` Logs: ${logger.logFilePath}`)
console.log(` CDP Logs: ${LOG_CDP_FILE_PATH}`)
console.log('')
console.log(`CDP endpoint: http://${options.host}:${RELAY_PORT}${token ? '?token=<token>' : ''}`)
console.log('')
@@ -320,7 +321,8 @@ cli
cli
.command('logfile', 'Print the path to the relay server log file')
.action(() => {
console.log(LOG_FILE_PATH)
console.log(`relay: ${LOG_FILE_PATH}`)
console.log(`cdp: ${LOG_CDP_FILE_PATH}`)
})
cli
+7 -1
View File
@@ -99,7 +99,13 @@ playwriter logfile # prints the log file path
# typically: /tmp/playwriter/relay-server.log (Linux/macOS) or %TEMP%\playwriter\relay-server.log (Windows)
```
The log file contains logs from the extension, MCP and WS server together with all CDP events. The file is recreated every time the server starts. For debugging internal playwriter errors, read this file with grep/rg to find relevant lines.
The relay log contains logs from the extension, MCP and WS server. A separate CDP JSONL log is created alongside it (see `playwriter logfile`) with all CDP commands/responses and events, with long strings truncated. Both files are recreated every time the server starts. For debugging internal playwriter errors, read these files with grep/rg to find relevant lines.
Example: summarize CDP traffic counts by direction + method:
```bash
jq -r '.direction + "\t" + (.message.method // "response")' /tmp/playwriter/cdp.jsonl | uniq -c
```
If you find a bug, you can create a gh issue using `gh issue create -R remorses/playwriter --title title --body body`. Ask for user confirmation before doing this.
+2
View File
@@ -1,5 +1,6 @@
import { startPlayWriterCDPRelayServer } from './cdp-relay.js'
import { createFileLogger } from './create-logger.js'
import { LOG_CDP_FILE_PATH } from './utils.js'
process.title = 'playwriter-ws-server'
@@ -25,6 +26,7 @@ export async function startServer({ port = 19988, host = '127.0.0.1', token }: {
console.log('CDP Relay Server running. Press Ctrl+C to stop.')
console.log('Logs are being written to:', logger.logFilePath)
console.log('CDP logs are being written to:', LOG_CDP_FILE_PATH)
process.on('SIGINT', () => {
console.log('\nShutting down...')
+3 -1
View File
@@ -9,7 +9,9 @@ export function getCdpUrl({ port = 19988, host = '127.0.0.1', token }: { port?:
return `ws://${host}:${port}/cdp/${id}${queryString}`
}
export const LOG_FILE_PATH = process.env.PLAYWRITER_LOG_FILE_PATH || path.join(os.tmpdir(), 'playwriter', 'relay-server.log')
const LOG_BASE_DIR = os.platform() === 'win32' ? os.tmpdir() : '/tmp'
export const LOG_FILE_PATH = process.env.PLAYWRITER_LOG_FILE_PATH || path.join(LOG_BASE_DIR, 'playwriter', 'relay-server.log')
export const LOG_CDP_FILE_PATH = process.env.PLAYWRITER_CDP_LOG_FILE_PATH || path.join(path.dirname(LOG_FILE_PATH), 'cdp.jsonl')
const packageJsonPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'package.json')
export const VERSION = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')).version as string