Skip to main content

Logging

Plugins have two logging surfaces:
  • api.logger (PluginLogger) — the plugin-scoped logger injected into register(). Use this for all logging inside your plugin’s register function and tool/hook handlers.
  • createSubsystemLogger — creates a named logger for standalone modules, background workers, or code that runs outside the plugin registration context.
Both are imported from openclaw/plugin-sdk. Neither is console.log — bare console.log bypasses the gateway’s log routing and should never be used in production code.

api.logger (inside plugins)

api.logger is a PluginLogger tied to your plugin’s id. It is the correct logger for all plugin handler code:
PluginLogger methods: info, warn, error, debug.
api.logger.debug may not be a no-op even in production — it respects the gateway’s configured log level. Only call debug for information that would be too noisy at info. Never debug-log secrets or full payloads.

createSubsystemLogger (standalone modules)

For code that lives outside a plugin (a shared utility, a background worker, a standalone script) use createSubsystemLogger:
The subsystem name appears in log output as a prefix (e.g. [my-plugin/worker]). Keep names short and slash-separated to match the convention used by core subsystems.

Available methods

SubsystemLogger has more methods than PluginLogger:

Child loggers

Use child() to add context to a logger without changing the subsystem name:
Child loggers inherit the parent’s log level and output targets.

Checking log level before expensive work

isEnabled(level, target?) lets you skip expensive serialization when a level is suppressed:
target values: "any" (default), "console", "file" — checks whether the level is active for a specific output target.

Which logger to use

What not to do

Async safety

logger.info() and other log methods are synchronous — they do not block the event loop. Do not await them.