Skip to content
VibORM
Esc
navigateopen⌘Jpreview
On this page

Logging

Configure structured logging with pretty console output or custom callbacks

Configuration

import { createClient } from "viborm";

// Simple: enable all levels with pretty console output
const client = createClient({
  schema,
  driver,
  instrumentation: {
    logging: true
  }
});

// Custom: configure individual levels
const clientCustom = createClient({
  schema,
  driver,
  instrumentation: {
    logging: {
      query: true,                    // Pretty console output
      error: (event, log) => {        // Custom callback
        errorTracker.capture(event.error);
        log();
      }
      // warning: omitted = disabled
    }
  }
});

Options

Option Type Default Description
all true | LogCallback - Catch-all handler for all levels
query true | LogCallback - Query log handler
cache true | LogCallback - Cache log handler
warning true | LogCallback - Warning log handler
error true | LogCallback - Error log handler
includeSql boolean false Include SQL query text in events
includeParams boolean false Include query parameters in events

Log Levels

Each level can be configured independently:

  • true - Use the built-in pretty console formatter
  • (event, log) => {} - Custom callback (receives default logger as second argument)
  • undefined (or omit) - Falls back to all handler, or disabled if no all
Level Description
query Emitted for every database query
cache Emitted for cache hits, misses, and revalidation
warning Emitted for non-fatal issues
error Emitted when operations fail

Pretty Console Output

When you set a level to true, VibORM uses a colorized console formatter:

logging: {
  query: true,
  error: true
}

Output example:

2024-01-15T10:30:00.000Z User.findMany 12ms
  SELECT * FROM "users" WHERE "active" = $1

The formatter includes:

  • Timestamp in ISO format
  • Model and operation name (color-coded)
  • Duration with color based on speed (green < 10ms, yellow < 100ms, red >= 100ms)
  • SQL query when includeSql is enabled (dimmed)
  • Parameters if enabled (dimmed)

Custom Callbacks

For full control, provide a callback function. The callback receives the event and a log function that invokes the default pretty logger:

logging: {
  query: (event, log) => {
    // Your custom handling
    myLogger.info(event);
    log(); // Also print with default pretty formatter
  },
  error: (event, log) => {
    errorTracker.capture(event.error);
    // Don't call log() - skip default output
  }
}

Catch-All Handler

Use all to handle all log levels with a single handler:

logging: {
  all: true  // Pretty output for everything
}

// Or with a callback
logging: {
  all: (event, log) => {
    myLogger.log(event);
    log();
  }
}

Specific level handlers take precedence over all:

logging: {
  all: true,                    // Default for all levels
  error: (event, log) => {      // Override for errors only
    errorTracker.capture(event.error);
    log();
  }
}

Log Event Structure

Every log event contains the following fields:

interface LogEvent {
  level: "query" | "cache" | "warning" | "error";
  timestamp: Date;
  duration?: number;       // Query duration in milliseconds
  model?: string;          // Model name (e.g., "User")
  operation?: string;      // Operation (e.g., "findMany")
  correlationId?: string;  // Stable identifier for this ORM operation
  error?: VibORMError;     // Error object (for error events)
  sql?: string;            // SQL query (if includeSql enabled)
  params?: unknown[];      // Query parameters (if includeParams enabled)
  meta?: Record<string, unknown>; // Allowlisted, sanitized event metadata
}

Callbacks receive a sanitized snapshot, not the mutable event object produced inside the driver. See Privacy & Security for the full sanitization and redaction policy.

Event Examples

Query Events

Emitted for every successful database operation:

{
  level: "query",
  timestamp: new Date("2024-01-15T10:30:00.000Z"),
  duration: 12,
  model: "User",
  operation: "findMany",
  sql: 'SELECT * FROM "users" WHERE "active" = $1',
  params: [true]
}

Warning Events

Emitted for non-fatal issues that may need attention:

{
  level: "warning",
  timestamp: new Date("2024-01-15T10:30:00.000Z"),
  model: "User",
  operation: "findMany",
  duration: 5000
}

Error Events

Emitted when operations fail:

// Unique constraint violation
{
  level: "error",
  timestamp: new Date("2024-01-15T10:30:00.000Z"),
  model: "User",
  operation: "create",
  duration: 15,
  error: UniqueConstraintError,  // VibORMError with code V3001
  sql: 'INSERT INTO "users" ("email") VALUES ($1)',
  // Stable constraint/provider details are available on error.meta.
}

// Connection error
{
  level: "error",
  timestamp: new Date("2024-01-15T10:30:00.000Z"),
  error: ConnectionError,  // VibORMError with code V1001
  // Raw connection messages and endpoints are redacted.
}

// Record not found (for findUniqueOrThrow)
{
  level: "error",
  timestamp: new Date("2024-01-15T10:30:00.000Z"),
  model: "User",
  operation: "findUniqueOrThrow",
  duration: 8,
  error: NotFoundError,  // VibORMError with code V6001
  sql: 'SELECT * FROM "users" WHERE "id" = $1'
}

Examples

Development Setup

Pretty output for all levels during development:

logging: {
  query: true,
  warning: true,
  error: true
}

Production Setup

JSON logging for queries, error tracking for errors:

logging: {
  query: (event, log) => {
    console.log(JSON.stringify({
      level: event.level,
      timestamp: event.timestamp.toISOString(),
      model: event.model,
      operation: event.operation,
      duration: event.duration
    }));
  },
  error: (event, log) => {
    errorTracker.capture(event.error, {
      model: event.model,
      operation: event.operation
    });
  }
}

Integrate with Pino

import pino from "pino";

const logger = pino();

logging: {
  query: (event, log) => {
    logger.info({
      model: event.model,
      operation: event.operation,
      duration: event.duration
    }, `${event.model}.${event.operation}`);
  },
  error: (event, log) => {
    logger.error({
      model: event.model,
      operation: event.operation,
      error: event.error?.message
    }, `${event.model}.${event.operation} failed`);
  }
}

Errors Only

Log only errors with pretty output:

logging: {
  error: true
}

SQL and Parameters

By default, both SQL and parameters are excluded:

logging: {
  query: true,
  includeSql: false,     // SQL hidden (default)
  includeParams: false   // Params hidden (default)
}

To include SQL or parameters (development only), opt in to each independently:

logging: {
  query: true,
  includeSql: true,
  includeParams: true
}

These options affect logging only — tracing and error diagnostics have independent disclosure policies.

Was this page helpful?