DataFn
Server

Observability

Monitor server performance with timing events.

Overview

DataFn provides structured execution timing for monitoring server performance. When enabled, every query, mutation, and sync operation emits a timing event with phase-by-phase duration breakdowns.

Configuration

Enable observability through the observability option:

const server = await createDatafnServer({
  schema,
  db,
  observability: {
    timing: true,
    onTiming: (event) => {
      // Send to your metrics system
      metrics.recordTiming(event.endpoint, event.totalMs, event.phases);
    },
  },
});

ObservabilityConfig

interface ObservabilityConfig {
  /** Enable execution timing events. Default: false. */
  timing?: boolean;

  /** Custom timing event handler. Default: logs via the server logger. */
  onTiming?: (event: ExecutionTimingEvent) => void;
}

When timing is false or omitted, no timing infrastructure is created and zero overhead is incurred.

ExecutionTimingEvent

Each timing event contains the following fields:

interface ExecutionTimingEvent {
  /** The endpoint that was called (e.g., "query", "mutation", "pull"). */
  endpoint: string;

  /** The resource name, if applicable. */
  resource?: string;

  /** The operation type, if applicable (e.g., "insert", "merge"). */
  operation?: string;

  /** Phase-by-phase duration in milliseconds. */
  phases: Record<string, number>;

  /** Total operation duration in milliseconds. */
  totalMs: number;

  /** ISO 8601 timestamp of when the event was recorded. */
  timestamp: string;
}

Example Event

{
  "endpoint": "mutation",
  "resource": "tasks",
  "operation": "merge",
  "phases": {
    "validate": 2,
    "execute": 15,
    "serialize": 1,
    "total": 18
  },
  "totalMs": 18,
  "timestamp": "2026-03-01T12:00:00.000Z"
}

ExecutionTimer

Route handlers use ExecutionTimer internally to measure phases. Each phase is sequential -- starting a new phase automatically ends the previous one:

const timer = new ExecutionTimer();

timer.startPhase("validate");
// ... validation logic ...

timer.startPhase("execute");
// ... execution logic ...

timer.startPhase("serialize");
// ... serialization logic ...

const event = timer.build("mutation", "tasks", "merge");
// event.phases = { validate: 2, execute: 15, serialize: 1, total: 18 }

Common phases measured by the built-in handlers:

PhaseDescription
validateSchema validation, authorization, and limit checks.
executeDatabase operations, query execution, mutation application.
serializeResponse serialization and JSON encoding.

Default Handler

When onTiming is not provided, timing events are logged via the server's logger at the debug level:

[DEBUG] [datafn:timing] { endpoint: "query", resource: "tasks", ... }

In production (debug: false), the default logger suppresses debug output, so timing events are effectively silent unless a custom onTiming handler is provided.

Error Handling

If a custom onTiming handler throws, the error is caught and logged as a warning. Timing handler failures never affect request processing.

Security

Timing events never include record data, filter values, or credentials. Only structural metadata (endpoint, resource, operation) and durations are emitted.