Query & Filters
Cross-table queries, retention, snapshot, and restore.
Auditor.QueryByTransaction(ctx, txID) → (*TransactionLog, error)
Fetches all data audit rows and API call rows sharing a
transaction_id.
trail, err := auditor.QueryByTransaction(ctx, txID)
// trail.DataLogs []AuditLog
// trail.APILogs []AuditAPILogAPI logs are only included if APIAudit.Enabled == true.
audit.TransactionLog
type TransactionLog struct {
TransactionID string
DataLogs []AuditLog
APILogs []AuditAPILog
}Use this in UIs that show a transaction timeline or in support tooling that reconstructs what happened during a single user action.
Pagination
Every filter type exposes Limit and Offset:
page1, _ := auditor.Query(ctx, audit.DataFilter{Limit: 25, Offset: 0})
page2, _ := auditor.Query(ctx, audit.DataFilter{Limit: 25, Offset: 25})For very large tables, prefer keyset pagination against created_at
(set the next page's DateTo to the previous page's last row's
CreatedAt) over deep offset pagination.
Sorting
All queries return results ordered by id DESC (newest first). Since
id is a monotonic auto-increment key, this matches insertion order —
effectively created_at DESC for normally-inserted rows.
Transaction ID Helpers
func audit.NewTransactionID() string
func audit.WithTransactionID(ctx context.Context, txID string) context.Context
func audit.TransactionIDFromContext(ctx context.Context) stringNewTransactionID returns a YYYYMMDDTHHmmss-<32-char hex> string
(lexicographically sortable by time). TransactionIDFromContext
returns the empty string when no ID is set.
Auditor.Purge(ctx, before) → (PurgeResult, error)
Deletes rows older than before from audit_logs and
audit_api_logs (only the tables that are enabled).
cutoff := time.Now().AddDate(-1, 0, 0) // 1 year ago
result, err := auditor.Purge(ctx, cutoff)
// result.DataLogs: int64 rows deleted
// result.APILogs: int64 rows deletedReturns a PurgeResult:
type PurgeResult struct {
DataLogs int64
APILogs int64
}Run Purge from a daily cron to enforce retention policies.
Auditor.Snapshot(ctx, entityType, entityID, at) → (map[string]any, error)
Reconstructs the state of an entity at a given point in time by
replaying its audit history up to at.
state, err := auditor.Snapshot(ctx,
"orders", "42",
time.Date(2026, 3, 1, 12, 0, 0, 0, time.UTC),
)
// state is the map of column → value at that momentReturns nil (without error) if the entity did not exist or had been
deleted at that time. Requires non-empty entityType, entityID,
and a non-zero at time.
Useful for "what did this record look like when X happened?" questions during incident response.
Auditor.Restore(ctx, entityType, entityID, at) → (*RestoreResult, error)
Reconstructs the target state via Snapshot, then writes a restore
audit entry (old values = current state, new values = target state),
and returns the target values for the caller to apply via their ORM.
result, err := auditor.Restore(ctx, "orders", "42", oldTime)
if err != nil { return err }
// Apply the returned values via GORM
gormDB.Model(&Order{ID: 42}).Updates(result.Values)type RestoreResult struct {
EntityType string `json:"entity_type"`
EntityID string `json:"entity_id"`
Values map[string]any `json:"values,omitempty"`
WasDeleted bool `json:"was_deleted"`
}The ORM adapter will not record the application-level write as a
separate update — the restore audit entry already captures it. Your
caller is responsible for actually persisting the restored values.