Recording Events
The EventBuilder API and how Chronicle persists events to the hash chain.
EventBuilder
All events are created through the fluent EventBuilder API. Start with a severity constructor on the chronicle.Chronicle (or chronicle.Emitter) instance, chain builder methods, then call Record():
err := c.Info(ctx, "login", "session", "sess-001").
Category("auth").
UserID("user-42").
SubjectID("user-42").
Meta("provider", "okta").
Outcome("success").
Record()Severity constructors
| Method | Severity | Description |
|---|---|---|
c.Info(ctx, action, resource, resourceID) | "info" | Routine operational events |
c.Warning(ctx, action, resource, resourceID) | "warning" | Anomalies needing attention |
c.Critical(ctx, action, resource, resourceID) | "critical" | Security events or failures |
All three take the same positional arguments: action (verb), resource (noun), and resourceID (ID of the specific instance).
Builder methods
| Method | Description |
|---|---|
.Category(s) | Required. Logical group for compliance filtering (e.g. "auth", "billing") |
.UserID(s) | Override the user ID from context |
.TenantID(s) | Override the tenant ID from context |
.AppID(s) | Override the app ID from context |
.SubjectID(s) | GDPR data subject identifier (required for crypto-erasure) |
.Meta(k, v) | Add a metadata key-value pair (can be called multiple times) |
.Outcome(s) | "success", "failure", or "denied" |
.Reason(s) | Human-readable reason string |
The TenantID and AppID override methods are useful when recording events on behalf of a different scope than the one stored in the context — for example, in an admin or background job context.
Terminal methods
| Method | Description |
|---|---|
.Record() | Build and persist the event. Returns an error. |
.Event() | Build and return the *audit.Event without persisting. |
Required fields
Chronicle rejects events missing any of these three fields:
- Action — the positional first argument to
c.Info/c.Warning/c.Critical - Resource — the positional second argument
- Category — set via
.Category(s)
AppID is also required and is extracted from the context automatically.
Record pipeline
When Record() is called, Chronicle executes these 7 steps:
- Apply scope —
scope.ApplyToEvent(ctx, event)stamps AppID, TenantID, UserID, IP. - Assign identity — assigns a
audit_TypeID and the current timestamp. - Validate — returns an error if Action, Resource, Category, or AppID are missing.
- Resolve stream — looks up (or creates) the stream for the app+tenant scope.
- Compute hash —
SHA256(prevHash|timestamp|action|resource|category|resourceID|outcome|severity|metadata_json). - Store.Append — persists the event via the
chronicle.Storerinterface. - Update stream head — advances HeadHash and HeadSeq on the stream.
Plugin BeforeRecord hooks fire between steps 3 and 4. Plugin AfterRecord hooks fire after step 7.
Direct record
You can also construct an audit.Event directly and call c.Record(ctx, event):
event := &audit.Event{
Action: "export",
Resource: "report",
Category: "billing",
Severity: audit.SeverityInfo,
Outcome: audit.OutcomeSuccess,
}
err := c.Record(ctx, event)Severity and outcome constants
The audit package exports named constants for severity and outcome to avoid stringly-typed code:
import "github.com/xraph/chronicle/audit"
// Severity constants
audit.SeverityInfo // "info"
audit.SeverityWarning // "warning"
audit.SeverityCritical // "critical"
// Outcome constants
audit.OutcomeSuccess // "success"
audit.OutcomeFailure // "failure"
audit.OutcomeDenied // "denied"Use these instead of raw string literals for type-safety and refactor-friendliness:
event := &audit.Event{
Action: "export",
Resource: "report",
Category: "billing",
Severity: audit.SeverityInfo,
Outcome: audit.OutcomeSuccess,
}Querying events
result, err := c.Query(ctx, &audit.Query{
Category: "auth",
Severity: "critical",
Limit: 50,
Order: "desc",
})Queries are always scoped to the app + tenant from the context. Available filters:
| Field | Description |
|---|---|
Category | Filter by event category |
Action | Filter by action verb |
Resource | Filter by resource noun |
UserID | Filter by user |
Severity | Filter by severity level |
Outcome | Filter by outcome |
From / To | Time range filter |
Limit / Offset | Pagination |
Order | "asc" or "desc" (by timestamp) |
Aggregate queries
result, err := c.Aggregate(ctx, &audit.AggregateQuery{
GroupBy: "category",
AppID: "myapp",
})
// result.Buckets: []AggregateItem{Name, Count}