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.
- Incompatible scan browsers — when a facial-scan participant’s browser can’t run the scan, a
warnline with their user agent and the missing capabilities is logged against the run. See Facial scan → When a browser can’t run the scan.
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.