go-auditGo Audit
ORM Adapters

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/bun

Setup

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.New and Bun both use the same *sql.DB. The audit hook is attached to bunDB.

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 SELECT against the same table to snapshot affected rows (skipped if DataAudit.SkipOldValues is set).
  • Records UPDATEs with diffed old/new values.
  • Records DELETEs with old values.

Snapshot Strategy (UPDATE/DELETE)

The adapter tries two strategies in order:

  1. By primary key. If the caller's model has PK values set (NewUpdate().Model(&x).WherePK() or bulk model updates), snapshots are SELECT ... WHERE pk IN (...).
  2. 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:

Go
auditbun.Register(bunDB, auditor, auditbun.WithErrorHandler(func(ctx context.Context, err error) {
    metrics.Inc("audit_failures")
}))
Go snippet

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.ExecContext bypasses the hook and is not audited.
  • ErrorFailLoud cannot fail the query. Audit-store errors reach the WithErrorHandler callback, not the caller of the Bun query. See Error Handling.

On this page