Skip to Content
ObservabilityEvent logs

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

FieldDescription
levelOne of debug, log, info, warn, error. Matches the console.* call you made.
messageThe stringified arguments you passed to console.*.
runIdThe run the entry belongs to. null only for platform-emitted lines that don’t tie to a run.
metadataStructured context attached to the entry — for platform lines, the originating stage; for console.error from an uncaught throw, the stack trace.
createdAtWhen the line was emitted.

Levels

LevelWhen the platform emits it
debugconsole.debug(...). Use for verbose diagnostic output.
logconsole.log(...). The default channel for free-form output.
infoconsole.info(...). Use for noteworthy events (a notification was sent, a scan was provisioned, …).
warnconsole.warn(...). Use for recovered-from problems.
errorconsole.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 at error level 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.

Last updated on