Drivers

Built-in log providers and guide to creating custom drivers

Logger providers (also called drivers) are responsible for processing and outputting log messages to various destinations. Vercube includes two built-in providers and makes it easy to create custom ones for any logging backend.

Built-in Providers

ConsoleProvider

The ConsoleProvider outputs colorful, human-readable logs to the console. It's ideal for development and debugging.

Usage:

import { Logger, BaseLogger, ConsoleProvider } from '@vercube/logger';

container.bind(Logger, BaseLogger);
container.get(Logger).configure({
  logLevel: 'debug',
  providers: [
    {
      name: 'console',
      provider: ConsoleProvider
    }
  ]
});

Output Format:

[12:34:56.789] [DEBUG] Looking up user: abc-123
[12:34:56.801] [INFO ] User found { userId: 'abc-123' }
[12:34:57.123] [WARN ] UserService Rate limit approaching
[12:34:58.456] [ERROR] Database connection failed Error: ...

Disabling Colors:

Set the NO_COLOR environment variable:

NO_COLOR=1 node app.js

JSONProvider

The JSONProvider outputs structured JSON logs suitable for log aggregation systems like ELK, Splunk, or CloudWatch.

Usage:

import { Logger, BaseLogger, JSONProvider } from '@vercube/logger';

container.bind(Logger, BaseLogger);
container.get(Logger).configure({
  logLevel: 'info',
  providers: [
    {
      name: 'json',
      provider: JSONProvider
    }
  ]
});

Output Format:

{"type":"application_log","level":"debug","args":["Looking up user: abc-123"],"timestamp":1703082896789}
{"type":"application_log","level":"info","args":["User found",{"userId":"abc-123"}],"timestamp":1703082896801}
{"type":"application_log","level":"warn","args":["Rate limit approaching"],"timestamp":1703082897123}
{"type":"application_log","level":"error","args":["Database failed",{"error":"..."}],"timestamp":1703082898456}

JSON Structure:

FieldTypeDescription
typestringLog type (application_log, access_log)
levelstringLog level (debug, info, warn, error)
argsarrayArray of log arguments
timestampnumberUnix timestamp in milliseconds
tagstringOptional tag for categorization
pidnumberOptional process ID

Creating Custom Providers

Creating a custom provider allows you to output logs to any destination: files, databases, external services, etc.

Basic Provider Template

import { LoggerProvider } from '@vercube/logger';
import type { LoggerTypes } from '@vercube/logger';

export class CustomProvider extends LoggerProvider {
  /**
   * Initialize the provider with custom options
   */
  public initialize(options: any): void {
    // Setup code here
    // Connect to external service, open file, etc.
  }
  
  /**
   * Process a log message
   */
  public processMessage(message: LoggerTypes.Message): void {
    // Format and output the message
    const formatted = this.format(message);
    this.output(formatted);
  }
  
  private format(message: LoggerTypes.Message): string {
    // Your formatting logic
    return JSON.stringify(message);
  }
  
  private output(formatted: string): void {
    // Your output logic
    console.log(formatted);
  }
}

Message Object Structure

The LoggerTypes.Message object contains:

interface Message {
  level: 'debug' | 'info' | 'warn' | 'error';
  args: any[];  // Arguments passed to logger method
  tag?: string;  // Optional tag
  timestamp?: number;  // Unix timestamp in ms
  pid?: number;  // Process ID
  type?: 'access_log' | 'application_log';  // Log type
}

Provider Configuration

Register Provider in Container

If your provider has dependencies, register it in the container:

import { Container } from '@vercube/di';
import { CustomProvider } from './providers/CustomProvider';

export function setupContainer(container: Container): void {
  // Register provider
  container.bind(CustomProvider);
  
  // Configure logger to use it
  container.get(Logger).configure({
    providers: [
      {
        name: 'custom',
        provider: CustomProvider,
        options: { ... }
      }
    ]
  });
}

Provider with Dependencies

If your provider needs other services:

import { Inject } from '@vercube/di';
import { LoggerProvider } from '@vercube/logger';
import { Database } from './database';

export class DatabaseProvider extends LoggerProvider {
  @Inject(Database)
  private db!: Database;
  
  public initialize(options: any): void {
    // this.db is now available
  }
  
  public processMessage(message: LoggerTypes.Message): void {
    // Use this.db to write logs
  }
}

Async Initialization

If your provider needs async initialization:

export class AsyncProvider extends LoggerProvider {
  private connection: Connection;
  
  public async initialize(options: any): Promise<void> {
    // Async initialization
    this.connection = await connectToService(options);
  }
  
  public processMessage(message: LoggerTypes.Message): void {
    // Use this.connection
  }
}

Using Multiple Providers

Configure multiple providers to output to different destinations:

container.get(Logger).configure({
  logLevel: 'debug',
  providers: [
    // Console for development
    {
      name: 'console',
      provider: ConsoleProvider,
      logLevel: 'debug'
    },
    // File for persistent storage
    {
      name: 'file',
      provider: FileProvider,
      logLevel: 'info',
      options: {
        filename: 'app.log',
        directory: './logs'
      }
    },
    // JSON for log aggregation
    {
      name: 'json',
      provider: JSONProvider,
      logLevel: 'warn'
    },
    // HTTP for external monitoring
    {
      name: 'http',
      provider: HTTPProvider,
      logLevel: 'error',
      options: {
        endpoint: 'https://logs.myservice.com/api/logs',
        apiKey: process.env.LOGGING_API_KEY
      }
    }
  ]
});

Provider Not Outputting Logs

Problem: Provider's processMessage() is not called

Solutions:

  1. Check log level hierarchy:
// If global level is 'error', only errors reach providers
container.get(Logger).configure({
  logLevel: 'debug',  // Lower this
  providers: [...]
});
  1. Check provider-specific log level:
providers: [
  {
    name: 'custom',
    provider: CustomProvider,
    logLevel: 'debug'  // This provider's level
  }
]
  1. Verify provider is registered:
// Make sure provider class is in providers array
providers: [
  {
    name: 'custom',
    provider: CustomProvider  // ✅ Correct
  }
]

Provider Initialization Error

Problem: "Failed to initialize logger provider"

Solutions:

  1. Check initialize() method doesn't throw:
public initialize(options: any): void {
  try {
    // initialization code
  } catch (error) {
    console.error('Provider init failed:', error);
    // Don't throw - handle gracefully
  }
}
  1. Verify provider is bound in container:
container.bind(CustomProvider);
  1. Check provider options:
providers: [
  {
    name: 'custom',
    provider: CustomProvider,
    options: {
      // Make sure all required options are provided
      requiredOption: 'value'
    }
  }
]

Provider Missing Dependency

Problem: Injected dependency is undefined

Solution: Make sure dependency is registered before logger configuration:

// Register dependencies first
container.bind(Database);
container.bind(CustomProvider);

// Then configure logger
container.bind(Logger, BaseLogger);
container.get(Logger).configure({
  providers: [
    {
      name: 'custom',
      provider: CustomProvider
    }
  ]
});

Async Provider Not Working

Problem: Async initialize() doesn't complete before logging

Solution: Await logger configuration:

container.bind(Logger, BaseLogger);

const logger = container.get(Logger);

// Await async configuration
await logger.configure({
  providers: [
    {
      name: 'async',
      provider: AsyncProvider,
      options: { ... }
    }
  ]
});
Previous

API

Complete API reference for Logger module

Next