Skip to main content

Interceptors and events

An interceptor sees every statement a context runs, bulk writes and raw SQL included. Logging, metrics, and tracing are all built on it, and since an ant.Option can install one, it's the plugin API too.

An interceptor​

Before can return a new context, as tracing needs. It can change the statement, and an error stops it. After hears the outcome.

type Timer struct{}

func (Timer) Before(ctx context.Context, s *ant.Statement) (context.Context, error) {
return ctx, nil
}

func (Timer) After(ctx context.Context, s *ant.Statement, o ant.Outcome) {
slog.Info("query", "sql", s.SQL, "took", o.Duration)
}

db, err := ant.Open[AppDB](ctx, postgres.Dialect, dsn, ant.WithInterceptor(Timer{}))

s.Replica says where the statement ran, and o.Err is any failure. s.Args holds real values, so take care what you record.

Watching saves​

An interceptor that also has SavingChanges is a SaveInterceptor. It sees each SaveChanges before it writes, as whole entities and field names, and an error cancels the save, so it suits rules that span a unit of work.

type Audit struct {
Timer
}

func (Audit) SavingChanges(ctx context.Context, changes []ant.Change) error {
for _, c := range changes {
slog.Info("saving", "model", c.Model, "state", c.State.String(), "fields", c.Changed)
}

return nil
}

Events​

Events are callbacks on the unit of work as a whole. They can't stop anything, and a nil callback is skipped. Tracked and StateChanged must not call back into the context.

db, err := ant.Open[AppDB](ctx, postgres.Dialect, dsn,
ant.WithEvents(ant.Events{
Saving: func(ctx context.Context, changes []ant.Change) {},
Saved: func(ctx context.Context, changes []ant.Change) {},
SaveFailed: func(ctx context.Context, err error) {},
Tracked: func(model string, entity any) {},
StateChanged: func(model string, entity any, from, to ant.State) {
slog.Info("state", "model", model, "from", from.String(), "to", to.String())
},
}),
)

Which one​

You needUse
Every statement, including raw SQLInterceptor
To refuse or rewrite a statementInterceptor.Before
To inspect or refuse a saveSaveInterceptor
To follow the unit of workEvents
To react to one model, after commitObserver