# Observability
URL: /docs/customization/observability
Description: Trace Ask AI and MCP runs with span IDs, timing, status, previews, errors, and callbacks

# Observability

Use `observability` when you need step-level visibility into agent runtime behavior. It emits
span-like trace events for Ask AI and MCP tools: run start/end, prompt building, retrieval, model
calls, model responses, streaming handoff, tool calls, tool results, errors, retries, and timeouts.

Observability is separate from [Analytics](/docs/customization/analytics). It does not receive
`page_view`, `search_query`, `api_ai_request`, or `mcp_tool`; those usage events belong to
`analytics.onEvent`.

## Quick Start

```ts title="docs.config.ts"
export default defineDocs({
  observability: true,
});
```

`observability: true` logs trace events to the console with the
`[@farming-labs/docs:observability]` prefix.

## Live Local Logs

Use console observability when you want to watch Ask AI and MCP work as it happens:

```ts title="docs.config.ts"
export default defineDocs({
  observability: {
    console: "debug",
  },
});
```

Every trace event prints to the dev-server terminal with its `traceId`, `spanId`, `name`, `status`,
`durationMs`, previews, and metadata.

## Callback

Use `onEvent` when you want to forward traces to a log drain, APM, OpenTelemetry bridge, database,
or your own debugging dashboard:

```ts title="docs.config.ts"
export default defineDocs({
  observability: {
    console: "debug",
    async onEvent(event) {
      await fetch("https://observability.example.com/events", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(event),
      });
    },
  },
});
```

Set `console: false` if you only want the callback and do not want terminal logs.

For metrics, aggregate by `event.type`, `event.name`, `event.status`, and `event.durationMs`. For
traces, group by `event.traceId` and order by `startedAt` or `timestamp`.

## Event Shape

`onEvent` receives a normalized `DocsObservabilityEvent`:

```ts
interface DocsObservabilityEvent {
  type: string;
  timestamp: string;
  source: "client" | "server" | "mcp";
  traceId?: string;
  spanId?: string;
  parentSpanId?: string;
  name?: string;
  startedAt?: string;
  endedAt?: string;
  durationMs?: number;
  status?: "started" | "success" | "error" | "retry" | "timeout";
  inputPreview?: Record<string, unknown>;
  outputPreview?: Record<string, unknown>;
  url?: string;
  path?: string;
  referrer?: string;
  locale?: string;
  input?: {
    query?: string;
    question?: string;
    feedbackComment?: string;
    content?: string;
  };
  metadata?: Record<string, unknown>;
  properties?: Record<string, unknown>;
}
```

Example tool event:

```ts
{
  traceId: "run_123",
  spanId: "span_456",
  parentSpanId: "run_123",
  type: "tool.call",
  name: "read_page",
  startedAt: "2026-01-01T00:00:00.000Z",
  endedAt: "2026-01-01T00:00:00.082Z",
  durationMs: 82,
  status: "success",
  inputPreview: { page: "/docs/mcp" },
  outputPreview: { chars: 4210 },
  metadata: {
    attempt: 1,
    model: "gpt-5.2",
  },
}
```

## Trace Events

Observability events describe runtime steps inside an agent run. They are span-like: use
`traceId` to group a run, `spanId` to identify a step, and `parentSpanId` to connect child steps to
their parent.

### Ask AI Events

| Event              | Meaning                                                               |
| ------------------ | --------------------------------------------------------------------- |
| `run.start`        | A new Ask AI run started. This is the root span for the request.       |
| `user.input`       | The latest user message was accepted and summarized into safe counts. |
| `retrieval.query`  | Docs retrieval started for the user question.                         |
| `retrieval.result` | Docs retrieval completed and produced measurable result counts.       |
| `retrieval.error`  | Docs retrieval failed before the model call could use the context.    |
| `prompt.build`     | The system prompt and retrieved docs context were assembled.          |
| `model.call`       | The outbound model request started.                                   |
| `model.response`   | The model provider returned successful response headers.              |
| `model.stream`     | A streaming model response was handed back to the client.             |
| `model.error`      | The model request failed, threw, or returned an error status.         |
| `agent.final`      | The final response handoff was reached after a successful model call. |
| `run.error`        | The run ended unsuccessfully.                                         |
| `run.end`          | The run ended, either successfully or after an error.                 |

### MCP Tool Events

| Event         | Meaning                                                           |
| ------------- | ----------------------------------------------------------------- |
| `tool.call`   | An MCP tool call started, such as `search_docs` or `read_page`.   |
| `tool.result` | An MCP tool completed successfully and returned a result.         |
| `tool.error`  | An MCP tool returned an error result or threw during execution.   |

### Generic Runtime Events

| Event     | Meaning                                                               |
| --------- | --------------------------------------------------------------------- |
| `retry`   | A retry attempt was recorded for a failed or unstable step.           |
| `timeout` | A step timed out before it could complete.                            |
| `error`   | A generic run-level error was recorded outside a more specific event. |

Built-in trace events avoid raw user-authored text. They use lengths, counts, route names, model
IDs, status codes, and content sizes so they are safer to aggregate.

## Using Analytics Together

Configure both hooks when you want product analytics and runtime traces:

```ts title="docs.config.ts"
export default defineDocs({
  analytics: {
    async onEvent(event) {
      await sendUsageEvent(event);
    },
  },
  observability: {
    console: "debug",
    async onEvent(event) {
      await sendTraceEvent(event);
    },
  },
});
```

In that setup:

- `analytics.onEvent` receives events like `page_view`, `search_query`, `api_ai_request`, and
  `mcp_tool`
- `observability.onEvent` receives events like `run.start`, `prompt.build`, `model.call`, and
  `tool.result`

## Options

| Option          | Type                                      | Default                      |
| --------------- | ----------------------------------------- | ---------------------------- |
| `enabled`       | `boolean`                                 | `true` when object is passed |
| `console`       | `boolean \| "log" \| "info" \| "debug"` | `true` for `observability: true` |
| `includeInputs` | `boolean`                                 | `false`                      |
| `onEvent`       | `(event) => void \| Promise<void>`        | `undefined`                  |