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

> Debug Cordis applications by intercepting LoggerService for debug output and using Registry to get live plugin instances. Learn how the Journal tracks state mutations.

- Repository: [Cordiverse/cordis](https://github.com/cordiverse/cordis)
- Tags: how-to-guide
- Published: 2026-09-12

---

**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`](https://github.com/cordiverse/cordis/blob/main/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:

```typescript
// 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:

```typescript
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:

```typescript
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`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/index.ts), and the **HMR** subsystem in [`packages/hmr/src/index.ts`](https://github.com/cordiverse/cordis/blob/main/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`](https://github.com/cordiverse/cordis/blob/main/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:

```typescript
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:

```typescript
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:

```typescript
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`](https://github.com/cordiverse/cordis/blob/main/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:

```typescript
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:

```typescript
// 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`](https://github.com/cordiverse/cordis/blob/main/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`](https://github.com/cordiverse/cordis/blob/main/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`](https://github.com/cordiverse/cordis/blob/main/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`](https://github.com/cordiverse/cordis/blob/main/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`](https://github.com/cordiverse/cordis/blob/main/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`](https://github.com/cordiverse/cordis/blob/main/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.