Skip to main content

Plugins

3 min read

Logging Plugin

The logging plugin hooks into every lifecycle event in a Directive system and logs it to the console (or a custom logger) with a configurable level and prefix.


Basic Usage

import { loggingPlugin } from '@directive-run/core/plugins';

// Zero-config: logs info-level events and above with the [Directive] prefix
const system = createSystem({
  module: myModule,
  plugins: [loggingPlugin()],
});

system.start();

With defaults, this logs info-level events and above using console, prefixed with [Directive].


Options

OptionTypeDefaultDescription
level"debug" | "info" | "warn" | "error""info"Minimum log level. Events below this level are silenced.
filter(event: string) => boolean() => trueReturn false to suppress a specific event. Receives the event name (e.g. "fact.set").
loggerPick<Console, "debug" | "info" | "warn" | "error" | "group" | "groupEnd">consoleCustom logger implementation.
prefixstring"[Directive]"Prefix prepended to every log message.

Log Levels

Each event is assigned a fixed level. Setting level filters out everything below it.

debug

The most verbose level. Includes all internal lifecycle activity:

init, destroy, fact.set, fact.delete, facts.batch, derivation.compute, derivation.invalidate, reconcile.start, reconcile.end, constraint.evaluate, requirement.created, requirement.canceled, resolver.start, resolver.cancel, effect.run, timetravel.snapshot

info

High-level system activity:

start, stop, requirement.met, resolver.complete, timetravel.jump

warn

Recoverable problems:

resolver.retry, error.recovery

error

Failures:

constraint.error, resolver.error, effect.error, error


Event Filtering

The filter function receives the event name string and returns a boolean. It runs after the level check, so you only need to filter within your configured level.

Log only fact-related events:

loggingPlugin({
  // Show all events, then narrow down to just fact mutations
  level: 'debug',
  filter: (event) => event.startsWith('fact.'),
})

Log everything except derivation noise:

loggingPlugin({
  // Derivations recompute frequently –filter them out to reduce clutter
  level: 'debug',
  filter: (event) => !event.startsWith('derivation.'),
})

Log only resolver lifecycle:

loggingPlugin({
  // Focus on async work: starts, completions, retries, errors
  level: 'debug',
  filter: (event) => event.startsWith('resolver.'),
})

Custom Logger

Replace console with any object that implements debug, info, warn, error, group, and groupEnd:

import pino from 'pino';

const log = pino();

// Redirect Directive logs through pino instead of the browser console
loggingPlugin({
  logger: {
    debug: (...args) => log.debug(args),
    info: (...args) => log.info(args),
    warn: (...args) => log.warn(args),
    error: (...args) => log.error(args),

    // pino doesn't support console grouping, so these are no-ops
    group: () => {},
    groupEnd: () => {},
  },
})

Production

The logging plugin has no side effects beyond console output, but you should exclude it from production builds to avoid noise and minor overhead:

const plugins = [];

// Only add logging in dev –avoids console noise and minor overhead in prod
if (process.env.NODE_ENV !== 'production') {
  plugins.push(loggingPlugin({ level: 'debug' }));
}

const system = createSystem({
  module: myModule,
  plugins,
});

system.start();

Example Output

Every log line follows the format ${prefix} ${event} followed by a data object:

[Directive] start
[Directive] fact.set { key: "count", value: 1, prev: 0 }
[Directive] derivation.compute { id: "doubled", value: 2, deps: ["count"] }
[Directive] constraint.evaluate { id: "fetchWhenReady", active: true }
[Directive] requirement.created { id: "req_1", type: "FETCH_USER" }
[Directive] resolver.start { resolver: "fetchUser", requirementId: "req_1" }
[Directive] resolver.complete { resolver: "fetchUser", requirementId: "req_1", duration: 45 }
[Directive] requirement.met { id: "req_1", byResolver: "fetchUser" }
[Directive] fact.set { key: "user", value: { name: "Alice" }, prev: undefined }

Events with no associated data log just the prefix and event name (e.g. [Directive] start).


Next Steps

Previous
Overview

We care about your data. We'll never share your email.

Powered by Directive. This signup uses a Directive module with facts, derivations, constraints, and resolvers – zero useState, zero useEffect. Read how it works

Directive - Constraint-Driven State Management for TypeScript