diff --git a/packages/evm-wallet-experiment/docker/docker-compose.yml b/packages/evm-wallet-experiment/docker/docker-compose.yml index 842866e526..c07d1ec2d1 100644 --- a/packages/evm-wallet-experiment/docker/docker-compose.yml +++ b/packages/evm-wallet-experiment/docker/docker-compose.yml @@ -13,7 +13,7 @@ x-kernel-standard: &kernel-standard networks: [ocap-e2e] volumes: - ocap-run:/run/ocap - - ocap-logs:/logs + - ../logs:/logs entrypoint: - node - '--conditions' @@ -46,7 +46,7 @@ services: ports: ['8545:8545'] volumes: - ocap-run:/run/ocap - - ocap-logs:/logs + - ../logs:/logs healthcheck: test: ['CMD', 'test', '-f', '/run/ocap/contracts.json'] interval: 3s @@ -211,4 +211,3 @@ networks: volumes: ocap-run: - ocap-logs: diff --git a/packages/evm-wallet-experiment/docker/entrypoint-kernel.mjs b/packages/evm-wallet-experiment/docker/entrypoint-kernel.mjs index aca16dacb7..1bff567268 100644 --- a/packages/evm-wallet-experiment/docker/entrypoint-kernel.mjs +++ b/packages/evm-wallet-experiment/docker/entrypoint-kernel.mjs @@ -19,7 +19,12 @@ import { NodejsPlatformServices } from '@metamask/kernel-node-runtime'; import { startRpcSocketServer } from '@metamask/kernel-node-runtime/daemon'; import { makeSQLKernelDatabase } from '@metamask/kernel-store/sqlite/nodejs'; import { Kernel } from '@metamask/ocap-kernel'; -import { mkdirSync, unlinkSync, writeFileSync } from 'node:fs'; +import { + createWriteStream, + mkdirSync, + unlinkSync, + writeFileSync, +} from 'node:fs'; import { dirname } from 'node:path'; const NAME = process.env.SERVICE_NAME ?? 'kernel'; @@ -27,6 +32,19 @@ const { SOCKET_PATH } = process.env; const QUIC_ADDR = process.env.QUIC_LISTEN_ADDRESS; const { READY_FILE } = process.env; +// Tee stdout/stderr to a file so logs are accessible on the host via the +// bind-mounted logs directory even after the container exits. +mkdirSync('/logs', { recursive: true }); +const logStream = createWriteStream(`/logs/${NAME}.log`, { flags: 'w' }); +for (const stream of [process.stdout, process.stderr]) { + const original = stream.write.bind(stream); + // eslint-disable-next-line jsdoc/require-jsdoc + stream.write = (chunk, ...args) => { + logStream.write(chunk); + return original(chunk, ...args); + }; +} + if (!SOCKET_PATH) { console.error(`[${NAME}] FATAL: SOCKET_PATH is required`); process.exit(1); diff --git a/packages/evm-wallet-experiment/package.json b/packages/evm-wallet-experiment/package.json index 6f7550cc86..7036dd9234 100644 --- a/packages/evm-wallet-experiment/package.json +++ b/packages/evm-wallet-experiment/package.json @@ -60,9 +60,10 @@ "docker:compose:interactive": "node docker/run-interactive-compose.mjs", "docker:build": "yarn docker:compose build", "docker:build:force": "yarn docker:compose build --no-cache", - "docker:up": "yarn docker:compose up", + "docker:ensure-logs": "mkdir -p logs", + "docker:up": "yarn docker:ensure-logs && yarn docker:compose up", "docker:down": "yarn docker:compose down", - "docker:interactive:up": "node docker/run-interactive-compose.mjs up --build", + "docker:interactive:up": "yarn docker:ensure-logs && node docker/run-interactive-compose.mjs up --build", "docker:interactive:down": "node docker/run-interactive-compose.mjs down", "docker:setup:wallets": "yarn tsx test/e2e/docker/setup-wallets.ts", "docker:interactive:setup": "node docker/interactive-setup.mjs", diff --git a/packages/evm-wallet-experiment/vitest.config.docker.ts b/packages/evm-wallet-experiment/vitest.config.docker.ts index a3b09d5818..ce3808e60d 100644 --- a/packages/evm-wallet-experiment/vitest.config.docker.ts +++ b/packages/evm-wallet-experiment/vitest.config.docker.ts @@ -14,5 +14,10 @@ export default defineConfig({ testTimeout: 180_000, // No setupFiles — we need real fetch (not mocked) and no lockdown shims. setupFiles: [], + // Write structured results to logs/ so agents and CI can inspect failures + // without parsing terminal output. Kernel service logs land alongside in + // logs/.log via the entrypoint tee. + reporters: ['default', 'json'], + outputFile: 'logs/test-results.json', }, });