Chronicle

Plugins

The Chronicle plugin system — hook interfaces, the registry, and ErrSkipEvent.

Chronicle's plugin system allows you to extend the record pipeline and react to system events without modifying Chronicle internals. Plugins implement any subset of the hook interfaces. The registry discovers capabilities at registration time for O(1) per-event dispatch overhead.

Plugin base interface

Every plugin must implement plugin.Plugin:

type Plugin interface {
    Name() string
}

Implement any additional interfaces below to opt into specific hooks.

Hook interfaces

OnInit / OnShutdown

func (p *MyPlugin) OnInit(ctx context.Context) error { ... }
func (p *MyPlugin) OnShutdown(ctx context.Context) error { ... }

OnInit fires when Chronicle starts. OnShutdown fires when Chronicle stops.

BeforeRecord

func (p *MyPlugin) OnBeforeRecord(ctx context.Context, event *audit.Event) error {
    event.Metadata["server"] = "us-east-1"  // enrich
    return nil
}

Fires before store.Append. Use for:

  • Enrichment — add computed metadata fields
  • Filtering — return plugin.ErrSkipEvent to silently discard the event
  • Transformation — modify any non-identity, non-hash field

AfterRecord

func (p *MyPlugin) OnAfterRecord(ctx context.Context, event *audit.Event) error {
    notify(event)
    return nil
}

Fires after store.Append succeeds. Use for notifications, alerting, forwarding, metrics counters.

SinkProvider

func (p *MySinkPlugin) Sink() sink.Sink {
    return sink.NewS3(client, "audit-bucket", "events/")
}

Provides a custom sink.Sink that is added to Chronicle's fan-out.

AlertHandler

func (p *MyPlugin) OnAlert(ctx context.Context, event *audit.Event, rule *plugin.AlertRule) error {
    send(fmt.Sprintf("ALERT: %s fired for event %s", rule.Name, event.ID))
    return nil
}

Fires when a recorded event matches an alert rule set on the registry.

Exporter

func (p *MyPlugin) Format() string { return "ndjson" }
func (p *MyPlugin) Export(ctx context.Context, data any, w io.Writer) error { ... }

Provides a custom export format for compliance reports.

ComplianceReporter

func (p *MyPlugin) ReportType() string { return "pci-dss" }

Provides a custom compliance report type.

Hook interface summary

InterfaceMethodWhen
OnInitOnInit(ctx)Chronicle starts
OnShutdownOnShutdown(ctx)Chronicle stops
BeforeRecordOnBeforeRecord(ctx, event)Before store.Append
AfterRecordOnAfterRecord(ctx, event)After store.Append succeeds
SinkProviderSink()Once at registration
AlertHandlerOnAlert(ctx, event, rule)When an alert rule matches
ExporterFormat() / Export(...)On report export
ComplianceReporterReportType()On compliance report generation

ErrSkipEvent

Returning plugin.ErrSkipEvent from OnBeforeRecord aborts the record pipeline for that event. No error is propagated to the caller — the event is silently dropped.

func (p *MyPlugin) OnBeforeRecord(ctx context.Context, event *audit.Event) error {
    if event.Category == "debug" {
        return plugin.ErrSkipEvent
    }
    return nil
}

Registry

import "github.com/xraph/chronicle/plugin"

registry := plugin.NewRegistry(logger)
registry.Register(&MyPlugin{})
registry.Register(&MySinkPlugin{})

// Set alert rules
registry.SetAlertRules([]plugin.AlertRule{
    {Name: "critical-auth", Severity: "critical", Category: "auth"},
})

The registry inspects each plugin at Register time to determine which interfaces it implements. At event time, only the registered interface paths are called — there is no runtime interface{} type assertion per event.

Full example

type EnrichmentPlugin struct{}

func (p *EnrichmentPlugin) Name() string { return "enrichment" }

func (p *EnrichmentPlugin) OnBeforeRecord(ctx context.Context, event *audit.Event) error {
    event.Metadata["region"] = "us-east-1"
    event.Metadata["version"] = "2.1.0"
    return nil
}

func (p *EnrichmentPlugin) OnAfterRecord(ctx context.Context, event *audit.Event) error {
    metrics.Counter("chronicle.events.recorded", 1, tag("category", event.Category))
    return nil
}

registry := plugin.NewRegistry(slog.Default())
registry.Register(&EnrichmentPlugin{})

On this page