Skip to content

Pre-GA Design Partner and Early Access only. Request access

Helix Docs

Observability

Write structured logs with ctx.log, then read one traced entry per execution in the Helix dashboard. Local runs pretty-print to your terminal.

For developers Updated Sep 18, 2026
View as Markdown

Every function execution is observable without extra setup. Use ctx.log in your code for structured records; open an execution in the Helix dashboard to see the full trace of that request.

What you get today:

  • Execution logs in the dashboard at https://app.helix.tray.ai. One entry per execution, HTTP request or scheduled job run; opening an entry shows the operations it ran, the third-party calls it made, and the request and response payloads.
  • ctx.log in your code, exactly as shown below. It is the supported way to add your own structured records to an execution.
  • Local logs in your terminal. helix dev --pretty-logs is the default and prints readable lines; helix dev --raw-logs prints the raw structured JSON instead.

Write logs with ctx.log

ctx.log is the primary logging interface. It produces structured, execution-scoped log records that appear on the execution’s timeline in the dashboard.

interface ContextLogger {
  info(message: string, metadata?: Record<string, unknown>): void;
  warn(message: string, metadata?: Record<string, unknown>): void;
  error(message: string, metadata?: Record<string, unknown>): void;
  debug(message: string, metadata?: Record<string, unknown>): void;
}
// functions/api/v1/sync.post.ts
import { defineFunction } from '@trayai/helix-sdk';
import { z } from 'zod';

export const input = z.object({
  userId: z.string(),
});

export default defineFunction<typeof input>(async (ctx) => {
  ctx.log.info('Starting sync', { userId: ctx.input.userId });

  const contacts = await ctx.http
    .authed('salesforce_prod')
    .get('https://acme.my.salesforce.com/services/data/v59.0/query', {
      searchParams: { q: 'SELECT Id, Name FROM Contact LIMIT 10' },
    })
    .json<{ records: Array<{ Id: string; Name: string }> }>();

  ctx.log.info('Fetched contacts', { count: contacts.records.length });

  await ctx.kv().set(`sync:last:${ctx.input.userId}`, {
    count: contacts.records.length,
    at: new Date().toISOString(),
  });

  ctx.log.warn('Duplicate check skipped (feature flag disabled)');
  return { synced: contacts.records.length };
});

Each call records:

  • Timestamp
  • Severity from the level (info, warn, error, debug)
  • Body, the message string
  • Attributes, your metadata object
  • Correlation with the current execution, so the record lands on the right timeline

In local dev, ctx.log also pretty-prints to the terminal for immediate visibility.

The dashboard shows spans and correlated log records on one time-ordered execution timeline:

exec_789xyz | 2025-03-19T10:30:00.123Z | POST /api/v1/sync

10:30:00.123  LOG  INFO   Starting sync { userId: "user_abc" }
10:30:00.145  SPAN HTTP → GET https://acme.my.salesforce.com/services/data/v59.0/query
                          auth: salesforce_prod
10:30:00.412  SPAN HTTP ← 200 OK (267ms)
10:30:00.413  LOG  INFO   Fetched contacts { count: 10 }
10:30:00.415  SPAN KV   → SET sync:last:user_abc
10:30:00.418  SPAN KV   ← OK (3ms)
10:30:00.450  LOG  WARN   Duplicate check skipped (feature flag disabled)
10:30:00.451  ←    200 OK { synced: 10 } (328ms total)

Full API reference: ctx.log.

Auto-instrumented operations

HTTP calls and KV operations appear as steps on the execution timeline without extra instrumentation.

HTTP calls. Authenticated calls via ctx.http.authed(...) capture the request method, URL, headers (credentials redacted), and body; the response status, headers, and body (truncated if large); the duration; and the auth alias used, plus whether a token refresh occurred. Unauthenticated calls capture method, URL, headers, response status, and duration.

KV operations. Every ctx.kv() operation produces a step with the operation type (get, set, delete, incr, list), the key (or prefix for list), and the duration.

Dev and production

You write the same code in both environments.

  • Local dev (helix dev). ctx.log pretty-prints to the terminal: colored level, readable timestamp, message, and metadata inline. Use --raw-logs for the structured JSON form.
  • Production. The same ctx.log calls appear on the execution timeline in the Helix dashboard. Open an execution to see logs interleaved with the operations that ran.

console.log capture

console.log, console.warn, console.error, and console.debug are captured best-effort alongside ctx.log, so functions that haven’t adopted ctx.log still get some observability without refactoring. Prefer ctx.log for anything you need to rely on:

  • No structured attributes. console.log('foo', { bar: 1 }) produces a string; ctx.log.info('foo', { bar: 1 }) produces a log record with queryable attributes.
  • Third-party library output. Captured with the execution, but may lack useful structure.
  • Serialization. Complex objects may not serialize cleanly; ctx.log serializes reliably.
ScenarioUse
New code, structured loggingctx.log.info('message', { key: value })
Quick debugging during developmentconsole.log(...), goes to the terminal plus best-effort capture
Existing function that hasn’t adopted ctx.logconsole.log(...), captured best-effort
Production-critical loggingctx.log, always reliable and structured

Error tracking

Unhandled errors are captured automatically on the same execution timeline. The record includes the stack trace with source-mapped file paths, and the timeline shows everything up to the failure plus the request that triggered it:

exec_abc123 | 2025-03-19T10:30:00.123Z | GET /api/v1/users/:userId (ERROR)

10:30:00.123  LOG  INFO   Fetching user
10:30:00.200  SPAN HTTP → GET https://acme.my.salesforce.com/...
10:30:00.450  SPAN HTTP ← 401 Unauthorized (250ms)
10:30:00.451  LOG  ERROR  TypeError: Cannot read property 'records' of undefined
                          at handler (functions/api/v1/users/[userId].get.ts:15:34)
10:30:00.452  ←    500 Internal Server Error (329ms total)

Read logs

Open the project’s Logs tab in the Helix dashboard. Each entry is one execution, an HTTP request or a scheduled job run; open it for the full execution timeline. See the dashboard for the walkthrough.

Loading search…

Jump to a section

tab to move · esc to close