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 Interface
Section titled “Logger Interface”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 Interface
Section titled “Store Interface”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
Section titled “Filter”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}Writing Audit Entries
Section titled “Writing Audit Entries”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.
Querying Entries
Section titled “Querying Entries”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
Section titled “InMemoryStore”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.
Registry
Section titled “Registry”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"]Config
Section titled “Config”type Config struct { Extra map[string]any // provider-specific key-value configuration}Related Packages
Section titled “Related Packages”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.