Logging

    Description — Couchbase Lite JavaScript — Logging and Debugging

    Overview

    Couchbase Lite JavaScript uses LogTape for logging, providing structured, configurable logging that helps diagnose issues during development and production.

    LogTape is easy to use, unobtrusive, and flexible. It’s designed to integrate with whatever logging system your application currently uses.

    If you don’t configure logging, LogTape logs nothing by default.

    Effective logging is essential for:

    • Debugging development issues

    • Monitoring production applications

    • Diagnosing replication problems

    • Understanding query performance

    • Tracking storage operations

    Installation

    LogTape must be installed separately from Couchbase Lite:

    Install LogTape
    npm install @logtape/logtape

    For additional LogTape documentation, see: LogTape Installation Guide

    Log Categories

    Couchbase Lite organizes logs using a hierarchical category system.

    Root Category: CouchbaseLite (exported as LogCategory constant)

    Subcategories:

    • DB - Database operations

    • Query - Query execution and optimization

    • Sync - Replication and synchronization

    You can configure different log levels for each subcategory or route them to different sinks.

    Quick Start

    Here’s a simple example that configures Couchbase Lite to log messages at "info" priority and above to the console:

    Example 1. Basic Logging Configuration
    import * as cbl from '@couchbase/lite-js';
    import * as logtape from '@logtape/logtape';
    
    await logtape.configure({
      sinks: {
        console: logtape.getConsoleSink(),
      },
      loggers: [
        {
          category: cbl.LogCategory,
          lowestLevel: 'info',
          sinks: ['console'],
        }
      ],
    });

    For more configuration examples, see: LogTape Quick Start

    Log Levels

    LogTape supports these log levels (from least to most verbose):

    • fatal - Critical errors that cause application failure

    • error - Errors that prevent operations from completing

    • warning - Potential issues that don’t prevent operation

    • info - Informational messages about normal operations

    • debug - Detailed information for debugging

    Performance Consideration

    LogTape at debug level can generate a lot of logs, especially for:

    • Query plans

    • Replication events

    Recommend: Use debug only during development or targeted troubleshooting.

    Configuring Logging

    Basic Configuration

    Example 2. Configure All Couchbase Lite Logs
    import { LogCategory } from '@couchbase/lite-js';
    import { configure, getConsoleSink } from '@logtape/logtape';
    
    await configure({
      sinks: {
        console: getConsoleSink(),
      },
      loggers: [
        {
          category: LogCategory,
          lowestLevel: 'debug',
          sinks: ['console'],
        }
      ],
    });

    Category-Specific Logging

    Configure different log levels for different Couchbase Lite subsystems:

    Example 3. Configure Subcategories
    import { LogCategory } from '@couchbase/lite-js';
    import { configure, getConsoleSink } from '@logtape/logtape';
    
    await configure({
      sinks: {
        console: getConsoleSink(),
      },
      loggers: [
        // General Couchbase Lite logs at info level
        {
          category: LogCategory,
          lowestLevel: 'info',
          sinks: ['console'],
        },
        // Verbose database logs
        {
          category: [LogCategory, 'DB'],
          lowestLevel: 'debug',
          sinks: ['console'],
        },
        // Verbose query logs
        {
          category: [LogCategory, 'Query'],
          lowestLevel: 'debug',
          sinks: ['console'],
        },
        // Verbose sync logs
        {
          category: [LogCategory, 'Sync'],
          lowestLevel: 'debug',
          sinks: ['console'],
        },
      ],
    });
    1 Root Couchbase Lite category at info level
    2 Database operations at debug level
    3 Query operations at debug level
    4 Replication/sync operations at debug level

    Log Sinks

    LogTape sinks determine where log messages are sent.

    Console Sink

    The console sink outputs logs to the browser console:

    Example 4. Console Sink
    import { configure, getConsoleSink } from '@logtape/logtape';
    import { LogCategory } from '@couchbase/lite-js';
    
    await configure({
      sinks: {
        console: getConsoleSink(),
      },
      loggers: [
        {
          category: LogCategory,
          lowestLevel: 'info',
          sinks: ['console'],
        }
      ],
    });

    File Sink

    For Node.js environments (like Electron), you can log to files:

    Example 5. File Sink (Node.js/Electron)
    import { getFileSink } from '@logtape/file';
    import { configure } from '@logtape/logtape';
    import { LogCategory } from '@couchbase/lite-js';
    
    await configure({
      sinks: {
        file: getFileSink('cbl.log'),
      },
      loggers: [
        {
          category: LogCategory,
          lowestLevel: 'debug',
          sinks: ['file'],
        }
      ],
    });
    File sinks require Node.js and are not available in browser environments.

    Multiple Sinks

    Send logs to multiple destinations:

    Example 6. Multiple Sinks
    import { configure, getConsoleSink } from '@logtape/logtape';
    import { LogCategory } from '@couchbase/lite-js';
    
    await configure({
      sinks: {
        console: getConsoleSink(),
        remote: async (record) => {
          // Send to remote logging service
          await fetch('/api/logs', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(record),
          });
        },
      },
      loggers: [
        {
          category: LogCategory,
          lowestLevel: 'info',
          sinks: ['console', 'remote'],
        }
      ],
    });

    Custom Log Sink

    Create custom sinks for specialized logging needs:

    Example 7. Custom Sink
    import { configure } from '@logtape/logtape';
    import { LogCategory } from '@couchbase/lite-js';
    
    // Custom sink that stores logs in IndexedDB
    const indexedDBSink = async (record) => {
      const db = await openDB('logs', 1, {
        upgrade(db) {
          db.createObjectStore('entries', { autoIncrement: true });
        },
      });
    
      await db.add('entries', {
        timestamp: record.timestamp,
        level: record.level,
        category: record.category,
        message: record.message,
      });
    };
    
    await configure({
      sinks: {
        indexedDB: indexedDBSink,
      },
      loggers: [
        {
          category: LogCategory,
          lowestLevel: 'info',
          sinks: ['indexedDB'],
        }
      ],
    });

    Sink Inheritance

    LogTape uses a hierarchical category system where child loggers inherit sinks from parent loggers.

    Example 8. Sink Inheritance Example
    import { configure, getConsoleSink } from '@logtape/logtape';
    import { LogCategory } from '@couchbase/lite-js';
    
    await configure({
      sinks: {
        console: getConsoleSink(),
        file: getFileSink('db.log'),
      },
      loggers: [
        // Parent logger - all Couchbase Lite logs to console
        {
          category: LogCategory,
          sinks: ['console'],
        },
        // Child logger - DB logs also go to file
        {
          category: [LogCategory, 'DB'],
          sinks: ['file'], // Inherits 'console' from parent
        },
      ],
    });

    To override inherited sinks instead of adding to them:

    Example 9. Override Inherited Sinks
    await configure({
      sinks: {
        console: getConsoleSink(),
        file: getFileSink('sync.log'),
      },
      loggers: [
        {
          category: LogCategory,
          sinks: ['console'],
        },
        {
          category: [LogCategory, 'Sync'],
          sinks: ['file'],
          parentSinks: 'override', // Don't inherit console sink
        },
      ],
    });

    Common Logging Scenarios

    Debugging Database Operations

    Example 10. Database Logging
    import { configure, getConsoleSink } from '@logtape/logtape';
    import { LogCategory } from '@couchbase/lite-js';
    
    await configure({
      sinks: {
        console: getConsoleSink(),
      },
      loggers: [
        {
          category: [LogCategory, 'DB'],
          lowestLevel: 'debug',
          sinks: ['console'],
        }
      ],
    });
    
    // Now database operations will be logged
    const database = await Database.open(config);

    Debugging Queries

    Example 11. Query Logging
    await configure({
      sinks: {
        console: getConsoleSink(),
      },
      loggers: [
        {
          category: [LogCategory, 'Query'],
          lowestLevel: 'debug',
          sinks: ['console'],
        }
      ],
    });
    
    // Query execution will now be logged
    const query = database.createQuery('SELECT * FROM tasks');
    await query.execute(row => console.log(row));

    Debugging Replication

    Example 12. Replication Logging
    await configure({
      sinks: {
        console: getConsoleSink(),
      },
      loggers: [
        {
          category: [LogCategory, 'Sync'],
          lowestLevel: 'debug',
          sinks: ['console'],
        }
      ],
    });
    
    // Replication activity will now be logged
    const replicator = new Replicator(config);
    await replicator.start();

    Production Logging

    Production Configuration

    In production, use less verbose logging:

    Example 13. Production Setup
    import { configure, getConsoleSink } from '@logtape/logtape';
    import { LogCategory } from '@couchbase/lite-js';
    
    await configure({
      sinks: {
        console: getConsoleSink(),
        remote: async (record) => {
          // Only send errors and warnings to remote service
          if (record.level === 'error' || record.level === 'fatal') {
            await sendToLoggingService(record);
          }
        },
      },
      loggers: [
        {
          category: LogCategory,
          lowestLevel: 'warning', // Only warnings and errors
          sinks: ['console', 'remote'],
        }
      ],
    });

    Error Tracking Integration

    Integrate with error tracking services like Sentry:

    Example 14. Sentry Integration
    import * as Sentry from '@sentry/browser';
    import { configure, getConsoleSink } from '@logtape/logtape';
    import { LogCategory } from '@couchbase/lite-js';
    
    const sentrySink = (record) => {
      if (record.level === 'error' || record.level === 'fatal') {
        Sentry.captureException(new Error(record.message), {
          level: record.level,
          extra: {
            category: record.category.join('.'),
            timestamp: record.timestamp,
          },
        });
      }
    };
    
    await configure({
      sinks: {
        console: getConsoleSink(),
        sentry: sentrySink,
      },
      loggers: [
        {
          category: LogCategory,
          lowestLevel: 'error',
          sinks: ['console', 'sentry'],
        }
      ],
    });

    Meta Logger

    LogTape has a special "meta logger" that logs LogTape’s own internal operations. This is useful for debugging logging configuration issues.

    The meta logger uses the category ["logtape", "meta"] and is automatically enabled when you call configure().

    Example 15. Configure Meta Logger
    import { configure, getConsoleSink } from '@logtape/logtape';
    import { LogCategory } from '@couchbase/lite-js';
    
    await configure({
      sinks: {
        console: getConsoleSink(),
      },
      loggers: [
        // Your app logging
        {
          category: LogCategory,
          lowestLevel: 'info',
          sinks: ['console'],
        },
        // LogTape internal logging
        {
          category: ['logtape', 'meta'],
          lowestLevel: 'debug',
          sinks: ['console'],
        },
      ],
    });
    Enabling the meta logger produces a large amount of internal logging. Use only while troubleshooting LogTape configuration issues.

    Using Browser DevTools

    Console Features

    Modern browsers provide rich console features for working with logs:

    • Filtering - Filter by log level or text

    • Stack traces - Click to jump to source code

    • Object inspection - Expand objects inline

    • Search - Search through console output

    • Timestamps - See when each log occurred

    • Copy - Right-click to copy log messages

    Console Log Levels

    LogTape maps its levels to browser console methods:

    • fatalconsole.error()

    • errorconsole.error()

    • warningconsole.warn()

    • infoconsole.info()

    • debugconsole.debug()

    Debugging Workflow

    Systematic Approach

    1. Enable logging for the relevant category

    2. Reproduce the issue while logging is active

    3. Collect logs from browser console or log files

    4. Analyze logs for errors or unexpected behavior

    5. Increase verbosity if needed (switch to debug level)

    6. Fix the issue based on insights from logs

    7. Verify fix with logging still enabled

    Common Debugging Patterns

    Example 16. Debug Specific Operation
    // Enable debug logging temporarily
    await configure({
      sinks: {
        console: getConsoleSink(),
      },
      loggers: [
        {
          category: [LogCategory, 'Sync'],
          lowestLevel: 'debug',
          sinks: ['console'],
        }
      ],
    });
    
    // Perform the operation
    try {
      await replicator.start();
      // Check console for detailed sync logs
    } catch (error) {
      console.error('Replication failed:', error);
    }

    Troubleshooting with Logs

    Common Issues and Log Patterns

    Replication Not Working:

    Enable Sync logging and look for: * WebSocket connection errors * Authentication failures (401 Unauthorized) * Network connectivity issues * Sync Gateway URL problems

    Queries Running Slow:

    Enable Query logging and look for: * "Scan collection" (no index used) * "Search index" (index used - faster) * Query execution times * Large result sets

    Storage Quota Exceeded:

    Enable DB logging and look for: * QuotaExceededError messages * Storage usage warnings * Failed save operations

    Document Conflicts:

    Enable Sync logging and look for: * Conflict resolution messages * Which documents are conflicting * Automatic vs custom resolution

    Advanced Topics

    Conditional Logging

    Enable verbose logging only in specific conditions:

    Example 17. Conditional Configuration
    const isDevelopment = process.env.NODE_ENV === 'development';
    const logLevel = isDevelopment ? 'debug' : 'warning';
    
    await configure({
      sinks: {
        console: getConsoleSink(),
      },
      loggers: [
        {
          category: LogCategory,
          lowestLevel: logLevel,
          sinks: ['console'],
        }
      ],
    });

    Runtime Reconfiguration

    You can reconfigure logging at runtime by calling configure() again:

    Example 18. Change Log Level at Runtime
    // Enable verbose logging
    window.enableDebugLogging = async () => {
      await configure({
        sinks: {
          console: getConsoleSink(),
        },
        loggers: [
          {
            category: LogCategory,
            lowestLevel: 'debug',
            sinks: ['console'],
          }
        ],
      });
      console.log('Debug logging enabled');
    };
    
    // Call from browser console: enableDebugLogging()