How to Debug Cordis Applications and Inspect Plugin State: A Complete Guide

To debug Cordis applications, intercept the LoggerService to enable debug-level output and use the Registry to retrieve live plugin instances at runtime, while the Journal tracks all reactive state mutations.

Cordis is a context-oriented, service-based plugin framework maintained in the cordiverse/cordis repository. When you need to debug Cordis applications, the framework exposes powerful introspection capabilities through its core services. Understanding how to leverage the LoggerService, Registry, and Journal enables you to trace execution flow and inspect plugin state without external debugging tools.

Enable Debug Logging with LoggerService

The LoggerService in packages/core/src/logger.ts provides the central logging mechanism for the entire framework. By default, it records messages at error, info, warn, and debug levels, with all internal components—including the loader, HMR subsystem, and plugin registrar—emitting diagnostic information through ctx.logger.

Intercepting the Logger Service

Interceptors are a first-class feature in Cordis. You can raise the minimum log level by adding an interceptor to the logger service from any Context instance:

// Raise the log level to DEBUG for the entire application
ctx.intercept('logger', { level: 3 })   // 3 === LoggerLevel.DEBUG

Once the level is set to 3, all calls to ctx.logger.debug() will emit output. The logger supports contextual formatting with %C for colorized plugin names:

ctx.logger.debug('Loading plugin %C', pluginName)

Adding Custom Exporters

You can redirect logs to files, remote collectors, or custom UIs by registering an exporter on the logger service:

ctx.logger.exporter({
  colors: false,
  export(message) {
    // Write JSON lines to file
    fs.appendFileSync('cordis.log', JSON.stringify(message) + '\n')
  },
})

The framework's internal modules respect this configuration. For example, the loader logs each resolved module at packages/loader/src/index.ts, and the HMR subsystem in packages/hmr/src/index.ts reports when plugins are skipped or reloaded.

Inspecting Live Plugin State via the Registry

The Registry in packages/core/src/registry.ts serves as the central catalog for all plugins. Every plugin registered via ctx.plugin(...) is stored here with its instance and metadata, making runtime inspection straightforward.

Retrieving Plugin Instances

Access any registered plugin at runtime using the registry's get method:

const myPlugin = ctx.registry.get('my-plugin')
// Or using the shortcut:
// const myPlugin = ctx.plugin('my-plugin')

Once retrieved, you can read any public properties the plugin exposes:

console.log('Current counter value:', myPlugin.counter)

Serializing State with Formatters

For quick ad-hoc inspection, dump the entire plugin object using the logger's %o formatter:

ctx.logger.debug('Plugin state: %o', myPlugin)

Because the Registry holds live references, retrieving a plugin always yields the current object, not a stale snapshot.

Tracing State Changes with the Journal

The Journal in packages/include/src/journal.ts records every mutation performed through Cordis's reactive APIs. Subscribing to the journal allows you to see exactly what changed, when the change occurred, and which plugin caused it:

ctx.journal.subscribe(entry => {
  ctx.logger.debug('Journal entry: %o', entry)
})

This is particularly useful when debugging state synchronization issues or tracking down which component modified shared data.

Complete Debugging Workflow

Combine these mechanisms to establish a comprehensive debugging session:

// 1️⃣ Configure logger to emit DEBUG
ctx.intercept('logger', { level: 3 })

// 2️⃣ Add a file exporter (optional)
ctx.logger.exporter({
  colors: false,
  export(message) {
    fs.appendFileSync('debug.log', JSON.stringify(message) + '\n')
  },
})

// 3️⃣ Load the application (loader emits debug logs automatically)
await ctx.loader.loadAll()

// 4️⃣ Grab a plugin and inspect its state
const auth = ctx.registry.get('auth')
ctx.logger.debug('Auth plugin config: %o', auth.config)

// 5️⃣ Observe runtime changes via the journal
ctx.journal.subscribe(e => ctx.logger.debug('Journal %C', e.type, e))

All core subsystems use the logger with contextual data, the Registry maintains the single source of truth for plugin instances, and the Journal captures deterministic histories of state changes.

Summary

  • LoggerService (packages/core/src/logger.ts) provides configurable logging with interceptor support for level control and custom exporters for output redirection.
  • Registry (packages/core/src/registry.ts) stores live plugin instances accessible via ctx.registry.get(name), enabling runtime state inspection.
  • Journal (packages/include/src/journal.ts) tracks every reactive mutation, offering a deterministic audit trail of state changes.
  • Debug output includes colorized plugin names via %C and object serialization via %o formatters.
  • Internal modules like the loader and HMR automatically emit debug information when the log level is set appropriately.

Frequently Asked Questions

How do I enable debug logging in Cordis?

Add a logger interceptor with ctx.intercept('logger', { level: 3 }) anywhere you have access to a Context instance. This raises the minimum log level to LoggerLevel.DEBUG, causing all internal framework components and your own ctx.logger.debug() calls to emit output.

Can I inspect a plugin's internal state at runtime?

Yes. Use ctx.registry.get('plugin-name') to retrieve the live plugin instance from the Registry in packages/core/src/registry.ts. You can then access any public properties or methods the plugin exposes, or serialize the entire object using ctx.logger.debug('state: %o', plugin).

What is the Journal service used for?

The Journal in packages/include/src/journal.ts records every mutation made through Cordis's reactive APIs. By subscribing with ctx.journal.subscribe(callback), you receive real-time notifications of state changes including the type of change, the affected data, and the originating plugin.

Where are plugin instances stored in Cordis?

All plugin instances are stored in the Registry, implemented in packages/core/src/registry.ts. This central catalog maintains a map of plugin names to their active instances, serving as the single source of truth for the application's plugin state throughout the lifecycle.

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 →