# Custom Plugins

While a plugin architecture is nothing novel, the LogBus plugin development story is refreshingly simple. Thanks to the dynamic runtime of JavaScript, registering a custom plugin is as simple as pointing to a source file. The path can be absolute or relative, in which case it will be resolved relative to the pipeline config's directory.

# Plugin Interface

  • onInput(event): the "main" hook for a plugin. event is the object emitted by the input stages. It is up to the user (the one configuring the pipeline) to wire them up correctly. All plugins except for input plugins should define this.

  • async start(): a hook for any prep work before event processing should begin. Typically needed for input plugins. If plugin needs to set up any timers, this is the place.

  • async stop(): a hook for any cleanup work before processing stops. Examples when this might be needed: flush any buffered state, close connections or files.

This skeleton of a plugin should do a better job than trying to explain the interface:

// Emit event as-is.  Intended as a template for creating your own plugin.

import type {LogEngine, LogEvent, PluginConfig} from '@/types.ts'
import {ConfigError} from '@/lib/index.ts'

interface PassPluginConfig extends PluginConfig {
  bar: string
  foo?: string
}

export default function Plugin(config: PassPluginConfig, logbus: LogEngine) {
  // All plugin state managed in this closure.

  // Example for optional config parameters.
  const foo = config.foo ?? 'SOME DEFAULT'

  // Example required parameters.
  if (!config.bar) {
    throw new ConfigError('bar', 'missing')
  }

  function start() {
    // Not typically needed. Perform any startup tasks such as connecting to a server or loading a data file.
    // See the read-opensearch plugin for an example.
    return {msg: `${foo} ${config.bar}`, stage: logbus.stage}
  }

  function stop() {
    // Not typically needed. Perform any shutdown tasks such as flushing buffers.
    // See the write-opensearch plugin for an example.
  }

  function onInput(event: LogEvent) {
    // Perform any run-time checks that make sense for this plugin.
    if (typeof event.bar !== 'string') {
      return
    }
    try {
      // Perform any filtering & transformation logic before emitting events to downstream stages.
      if (event.bar.startsWith(config.bar)) {
        if (!event.foo) {
          event.foo = foo
        }
        logbus.event(event)
        // Support for collecting & publishing metrics (see `stats` plugin).
        logbus.stats({rxEvents: 1})
      }
    } catch (err) {
      // Try to catch & handle all errors, passing as much or as little of the event to help trouble-shoot.
      logbus.error(err as Error, event)
    }
  }

  return {start, stop, onInput}
}

Plugins can be written in JavaScript or TypeScript. For dependencies, it is recommended to use npm: & jsr: prefixes as described in the Deno docs. Doing this avoids needing to perform any "install dependencies" step since Deno will do that automatically. Also, Deno will automatically compile TypeScript plugins, avoiding the need for a separate compile step.

For some real-world example plugins, please see the journald-opensearch example which also shows how to perform unit tests.