Bun Adapter
Use Go Audit with the Bun ORM.
The Bun adapter installs a Bun QueryHook so every insert, update,
and delete is captured automatically.
Installation
go get github.com/gopackx/go-audit/adapters/bunSetup
import (
"context"
"database/sql"
"github.com/gopackx/go-audit"
auditbun "github.com/gopackx/go-audit/adapters/bun"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
)
// sqlDB is the underlying *sql.DB (not *bun.DB)
bunDB := bun.NewDB(sqlDB, pgdialect.New())
auditor, err := audit.New(sqlDB, audit.Config{
Dialect: audit.PostgreSQL,
UserFunc: func(ctx context.Context) (string, string) {
return "user-123", "user"
},
DataAudit: audit.DataAuditConfig{Enabled: true},
})
if err != nil {
return err
}
auditbun.Register(bunDB, auditor)
if err := auditor.AutoMigrate(context.Background()); err != nil {
return err
}
audit.Newand Bun both use the same*sql.DB. The audit hook is attached tobunDB.
How It Works
The adapter installs a single QueryHook on the bun.DB that:
- Records INSERTs with
new_values. - Before each UPDATE/DELETE, issues a
SELECTagainst the same table to snapshot affected rows (skipped ifDataAudit.SkipOldValuesis set). - Records UPDATEs with diffed old/new values.
- Records DELETEs with old values.
Snapshot Strategy (UPDATE/DELETE)
The adapter tries two strategies in order:
- By primary key. If the caller's model has PK values set
(
NewUpdate().Model(&x).WherePK()or bulk model updates), snapshots areSELECT ... WHERE pk IN (...). - By rendered WHERE clause. Otherwise, the adapter parses the
rendered SQL and issues an equivalent
SELECT * FROM <table> WHERE <same clause>.
Supported Operations
db.NewInsert()db.NewUpdate()db.NewDelete()
Batch operations are grouped under one transaction_id.
Error Handling
bun.QueryHook.AfterQuery runs after the statement has executed and
cannot change the error Bun returns to the caller, so under
ErrorFailLoud a failed audit write cannot abort the Bun query.
The query succeeds and the hook hands the audit-store error to an
error handler instead. The default handler logs it through
Config.Logger (log.Printf when nil); replace it with
auditbun.WithErrorHandler to raise a metric, trip a health check, or
mark the surrounding transaction for rollback:
auditbun.Register(bunDB, auditor, auditbun.WithErrorHandler(func(ctx context.Context, err error) {
metrics.Inc("audit_failures")
}))The error passed to the handler is
go-audit: bun: record <action> <table>/<id>: <store error> and can be
unwrapped with errors.Is / errors.As.
Under ErrorFailSilent the auditor logs and swallows the error itself,
so the handler is never called. In both modes the Bun write has already
happened by the time the audit store fails — fail-loud reports, it does
not roll back on its own.
Caveats
- Transactions. The snapshot SELECT runs against the base
*bun.DB, not the current*bun.Tx. Rows written earlier in the same transaction are not visible to the snapshot. For strict transaction-local audits, load old values explicitly before the write, or use the GORM adapter. - Bulk without PKs. If a bulk UPDATE/DELETE uses a custom predicate without PK hints, the adapter emits one audit entry per snapshotted row logging old values only — post-state is not available after a bulk write.
- Raw SQL via
db.ExecContextbypasses the hook and is not audited. ErrorFailLoudcannot fail the query. Audit-store errors reach theWithErrorHandlercallback, not the caller of the Bun query. See Error Handling.