Log AI calls
@warlock.js/ai does not own a logger contract. Every primitive imports the log singleton from @warlock.js/logger directly and emits structured entries through it. Configuration — channels, levels, redaction — lives entirely on the logger.
No ai.config({ logger }). No per-primitive logger: override. Configure once at app boot; the framework picks it up.
Configure at boot
Section titled “Configure at boot”import { log, ConsoleLog, FileLog } from "@warlock.js/logger";
log.configure({ channels: [ new ConsoleLog(), new FileLog({ chunk: "daily" }), ], autoFlushOn: ["SIGINT", "SIGTERM", "beforeExit"],});
log.setMinLevel("info");That’s it. Every agent / workflow / supervisor running in the process emits to the configured channels.
Call convention
Section titled “Call convention”Every framework log call uses the 4-arg positional form:
log.info("ai.agent", "trip.started", "agent starting trip", { tripIndex, model });module— emitting primitive ("ai.agent","ai.openai","ai.workflow.<name>","ai.supervisor.<name>").action— mirrors event names without the primitive prefix ("trip.started","tool.called").message— human-readable summary.context— structured bag of diagnostic fields.
action strips the prefix of the corresponding event (agent.trip.started → trip.started) so grep filters and event handlers share vocabulary.
Level mapping
Section titled “Level mapping”| Level | Framework usage |
|---|---|
debug | Internals (request/response bodies, token counts per trip) |
info | Milestones (agent starting, agent completed) |
warn | Retries, repair attempts, recoverable tool failures |
error | Terminal failures surfaced via result.error |
success | Tool-call success |
Streaming token deltas are intentionally NOT logged at token granularity — trip boundaries carry the same information at readable volume.
What gets logged
Section titled “What gets logged”| Action | Level | Context |
|---|---|---|
agent.starting | info | input, modelName, maxTrips |
trip.started | debug | tripIndex |
tool.calling | debug | tool name, action, tripIndex |
tool.called | success | tool name, duration, tripIndex |
tool.failed | warn | tool name, error code, tripIndex |
repair.attempting | warn | tripIndex, validation issues |
agent.completed | info | totalUsage, totalDuration, trip count |
agent.error | error | error code, message, stack |
Workflow
Section titled “Workflow”workflow.starting / step.starting / step.completed / step.failed / workflow.completed / workflow.error. Module is ai.workflow.<name>.
Supervisor
Section titled “Supervisor”supervisor.starting / iteration.starting / router.deciding / router.decided / agent.starting (per dispatched agent) / iteration.completed / evaluate.verdict / supervisor.completed. Module is ai.supervisor.<name>.
Provider adapter
Section titled “Provider adapter”ai.openai (and future adapters) emit request (debug) and response (debug) per call, plus error on the wrapped AIError.
Redaction
Section titled “Redaction”Redaction is a @warlock.js/logger feature — configure once, applies to every framework log automatically.
log.configure({ redact: { paths: [ "context.messages", // prompts "context.input", // user input "context.apiKey", // defense-in-depth ], },});Sensitive data — prompts, customer messages, API keys — should be redacted at the channel level. Don’t try to do it at the agent level; the logger applies redaction once for every log call regardless of source.
Events vs logs — two channels, one source
Section titled “Events vs logs — two channels, one source”- Events are push-model (subscribers), typed payloads, per-execution lifetime — ideal for UI streaming, SSE, metrics.
- Logs are pull-model (written to channels), structured-string + context, persistent — ideal for grep, post-mortem.
Both fire from the same internal emit, so every event produces both a typed event payload and a structured log entry.
Patterns
Section titled “Patterns”Silence everything in tests
Section titled “Silence everything in tests”import { log } from "@warlock.js/logger";
beforeAll(() => log.setChannels([]));Capture all framework log entries in a test
Section titled “Capture all framework log entries in a test”import { log, LogChannel } from "@warlock.js/logger";
class Capture extends LogChannel { public name = "capture"; public entries: any[] = []; public log(data) { this.entries.push(data); }}
const capture = new Capture();log.setChannels([capture]);Related
Section titled “Related”- Handle errors — what lands on the
errorchannel. @warlock.js/logger— the logger package itself.