add cdp jsonl logging and update log docs
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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,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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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...')
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user