Skip to content

Logging

Plugin Kit logs through Dart’s standard package:logging. By default, nothing is printed. You decide where the output goes.

This page covers what the runtime logs, at what level, and how to listen.

This is an operations guide. Once plugins have lifecycle, settings, and runtime reconciliation, logs become the fastest way to answer “what did the runtime actually do?”

Logger nameCovers
plugin_kit.PluginRuntimeruntime init/dispose, global plugin enable/disable, session creation, settings reconciliation, wildcard override resolution
plugin_kit.PluginSessionsession init and dispose

Both sit under plugin_kit, so you can subscribe to one specifically or to the whole library with a single listener.

/// Wires the root `plugin_kit` logger to print every record.
void listenToPluginKitLogger() {
Logger('plugin_kit').onRecord.listen((record) {
print('${record.level.name}: ${record.loggerName}: ${record.message}');
});
}

If your app already uses hyper_logger, Plugin Kit records flow through its root subscription automatically. No extra setup.

LevelWhat you will see
infoLifecycle events: runtime initialized, session created, plugin enabled or disabled, settings updated
fineDiagnostics: which plugins are enabled, wildcard override resolutions
warningPartial failures: a lifecycle phase completed but some plugins threw
severeIndividual plugin errors during attach, detach, or settings update, always with error and stack trace

Most production apps want info and above in normal operation. Turn on fine when debugging enablement or override behavior. severe is never optional signal.

Plugin Kit does not silently swallow errors.

When a plugin throws during a lifecycle phase, the runtime:

  1. Logs the failure at severe, including the stack trace.
  2. Continues processing the remaining plugins in that phase.
  3. After the phase finishes, throws PluginLifecycleException with every collected failure.
Future<void> safeInit() async {
final runtime = PluginRuntime();
try {
runtime.init();
} on PluginLifecycleException catch (e) {
print('attach failed: ${e.phase}');
// Inspect e.failures for per-plugin error details.
rethrow;
}
}

The exception carries:

  • phase: a string like 'attachGlobal', 'detachSession', 'updateSessionSettings'
  • failures: a list of (pluginId, error, stackTrace) tuples

The aggregation is deliberate. Fail-fast on the first exception would hide real problems: if plugin B and plugin C are both broken, you find out about B, fix it, and then discover C. Plugin Kit surfaces the whole batch at once.

A few things to set up once, then forget about.

Forward to your existing pipeline. Do not write a custom subscription if your app already has a logging setup. Pipe Logger.root.onRecord (or Logger('plugin_kit').onRecord) into whatever that pipeline is.

Fine-grained control during debugging.

/// Raises specific plugin_kit loggers to FINE for diagnostic output.
void setFineLogging() {
Logger('plugin_kit.PluginRuntime').level = Level.FINE;
Logger('plugin_kit.PluginSession').level = Level.FINE;
}

Raise a specific logger without turning on every library in your app.

Always handle PluginLifecycleException. Uncaught, it aborts your init or session creation. In production, catch it at the runtime boundary and decide whether to continue with a partial runtime or fail the whole process. Both are defensible. Silently ignoring is not.