# ctx.log

> Reference for ctx.log with the info, warn, error, and debug signatures, metadata, and how logs appear locally versus in the dashboard.

`ctx.log` is the structured logger. Each call emits one log record correlated with the current execution, so log lines appear inline with HTTP and KV steps on the execution timeline. Use it instead of `console.log`, which is captured only best-effort.

## ContextLogger

```typescript
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;
}
```

| Parameter | Type | Description |
|---|---|---|
| `message` | `string` | The log record body |
| `metadata` | `Record<string, unknown>` (optional) | Structured context, serialized reliably |

All four methods are synchronous and return `void`; the level sets the record's severity.

## How metadata is carried

Metadata is kept as a structured object on the log record rather than flattened into one field per key. That preserves the full object for display and search while keeping cardinality bounded.

## Correlation with the execution

Every record is tied to the current execution so it lands on the right timeline in the dashboard, alongside auto-instrumented HTTP and KV steps. Filter and search from the project's Logs tab.

## Dev versus production

The code is identical in both environments. Under `helix dev`, `ctx.log` pretty-prints to the terminal (colored level, timestamp, message, and metadata inline). In production, the same records appear on the execution timeline in the Helix dashboard.

## Example

```typescript
// functions/api/v1/sync.post.ts
import { z } from 'zod';
import { defineFunction } from '@trayai/helix-sdk';

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 res = await ctx.http
    .authed('salesforce_prod')
    .get('https://acme.my.salesforce.com/services/data/v59.0/query', {
      query: { q: 'SELECT Id, Name FROM Contact LIMIT 10' },
    });
  const contacts = res.body as { records: unknown[] };

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

  if (contacts.records.length === 0) {
    ctx.log.warn('Nothing to sync');
  }

  return { synced: contacts.records.length };
});
```

The execution timeline, auto-instrumented steps, and `console.log` capture limits are covered in [the observability guide](/documentation/guides/observability/).

---

Canonical: https://helix.tray.ai/documentation/reference/context/log/
Any link on this page is available as markdown by appending .md to its URL.
Full corpus: https://helix.tray.ai/documentation/llms-full.txt