Skip to content

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.

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' })
└─ ...

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
}

The createAxiomBuffer() factory from scripts/lib/axiom.ts batches events before sending to reduce HTTP overhead:

ParameterValue
Max buffer size20 events
Max buffer age3 seconds
Urgent flushOn 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.

Env VarDatasetScope
AXIOM_BUILD_API_TOKENlk-build-logsAPI token for Axiom ingest/query
AXIOM_BUILD_DATASET_NAMElk-build-logsDataset 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"
GET /builds/:buildId/logs?limit=1000&startTime=...&endTime=...
Authorization: Bearer {CLOUDFLARE_API_TOKEN}

Returns { events: AxiomEvent[] }. Used by the dashboard build detail view.

Terminal window
pnpm deploy:logs <buildId> # fetch + pretty-print all logs
pnpm deploy:logs <buildId> --follow # tail mode (live build)
pnpm deploy:logs <buildId> --json # raw JSON output

The --follow mode uses tailAxiomLogs() which polls every 2 seconds and stops when it sees a terminal phase (complete or failed). Timeout: 15 minutes.

Find all failed builds in the last 24 hours:

['lk-build-logs']
| where level == "error"
| summarize count() by buildId
| sort by count_ desc

Find slow transpile phases:

['lk-build-logs']
| where phase == "transpile" and status == "done"
| where durationMs > 30000
| project buildId, slug, durationMs, _time
| sort by durationMs desc
FilePurpose
scripts/lib/axiom.tscreateAxiomBuffer(), queryAxiomLogs(), tailAxiomLogs()
scripts/deploy-logs.tsCLI log viewer (pnpm deploy:logs)
packages/cf-client-deploy/src/worker.ts/builds/:id/logs endpoint