GORM Adapter
Use Go Audit with GORM via the auto-hook plugin.
The GORM adapter registers callbacks on the GORM connection so every
Create, Save, Updates, and Delete is tracked with no changes
to your model or query code.
Installation
go get github.com/gopackx/go-audit/adapters/gormSetup
import (
"context"
"database/sql"
"github.com/gopackx/go-audit"
auditgorm "github.com/gopackx/go-audit/adapters/gorm"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
gormDB, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
return err
}
sqlDB, err := gormDB.DB()
if err != nil {
return err
}
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
}
if err := gormDB.Use(auditgorm.Plugin(auditor)); err != nil {
return err
}
if err := auditor.AutoMigrate(context.Background()); err != nil {
return err
}Note the two-step DB handle:
audit.Newtakes the underlying*sql.DB(fromgormDB.DB()), whilePluginattaches to the*gorm.DB.
How It Works
The plugin registers five callbacks:
after_create— recordsActionCreatewithnew_values.before_update— snapshots the current row viaSELECT(unlessDataAudit.SkipOldValuesis set).after_update— diffs the snapshot against the new values, recordsActionUpdatewith only the changed fields.before_delete— snapshots the row; detects soft deletes via presence of adeleted_atfield.after_delete— recordsActionDelete(hard delete) orActionSoftDelete(soft delete viagorm.DeletedAt).
Errors from audit writes are attached to the *gorm.DB via
AddError, so callers see them via db.Error.
Supported Operations
db.Create()/db.CreateInBatches()db.Save()db.Updates()(struct ormap[string]any)db.UpdateColumn()/db.UpdateColumns()db.Delete()— hard and soft deletes- Bulk operations — all affected rows share one
transaction_id
Configuration
audit.Config{
Dialect: audit.PostgreSQL,
DataAudit: audit.DataAuditConfig{
Enabled: true,
Table: "audit_logs", // override table name
ExcludeFields: []string{"password", "token"},
ExcludeEntities: []string{"sessions"},
SkipOldValues: false, // true = skip pre-UPDATE SELECT
OnError: audit.ErrorFailLoud,
},
UserFunc: func(ctx context.Context) (string, string) {
return "user-123", "user"
},
}Soft Delete Detection
If your model includes a gorm.DeletedAt field, GORM performs an
UPDATE rather than a DELETE. The adapter detects this and records
the action as "soft_delete" with the new deleted_at value in
new_values. Hard deletes (e.g. .Unscoped().Delete()) are recorded
as "delete" with new_values: null.
Caveats
- Raw SQL executed via
db.Exec()bypasses GORM's callbacks and is not audited. Use model-based calls when you want audit trail. db.Session(&gorm.Session{SkipHooks: true})explicitly skips audit writes.- Queries that don't go through GORM (e.g. direct
database/sqlon the same connection) are not audited.