Cordis Logger Plugin Integration with Events System: A Complete Technical Guide

The Cordis logger plugin integrates with the events system by registering exporters that listen to the LoggerService, while the core EventsService handles internal plugin lifecycle hooks like internal/plugin to enable dynamic exporter registration at runtime.

Cordis ships with a built-in logger service exposed on every Context instance as ctx.logger. This service works hand-in-hand with the events subsystem to emit log messages as regular events, allowing plugins—including the console logger—to listen, filter by level, and format output dynamically. This article examines the source code in the cordiverse/cordis repository to explain exactly how the logger plugin integrates with Cordis's event-driven architecture.

Core Logger Implementation in packages/core/src/logger.ts

The foundation of Cordis logging resides in packages/core/src/logger.ts, which defines the Logger class, the LoggerService callable, and the static helper methods used by the events system.

LoggerService is instantiated inside the Context constructor and registers a default exporter that buffers messages. Each Logger instance (created via ctx.logger(name)) stores its own name, level, and metadata, then forwards every logging call (error, info, warn, debug) to the internal _method implementation.

Inside _method, the logger constructs a message object (Message) and dispatches it to every registered exporter that accepts the current log level:

// packages/core/src/logger.ts – core dispatch logic
export class Logger {
  private _method(type: LoggerType, level: number): LoggerMethod {
    return (...args) => {
      const sn = ++this.service._snMessage
      const ts = Date.now()
      for (const exporter of this.service.exporters.values()) {
        const targetLevel = exporter.levels?.[this.name] ?? exporter.levels?.default ?? this.level ?? LoggerLevel.INFO
        if (targetLevel < level) continue
        const message: Message = { sn, ts, type, level, name: this.name, ...this.meta, args }
        exporter.export(message)               // <-- integration point with events system
      }
    }
  }
}

Exporters are simple objects implementing an export(message) function. While the default exporter only buffers messages, specialized plugins like the console logger implement custom exporters that interact with the broader events ecosystem.

Events System Architecture in packages/core/src/events.ts

The events engine is defined in packages/core/src/events.ts, providing methods like emit, parallel, serial, bail, and waterfall. While the logger does not call the events engine directly for every log line, the integration occurs through internal lifecycle hooks.

The logger service registers listeners for the internal/plugin event (handled within packages/core/src/context.ts), allowing plugins to react when new services are registered. This enables hot-module-replacement (HMR) and dynamic exporter registration:

// packages/core/src/events.ts – internal listener registration
this.on('internal/listener', function (this: Context, name, listener, options) {
  if (name === 'internal/update' && !options.global) {
    const hooks = this.fiber._hooks['internal/update'] ??= new DisposableList()
    const method = options.prepend ? 'unshift' : 'push'
    return hooks[method](listener)
  }
})

When a plugin (such as @cordis/logger-console) loads via ctx.loader, the events system triggers the internal/plugin hook, giving the logger plugin an opportunity to attach its exporter to the LoggerService.

Console Logger Plugin Implementation

The concrete console implementation lives in packages/logger-console/src/index.ts and packages/logger-console/src/shared.ts. The ConsoleExporter extends the abstract base exporter, adding Node.js util.inspect formatters (%o and %O) and color support detection:

// packages/logger-console/src/index.ts – console exporter implementation
export class ConsoleExporter extends Base {
  formatters: Record<string, Formatter> = {
    o: inspectFormatter,
    O: inspectFormatter,
  }

  getDefaults() {
    return {
      ...super.getDefaults(),
      colors: (supportsColor.stdout ? supportsColor.stdout.level : 0) as false | 0 | 1 | 2 | 3,
    }
  }
}

When the plugin loads, it calls ctx.logger.exporter(new ConsoleExporter()). The exporter's export method receives every Message produced by the core logger, applies formatting and color codes via Logger.color, and writes to stdout.

Step-by-Step Integration Flow

The integration between the Cordis logger plugin and events system follows this sequence:

  1. Context Creation – Context instantiates LoggerService and attaches it to the context prototype as ctx.logger.
  2. Plugin Loading – When @cordis/logger-console installs, its setup function registers a ConsoleExporter via ctx.logger.exporter().
  3. Event Hook Registration – The events system emits internal/plugin, allowing the logger to finalize exporter setup and register for internal/update hooks if needed.
  4. Logging Call – ctx.logger('my-plugin').info('message') creates a Message object and iterates through all registered exporters.
  5. Message Export – Each exporter's export(message) method receives the message. The console exporter formats and prints it, while custom exporters might emit additional events or write to external systems.

Practical Implementation Examples

Here is a complete example demonstrating how to initialize the Cordis logger plugin, create named loggers, and implement custom exporters that leverage the events system:

import { createApp } from 'cordis'
import ConsoleLogger from '@cordis/logger-console'

const app = createApp()

// Install the console logger plugin (registers exporter via events system)
app.plugin(ConsoleLogger)

// Create a named logger instance
const log = app.logger('my-plugin')

// Log at different levels with formatting
log.info('Server started at %s', new Date().toLocaleTimeString())
log.warn('Low memory: %d MB left', process.memoryUsage().heapTotal / 1e6)
log.error(new Error('Something went wrong'))

// Dynamically add a custom exporter (e.g., for file logging or external services)
app.logger.exporter({
  colors: false,
  export(message) {
    // Custom logic: write JSON to file, send to remote service, etc.
    console.log('FILE:', JSON.stringify(message))
  },
})

Summary

  • packages/core/src/logger.ts contains the LoggerService and Logger classes that dispatch messages to registered exporters via the _method implementation.
  • packages/core/src/events.ts provides the event infrastructure used for internal/plugin and internal/update hooks, enabling dynamic exporter registration.
  • Exporters act as the bridge between the logger and the events system, implementing the export(message) interface to receive log data.
  • packages/logger-console/src/index.ts demonstrates a concrete implementation using ConsoleExporter with formatting and color support.
  • The integration is runtime-extensible: plugins can register or remove exporters dynamically through the events system without restarting the application.

Frequently Asked Questions

How does the Cordis logger plugin integrate with the events system?

The integration occurs through the LoggerService registering exporters that listen to the internal/plugin event. When plugins load, the events system triggers these hooks, allowing exporters to attach themselves to the logger. The LoggerService then broadcasts log messages to all registered exporters, effectively treating them as event listeners for log data.

What is the role of exporters in the Cordis logging system?

Exporters are objects implementing an export(message) method that receives Message objects from the LoggerService. They act as the terminal point for log output, handling formatting, filtering by level, and transport (console, file, network). The default exporter buffers messages, while plugins like @cordis/logger-console provide specialized exporters that write to specific destinations.

How do I create a custom logger exporter in Cordis?

Create an object with an export(message) method and pass it to ctx.logger.exporter(). The method receives a Message object containing sn (sequence number), ts (timestamp), type, level, name, and args. You can optionally include a levels property to control filtering per logger name. For example, you could implement a file exporter that writes JSON lines or a webhook exporter that POSTs to an external service.

Where is the LoggerService instantiated in the Cordis codebase?

LoggerService is instantiated inside the Context constructor located in packages/core/src/context.ts. It is immediately attached to the context prototype as ctx.logger, making it available to all plugins and application code within that context scope. The service maintains a registry of exporters and a message sequence counter used for ordering log entries.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →