Skip to content
Docs

Audit API — Structured Audit Logging

import "github.com/lookatitude/beluga-ai/audit"

Package audit provides structured, queryable audit logging for the Beluga AI framework. It separates the write path (Logger) from the read path (Store), allowing write-only integrations where query access is not needed. All implementations must be safe for concurrent use.

Entry is a single structured audit record describing one action in the system. IDs and timestamps are auto-generated by the store when the corresponding fields are empty.

type Entry struct {
ID string
Timestamp time.Time
TenantID string
AgentID string
SessionID string
Action string // dot-separated, e.g. "tool.execute" or "llm.generate"
Input any // redact PII before logging
Output any // redact PII before logging
Metadata map[string]string
Error string // empty when the action succeeded
Duration time.Duration
}

Callers are responsible for redacting sensitive data — PII, API keys, tokens, and passwords — from Input and Output before passing an Entry to Log. The store does not inspect or sanitize field contents.

Logger is the write-only interface for emitting audit entries. Use Logger when the consuming code only needs to append records.

type Logger interface {
Log(ctx context.Context, entry Entry) error
}

Store extends Logger with a query method. Use Store when historical entries must be retrieved for compliance reports or debugging.

type Store interface {
Logger
Query(ctx context.Context, filter Filter) ([]Entry, error)
}

Filter constrains a Query call. All fields are optional; zero values match any record.

type Filter struct {
TenantID string
AgentID string
SessionID string
Action string
Since time.Time
Until time.Time
Limit int // 0 or negative means no cap
}
import (
"context"
"fmt"
"time"
"github.com/lookatitude/beluga-ai/audit"
)
store := audit.NewInMemoryStore()
err := store.Log(context.Background(), audit.Entry{
TenantID: "tenant-1",
AgentID: "planner",
SessionID: "session-abc",
Action: "tool.execute",
Input: map[string]string{"tool": "calculator", "expression": "2+2"},
Output: map[string]string{"result": "4"},
Duration: 12 * time.Millisecond,
})
if err != nil {
fmt.Println("log error:", err)
}

The store generates a cryptographically random ID and sets the Timestamp to UTC now when those fields are zero in the provided Entry.

entries, err := store.Query(context.Background(), audit.Filter{
TenantID: "tenant-1",
Action: "tool.execute",
Since: time.Now().Add(-1 * time.Hour),
Limit: 100,
})
if err != nil {
fmt.Println("query error:", err)
}
for _, e := range entries {
fmt.Printf("%s %s %s\n", e.Timestamp.Format(time.RFC3339), e.AgentID, e.Action)
}

Entries are returned in insertion order. When Limit is positive the result is capped at that count.

InMemoryStore is the built-in Store implementation. It is intended for development and testing. Memory growth is bounded by a configurable entry cap; the oldest entries are evicted when the limit is reached.

store := audit.NewInMemoryStore(
audit.WithMaxEntries(10000),
)

The default cap is 100,000 entries. InMemoryStore is registered under the name "inmemory" via the package’s init function.

The registry allows pluggable Store backends. Register is called from init functions and panics on duplicate names or an empty name.

// Register a custom backend (call from your package's init).
func init() {
audit.Register("bigquery", func(cfg audit.Config) (audit.Store, error) {
return newBigQueryStore(cfg)
})
}
// Create a store by name.
store, err := audit.New("bigquery", audit.Config{
Extra: map[string]any{
"project": "my-gcp-project",
"dataset": "beluga_audit",
},
})
if err != nil {
// handle registration miss
}
// Discover registered names.
names := audit.List() // e.g. ["bigquery", "inmemory"]
type Config struct {
Extra map[string]any // provider-specific key-value configuration
}
  • cost — Usage tracking; pair with audit for full observability of LLM calls.
  • auth — Authorization events can be piped to audit for compliance trails.
  • o11y — OpenTelemetry tracing; correlate audit entries with trace IDs via Metadata.
  • core — Error types propagated on context cancellation.
  • docs/concepts.md — Multi-tenancy model and how TenantID scopes audit records.