Build Logging
Each deploy build emits granular per-step logs to the lk-build-logs Axiom dataset. These run inside the Docker container (Node.js) and provide detailed visibility into the transpile → bundle → deploy pipeline.
This is separate from the wide event system which captures one deploy:complete event per build. Build logs capture many events — one per pipeline step, with timing and detail payloads.
Architecture
Section titled “Architecture”CONTAINER (Node.js) AXIOM─────────────────── ─────createAxiomBuffer() ──buffer──▶ flush() ──▶ lk-build-logs │ ├─ push({ phase: 'transpile', status: 'start' }) ├─ push({ phase: 'transpile', message: 'compiled 42 sections' }) ├─ push({ phase: 'transpile', status: 'done', durationMs: 12340 }) ├─ push({ phase: 'bundle', status: 'start' }) └─ ...Event shape
Section titled “Event shape”Every build log event follows the AxiomEvent interface:
interface AxiomEvent { buildId: string // UUID — primary key for queries slug?: string // client slug _time: string // ISO timestamp level: 'debug' | 'info' | 'warn' | 'error' phase?: string // pipeline step: transpile, bundle, deploy, complete, failed message: string // human-readable description step?: string // sub-step within phase status?: string // start, done, error durationMs?: number // step duration detail?: Record<string, unknown> // arbitrary payload}Buffered ingest
Section titled “Buffered ingest”The createAxiomBuffer() factory from scripts/lib/axiom.ts batches events before sending to reduce HTTP overhead:
| Parameter | Value |
|---|---|
| Max buffer size | 20 events |
| Max buffer age | 3 seconds |
| Urgent flush | On level: 'error', status: 'start', or status: 'done' |
Urgent events (errors, phase start/done) flush the buffer immediately so the dashboard and tail CLI see progress without delay. All other events batch up to 20 or 3 seconds.
The final flush() is awaited in the pipeline’s finally block to ensure all events are sent before the container exits.
Token configuration
Section titled “Token configuration”| Env Var | Dataset | Scope |
|---|---|---|
AXIOM_BUILD_API_TOKEN | lk-build-logs | API token for Axiom ingest/query |
AXIOM_BUILD_DATASET_NAME | lk-build-logs | Dataset name (defaults to lk-build-logs) |
The Worker passes the token to the container via buildContainerPayload(). The container creates its own AxiomBuffer instance with this token.
Set locally in .mise.local.toml:
[env]AXIOM_BUILD_API_TOKEN = "xaat-..."AXIOM_BUILD_DATASET_NAME = "lk-build-logs"Querying build logs
Section titled “Querying build logs”Worker endpoint
Section titled “Worker endpoint”GET /builds/:buildId/logs?limit=1000&startTime=...&endTime=...Authorization: Bearer {CLOUDFLARE_API_TOKEN}Returns { events: AxiomEvent[] }. Used by the dashboard build detail view.
CLI — pnpm deploy:logs
Section titled “CLI — pnpm deploy:logs”pnpm deploy:logs <buildId> # fetch + pretty-print all logspnpm deploy:logs <buildId> --follow # tail mode (live build)pnpm deploy:logs <buildId> --json # raw JSON outputThe --follow mode uses tailAxiomLogs() which polls every 2 seconds and stops when it sees a terminal phase (complete or failed). Timeout: 15 minutes.
APL queries in Axiom UI
Section titled “APL queries in Axiom UI”Find all failed builds in the last 24 hours:
['lk-build-logs']| where level == "error"| summarize count() by buildId| sort by count_ descFind slow transpile phases:
['lk-build-logs']| where phase == "transpile" and status == "done"| where durationMs > 30000| project buildId, slug, durationMs, _time| sort by durationMs descKey files
Section titled “Key files”| File | Purpose |
|---|---|
scripts/lib/axiom.ts | createAxiomBuffer(), queryAxiomLogs(), tailAxiomLogs() |
scripts/deploy-logs.ts | CLI log viewer (pnpm deploy:logs) |
packages/cf-client-deploy/src/worker.ts | /builds/:id/logs endpoint |