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()