Chronicle

Observability

Logging, AfterRecord plugins for metrics and tracing, and AlertHandler for real-time alerting.

Chronicle does not ship a built-in OpenTelemetry package — observability is composable via the plugin system and structured logging.

Structured logging

Chronicle uses log/slog throughout. Provide your own logger:

c, err := chronicle.New(
    chronicle.WithStore(adapter),
    chronicle.WithLogger(slog.New(slog.NewJSONHandler(os.Stdout, nil))),
)

Or via the extension:

ext := extension.New(
    extension.WithStore(s),
    extension.WithLogger(myLogger),
)

Chronicle emits log records for:

  • chronicle.record — every persisted event (level Debug)
  • chronicle.verify — verification runs (level Info)
  • retention enforcement complete — enforcement results (level Info)
  • chronicle: migration failed — schema migration errors (level Error)
  • retention: enforce policy failed — per-policy errors (level Error)

Each log record includes the relevant scope fields (app_id, tenant_id) when available.

Metrics via AfterRecord

Use an AfterRecord plugin to emit metrics after every successful event:

type MetricsPlugin struct{}

func (p *MetricsPlugin) Name() string { return "metrics" }

func (p *MetricsPlugin) OnAfterRecord(ctx context.Context, event *audit.Event) error {
    otelCounter.Add(ctx, 1,
        attribute.String("category", event.Category),
        attribute.String("severity", event.Severity),
        attribute.String("outcome", event.Outcome),
    )
    return nil
}

Register with the plugin registry and pass the registry to chronicle.New (or configure it through the extension).

Tracing via AfterRecord

Add OpenTelemetry spans for each recorded event:

func (p *TracingPlugin) OnAfterRecord(ctx context.Context, event *audit.Event) error {
    _, span := tracer.Start(ctx, "chronicle.event.recorded")
    span.SetAttributes(
        attribute.String("event.id", event.ID.String()),
        attribute.String("event.action", event.Action),
        attribute.String("event.category", event.Category),
    )
    span.End()
    return nil
}

The handler package automatically creates OTel spans for all 21 HTTP endpoints using tracer name github.com/xraph/chronicle/handler.

Real-time alerting via AlertHandler

Use the AlertHandler interface for real-time alerting when events match criteria:

type PagerDutyAlertPlugin struct {
    client pagerduty.Client
}

func (p *PagerDutyAlertPlugin) Name() string { return "pagerduty-alerts" }

func (p *PagerDutyAlertPlugin) OnAlert(ctx context.Context, event *audit.Event, rule *plugin.AlertRule) error {
    return p.client.Trigger(ctx, &pagerduty.Event{
        Summary:  fmt.Sprintf("Chronicle alert: %s%s/%s", rule.Name, event.Action, event.Resource),
        Severity: "critical",
    })
}

registry := plugin.NewRegistry(logger)
registry.Register(&PagerDutyAlertPlugin{client: pd})
registry.SetAlertRules([]plugin.AlertRule{
    {Name: "critical-auth", Severity: "critical", Category: "auth"},
    {Name: "all-denied",    Outcome: "denied"},
})

Retention monitoring

The extension logs every enforcement cycle:

INFO chronicle: retention enforcement complete archived=150 purged=150
ERROR chronicle: retention enforcement failed error="..."

Set WithRetentionInterval(0) to disable automatic scheduling and call enforcer.Enforce(ctx) manually in your own monitoring loop.

On this page