Transaction Correlation
Link related data changes and API calls with a shared transaction ID.
A single user action often causes multiple side effects: a DB write, one or more third-party API calls, another DB write. Transaction correlation lets you link them all under one ID so you can replay the full story later.
Generating a Transaction ID
txID := audit.NewTransactionID()
// "20260418T142318-9f3c2a71c8e4d2b15c90a63e4f712b88"
ctx = audit.WithTransactionID(ctx, txID)NewTransactionID returns a YYYYMMDDTHHmmss-<32-char hex> string —
the timestamp prefix makes IDs sortable while the random suffix keeps
them globally unique.
Every audit write on that context — data or API — receives the same
transaction_id value unless DataEntry.TransactionID or
APIEntry.TransactionID is set explicitly on the entry itself.
Full Flow Example
func Transfer(ctx context.Context, orderID string, amount int) error {
ctx = audit.WithTransactionID(ctx, audit.NewTransactionID())
// 1. Call the payment gateway — logged to audit_api_logs
ref, err := bca.Transfer(ctx, amount)
if err != nil {
return err
}
// 2. Update the order — logged to audit_logs
return gormDB.WithContext(ctx).
Model(&Order{}).
Where("id = ?", orderID).
Updates(map[string]any{
"payment_ref": ref,
"status": "paid",
}).Error
}Both the BCA API call row and the order update row carry the same
transaction_id.
Reading the Transaction ID
txID := audit.TransactionIDFromContext(ctx) // "" if unsetUseful inside middleware that wants to log the current transaction ID alongside application logs.
Querying by Transaction
trail, err := auditor.QueryByTransaction(ctx, txID)
// trail.DataLogs []AuditLog
// trail.APILogs []AuditAPILogOutput Shape
type TransactionLog struct {
TransactionID string
DataLogs []AuditLog
APILogs []AuditAPILog
}This gives you a single object that reconstructs the full sequence of side effects for the transaction — perfect for support tickets, post-mortems, and compliance reviews.
Batch Writes
ORM adapters automatically group batch writes under a single
transaction_id per SQL statement, even if the caller didn't stamp a
transaction ID onto the context. That means every row updated by a
bulk UPDATE ... WHERE ... ends up in the same transaction group,
and QueryByTransaction returns them together.