Changelog
Release notes for Go Audit.
mcp v1.1.2
Patch release for the MCP server (mcp/). Depends on core v1.1.1.
Bug fixes
- mcp:
date_from/date_toonquery_data_logsandquery_api_logs, andatonsnapshot_entity, are now compared as instants on SQLite. The server parses these RFC3339 parameters and passes them to the core date filters, which onmcp/v1.1.1and earlier bound them as zone-tagged strings; a value carrying a non-UTC offset could therefore match the wrong rows. Picking up corev1.1.1fixes this (see below). PostgreSQL and MySQL were unaffected.
No tool changes: names, parameters and response shapes are identical to
mcp/v1.1.1.
Upgrade
go install github.com/gopackx/go-audit/mcp@v1.1.2v1.1.1
Core patch release. Adapter users should also pick up the matching
adapters/bun/v1.1.1 and adapters/ent/v1.1.1 below; adapters/gorm
is unchanged at v1.1.3.
Bug fixes (correctness — data loss on SQLite)
-
core / store:
Purge,DataFilter.DateFrom/DateTo,APIFilter.DateFrom/DateToand thereforeSnapshot'satnow compare instants on SQLite.created_atis written in UTC, but SQLite has no time type: the driver binds atime.Timeas text including its zone offset and SQLite compares the strings lexicographically. A cutoff built in a local zone (... 20:00:00+07:00) sorted after every UTC row written in the same hour (... 14:00:00+00:00), soPurge(time.Now().Add(-1*time.Hour))on a UTC+7 machine deleted every fresh row, and date filters missed or included rows by the zone offset. The SQL store now normalises every time parameter to UTC before binding. PostgreSQL (TIMESTAMPTZ) and MySQL (TIMESTAMP) already compared instants, so nothing changes there.Contract:
created_atis stored in UTC; callers may pass atime.Timein any zone toPurge,Snapshot,Restoreand the date filters. See Querying → Retention.
Upgrade
go get github.com/gopackx/go-audit@v1.1.1No schema change. SQLite deployments in non-UTC zones should audit
their retention jobs: rows newer than the intended cutoff may already
have been purged on v1.1.0 and earlier.
adapters/bun v1.1.1
Patch release for the Bun adapter. Requires core v1.1.0 or later.
Behaviour changes
- adapters/bun:
ErrorFailLoud(the default) is now honoured as far as Bun allows.bun.QueryHook.AfterQueryruns after the statement and cannot change the error Bun returns, so the query cannot be aborted; previously the error was silently discarded. The hook now passes it to an error handler whose default logs viaConfig.Logger(log.Printfwhen nil). See Bun → Error Handling. - Under
ErrorFailSilentnothing changed: the auditor logs viaConfig.Loggerand swallows the error.
New
auditbun.Register(db, auditor, opts ...Option)accepts functional options.auditbun.WithErrorHandler(func(ctx context.Context, err error))replaces the default handler so you can raise a metric, trip a health check, or mark the surrounding transaction for rollback. The error isgo-audit: bun: record <action> <table>/<id>: <store error>.
Upgrade
go get github.com/gopackx/go-audit/adapters/bun@v1.1.1adapters/ent v1.1.1
Patch release for the Ent adapter. Requires core v1.1.0 or later.
Behaviour changes
- adapters/ent:
ErrorFailLoud(the default) is now honoured. Previously the hook discarded audit-store errors, so a failed audit write under fail-loud went unnoticed.Mutatenow returnsgo-audit: record <action>: <store error>(unwrap witherrors.Is/errors.As) together with the already-applied mutation result, mirroring the GORM adapter'sAddError. Bulk mutations still offer every row to the store and join the failures into one error. If your code relied on Ent mutations never failing because of the audit store, wrap them in a transaction or switch that table toErrorFailSilent. See Ent → Error Handling. - Under
ErrorFailSilentnothing changed.
Upgrade
go get github.com/gopackx/go-audit/adapters/ent@v1.1.1Examples
_examples/gorm-basicand_examples/full-example:gorm.io/gormv1.25.12→v1.31.1,golang.org/x/textv0.14.0→v0.20.0. All examples requirego-audit v1.1.0(still replaced by the local checkout) instead ofv0.0.0. No library change.
adapters/gorm v1.1.3
Patch release for the GORM adapter only — core v1.1.0 is unchanged.
Bug fixes (correctness / performance)
- adapters/gorm: the pre-write snapshot for primary-key-driven
writes (
db.Model(&p).Update(...),db.Save(&p),db.Delete(&p),db.Delete(&rows)) no longer scans the whole table. GORM adds the primary-key predicate only inside its owngorm:update/gorm:deletecallbacks, after the snapshot has run, so the snapshot executedSELECT * FROM <table>with noWHERE(a 1-row update on a large table loaded every row). The adapter now builds the same primary-key predicate itself and ANDs it with any userWHERE, so the snapshot loads only the targeted rows. - adapters/gorm: WHERE-driven bulk updates and deletes
(
db.Model(&T{}).Where(...).Update(...),db.Where(...).Delete(&T{})) no longer produce a single bogus entry withentity_id "0"and emptyold_values. They now emit one entry per row found by the snapshot, with that row's realentity_id,old_valuesfrom the snapshot, andnew_values= the snapshot with the assigned columns applied (nullfor a hard delete,{"deleted_at": ...}for a soft delete). - adapters/gorm:
db.Unscoped().Delete(&row)on a row that was already soft-deleted is now recorded asdelete(with its old values) rather thansoft_delete. The snapshot also respectsUnscoped, so it finds the soft-deleted row.
Behaviour changes
- With
DataAudit.SkipOldValues: true, WHERE-driven bulk updates and deletes are no longer audited at all — without a snapshot there is no way to attribute them to rows. Previously they produced one misleadingentity_id "0"entry. Primary-key-driven writes are still audited per row. - Writes run with
AllowGlobalUpdate(noWHERE, no primary key) snapshot the whole table by design, since the write itself touches every row; each row gets its own entry.
Upgrade
go get github.com/gopackx/go-audit/adapters/gorm@v1.1.3The
adapters/gorm/v1.1.3tag is pending; until it is pushed, this command will not resolve.
adapters/gorm v1.1.2
Patch release for the GORM adapter only — core v1.1.0 is unchanged.
Bug fixes (correctness)
-
adapters/gorm (issue #1, "A2b"): a parent
Updatethat still carries its loaded HasMany associations no longer double-audits the children ascreate. Callingdb.Model(&order).Update(...)whileorder.Itemsis still attached makes GORM re-upsert the children (ON CONFLICT DO UPDATE), which runs the create-callback chain a second time — so each child was recorded ascreateagain (order_items/create == 4instead of2). Because this happens in the Update's transaction (separate from the original Create), the v1.1.1 transaction-scoped dedup could not see it.The adapter now distinguishes a genuine insert from an association re-save: a re-saved child already has a non-zero primary key before the statement runs, and the operation's root is an update/delete rather than a create. Such rows are skipped for
createauditing. Genuinely new children added during an update (PK still zero) and all top-level / nested creates remain fully audited.Workaround on
v1.1.1and earlier:db.Model(&order).Omit("Items").Update(...).
Upgrade
go get github.com/gopackx/go-audit/adapters/gorm@v1.1.2adapters/gorm v1.1.1
Patch release for the GORM adapter only — core v1.1.0 is unchanged.
Bug fixes (correctness)
- adapters/gorm: nested
Createno longer double-audits child rows on GORM ≥ v1.31 (the "2 items → 4 rows" report). Thev1.1.0dedup was scoped to a single GORM statement, but on GORM ≥ v1.31 onedb.Create(&parent)runs the child create-callback chain twice within its auto-transaction — an initial INSERT and an FK-backfill upsert, in two separate statements — so the per-statement set never caught the repeat. Dedup is now scoped to the transaction, shared across every statement of one logical write, with a depth counter so it is freed when the outermost operation completes (no leak) and two explicit sequential writes of the same row are each still audited.
Upgrade
go get github.com/gopackx/go-audit/adapters/gorm@v1.1.1If you installed adapters/gorm@v1.1.0, upgrade to pick up this fix.
No schema change and no core upgrade required.
v1.1.0
Bug fixes (correctness)
- adapters/gorm:
db.Where("…").Update/Deleteno longer emitsWHERE WHERE …(PostgreSQL42601, which previously cancelled the user's write). The pre-write snapshot SELECT now unwraps theclause.Whereexpression so the SQL builder writes exactly oneWHERE. - adapters/gorm:
Createwith nested associations no longer duplicates child audit rows (2 items → was 4 rows). The callback keeps a per-statement(entity_type, entity_id)dedup set. - core:
old_valuesoncreateis now persisted as SQLNULLinstead of the literal JSON token"null". Affected callers that passed a typed-nilmap[string]any.
New
APIEntry.ResponseHeaders(map[string]string) — redacted using the sameRedactHeaderslist asRequestHeaders, persisted into a newresponse_headerscolumn onaudit_api_logs.RedactBodyFieldsnow redacts struct and pointer payloads, not onlymap[string]any. The auditor JSON-round-trips the value first; matching is on JSON tag names.
Docs
- README pulled to parity with the docs site:
ExcludeEntities,SkipOldValues, per-tableOnError,gorm.Session{SkipHooks: true}, adapter pseudo-version install, and a dedicated*sql.DBproduction tip.
AI integration
- Claude Skill (
skills/go-audit/SKILL.md) — drop into~/.claude/skills/go-audit/and Claude Code knows how to integrate go-audit from a cold prompt. See Claude Skill. - MCP server (
mcp/) — read-only Model Context Protocol server exposingquery_data_logs,query_api_logs,query_by_transaction,snapshot_entity, andrecent_changesso AI hosts can investigate audit history conversationally. See MCP Server.
⚠️ Migration
AutoMigrate uses CREATE TABLE IF NOT EXISTS, so the new
response_headers column is only created on fresh installs. Existing
deployments must run a one-shot ALTER TABLE per dialect — see
audit_api_logs → Upgrading from v1.0.x.
v1.0.0
- Initial stable release
- Core auditor backed by
database/sql; zero network dependencies - Auto-diff on UPDATE — only changed fields stored
- Manual entry point (
RecordDataChange) for raw-SQL code paths - API call logging with header and JSON body redaction
- Body truncation marker for oversized payloads
- Transaction correlation via
transaction_idand context helpers - Snapshot and Restore — reconstruct entity state at a past time
- Retention via
Purge(ctx, before) - Configurable error modes (
ErrorFailLoud/ErrorFailSilent) - Custom table names for both audit tables
- Field and entity exclusion
- Dialects: PostgreSQL, MySQL, SQLite (with
RegisterDialectfor custom) - GORM adapter — full auto-hooks, soft-delete detection
- Bun adapter —
QueryHookwith snapshot via WHERE-clause parsing - Ent adapter — mutation hook with single-row old-values
- Query builder with typed filters (
DataFilter,APIFilter) - In-memory
Storefor tests