Event logs
The event log is the loop’s stream of free-form log lines. Anything you write with console.* from inside run() (or inside a step.do callback) lands here, alongside any uncaught errors. Each entry is tagged to the run that produced it, so you can scan one loop’s logs or zoom in on a specific run.
What’s captured per entry
| Field | Description |
|---|---|
level | One of debug, log, info, warn, error. Matches the console.* call you made. |
message | The stringified arguments you passed to console.*. |
runId | The run the entry belongs to. null only for platform-emitted lines that don’t tie to a run. |
metadata | Structured context attached to the entry — for platform lines, the originating stage; for console.error from an uncaught throw, the stack trace. |
createdAt | When the line was emitted. |
Levels
| Level | When the platform emits it |
|---|---|
debug | console.debug(...). Use for verbose diagnostic output. |
log | console.log(...). The default channel for free-form output. |
info | console.info(...). Use for noteworthy events (a notification was sent, a scan was provisioned, …). |
warn | console.warn(...). Use for recovered-from problems. |
error | console.error(...). Also emitted automatically when an uncaught error propagates out of run() — the stack lands here. |
The platform doesn’t filter or drop levels — console.debug is captured the same as console.error.
What gets logged for free
You don’t have to instrument anything to get useful logs:
- Uncaught errors — any error that propagates out of
run()is logged aterrorlevel against the run, with the message and stack. See Errors and retries. - Step failures — a step that throws is recorded on the step (see Steps) and the error is mirrored to the event log.
You only need to log explicitly when you want to record something the platform can’t infer — a decision, a fallback, a third-party API response code.
Read the log stream
Each loop has a Logs tab showing the captured stream, newest first. Each row renders the level (as a color-coded badge), the timestamp, the runId it belongs to, and the message. There’s a refresh action; new lines arrive as the loop emits them.
To narrow to one run, copy its runId from the runs list (or from an API response) and use it as a filter against the message stream.
Examples
Log a decision your loop made
When the loop takes a fallback path, log it explicitly so the timeline + log stream together explain what happened:
let scan;
try {
await step.do("Generate a health scan", async ({ ctx }) => {
await ctx.FACIAL_SCAN.create({ expiresIn: 3600 });
});
scan = await step.waitForScan({ timeout: "30 minutes" });
} catch (error) {
console.warn("scan failed, falling back to manual review", error);
scan = { status: "fallback" };
}The console.warn shows up at warn level on the run, alongside the failed step entry.
Log structured context
console.log stringifies its arguments. To preserve structure for later scanning, pass a JSON-encoded object:
console.log(
JSON.stringify({
event: "notification.sent",
patientId,
channel: "sms",
}),
);The line is still recorded as plain text on message, but you can now grep or eyeball it for fields without parsing freeform prose.