go-auditGo Audit
Examples

API Call Logging Example

Track a BCA payment call alongside the order data change.

This example shows a payment flow where a BCA API call and an order update are linked via a shared transaction_id.

Complete Code

package main

import (
    "bytes"
    "context"
    "encoding/json"
    "io"
    "log"
    "net/http"
    "time"

    "github.com/gopackx/go-audit"
    auditgorm "github.com/gopackx/go-audit/adapters/gorm"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

type Order struct {
    ID         uint `gorm:"primaryKey"`
    Amount     int
    Status     string
    PaymentRef string
}

func main() {
    gormDB, err := gorm.Open(postgres.Open("..."), &gorm.Config{})
    if err != nil {
        log.Fatal(err)
    }
    sqlDB, err := gormDB.DB()
    if err != nil {
        log.Fatal(err)
    }

    auditor, err := audit.New(sqlDB, audit.Config{
        Dialect: audit.PostgreSQL,
        UserFunc: func(ctx context.Context) (string, string) {
            return "user-42", "user"
        },
        DataAudit: audit.DataAuditConfig{Enabled: true},
        APIAudit: audit.APIAuditConfig{
            Enabled:          true,
            RedactHeaders:    []string{"Authorization"},
            RedactBodyFields: []string{"card_number", "cvv"},
            MaxBodySize:      64 * 1024,
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    _ = gormDB.Use(auditgorm.Plugin(auditor))
    _ = auditor.AutoMigrate(context.Background())
    _ = gormDB.AutoMigrate(&Order{})

    order := Order{Amount: 100000, Status: "pending"}
    gormDB.Create(&order)

    txID := audit.NewTransactionID()
    ctx := audit.WithTransactionID(context.Background(), txID)
    tx := gormDB.WithContext(ctx)

    // Outbound API call to BCA
    reqBody, _ := json.Marshal(map[string]any{"amount": order.Amount})
    req, _ := http.NewRequestWithContext(ctx,
        http.MethodPost,
        "https://api.bca.co.id/v1/transfer",
        bytes.NewReader(reqBody),
    )
    req.Header.Set("Authorization", "Bearer secret")
    req.Header.Set("Content-Type", "application/json")

    headers := map[string]string{}
    for k, v := range req.Header {
        if len(v) > 0 {
            headers[k] = v[0]
        }
    }

    start := time.Now()
    resp, err := http.DefaultClient.Do(req)
    duration := time.Since(start)

    var respPayload map[string]any
    if resp != nil {
        body, _ := io.ReadAll(resp.Body)
        _ = json.Unmarshal(body, &respPayload)
    }

    _ = auditor.API().Record(ctx, audit.APIEntry{
        Service:        "bca",
        Endpoint:       "/v1/transfer",
        Method:         http.MethodPost,
        StatusCode:     statusCode(resp),
        RequestHeaders: headers,
        RequestBody:    map[string]any{"amount": order.Amount},
        ResponseBody:   respPayload,
        DurationMs:     int(duration.Milliseconds()),
        ErrorMessage:   errString(err),
    })

    // Update the order to reflect the payment outcome
    ref, _ := respPayload["reference"].(string)
    tx.Model(&order).Updates(map[string]any{
        "payment_ref": ref,
        "status":      "paid",
    })

    // Fetch the full transaction trail
    trail, _ := auditor.QueryByTransaction(ctx, txID)
    _ = trail // contains both DataLogs and APILogs
}

statusCode and errString are trivial helpers — return 0 / "" when the input is nil.

What You Get

  • audit_logs has one row for the order update with the new payment_ref and status.
  • audit_api_logs has one row for the BCA call with the Authorization header replaced by "***REDACTED***" and the full response body (or a truncation marker if it exceeded MaxBodySize).
  • Both rows share the same transaction_id.

Next

See the full example for a multi-step flow with several API calls.

On this page