Documentation
¶
Overview ¶
Package audit provides a simple, thread-safe audit logging library for tracking entity changes and events in Go applications.
The library supports:
- Creating, updating, and deleting entity audit logs
- Tracking field-level changes with before/after values
- Hiding sensitive data (e.g., passwords, tokens)
- Concurrent access with sync.RWMutex
- Filtering events by payload fields
Basic usage:
logger := audit.New()
logger.Create("user:123", "admin", "User created", map[string]audit.Value{
"email": audit.PlainValue("user@example.com"),
"password": audit.HiddenValue(),
})
See the examples directory for complete usage examples.
Example ¶
package main
import (
"fmt"
"github.com/w0rng/audit"
)
func main() {
// Create a new audit logger
logger := audit.New()
// Log entity creation
logger.Create("order:12345", "john.doe", "Order created", map[string]audit.Value{
"status": audit.PlainValue("pending"),
"total": audit.PlainValue(99.99),
})
// Log entity update
logger.Update("order:12345", "jane.smith", "Order approved", map[string]audit.Value{
"status": audit.PlainValue("approved"),
})
// Get change history
changes := logger.Logs("order:12345")
fmt.Printf("Total changes: %d\n", len(changes))
fmt.Printf("Last change: %s\n", changes[len(changes)-1].Description)
}
Output: Total changes: 2 Last change: Order approved
Index ¶
- Constants
- type Action
- type Change
- type ChangeField
- type Event
- type InMemoryStorage
- type Logger
- func (l *Logger) Create(key, author, description string, payload map[string]Value)
- func (l *Logger) Delete(key, author, description string, payload map[string]Value)
- func (l *Logger) Events(key string, fields ...string) []Event
- func (l *Logger) LogChange(key string, action Action, author, description string, ...)
- func (l *Logger) Logs(key string) []Change
- func (l *Logger) Update(key, author, description string, payload map[string]Value)
- type Option
- type Storage
- type Value
Examples ¶
Constants ¶
const ( ActionCreate Action = "create" ActionUpdate Action = "update" ActionDelete Action = "delete" HideText string = "***" )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Change ¶
type Change struct {
Fields []ChangeField
Description string
Author string
Timestamp time.Time
}
type ChangeField ¶
type InMemoryStorage ¶
type InMemoryStorage struct {
// contains filtered or unexported fields
}
InMemoryStorage provides a thread-safe in-memory storage implementation backed by a map. This is the default storage used by New().
func NewInMemoryStorage ¶
func NewInMemoryStorage() *InMemoryStorage
NewInMemoryStorage creates a new in-memory storage instance.
func (*InMemoryStorage) Clear ¶
func (s *InMemoryStorage) Clear(key string)
Clear removes all events for a given key.
func (*InMemoryStorage) Get ¶
func (s *InMemoryStorage) Get(key string) []Event
Get retrieves all events for a given key. Returns an empty slice if the key doesn't exist.
func (*InMemoryStorage) Has ¶
func (s *InMemoryStorage) Has(key string) bool
Has checks if any events exist for a given key.
func (*InMemoryStorage) Store ¶
func (s *InMemoryStorage) Store(key string, event Event)
Store appends an event to the storage for the given key.
type Logger ¶
type Logger struct {
// contains filtered or unexported fields
}
Logger provides thread-safe audit logging functionality.
func New ¶
New creates a new Logger with the given options. If no options are provided, it uses in-memory storage by default.
Example:
logger := audit.New() // uses in-memory storage logger := audit.New(audit.WithStorage(customStorage)) // uses custom storage
func (*Logger) Create ¶
Example ¶
package main
import (
"fmt"
"github.com/w0rng/audit"
)
func main() {
logger := audit.New()
logger.Create(
"user:123",
"admin",
"User account created",
map[string]audit.Value{
"email": audit.PlainValue("user@example.com"),
"password": audit.HiddenValue(),
},
)
events := logger.Events("user:123")
fmt.Printf("Created %d event(s)\n", len(events))
}
Output: Created 1 event(s)
func (*Logger) Delete ¶
Example ¶
package main
import (
"fmt"
"github.com/w0rng/audit"
)
func main() {
logger := audit.New()
logger.Create("item:1", "user", "Item created", map[string]audit.Value{
"name": audit.PlainValue("Widget"),
})
logger.Delete("item:1", "admin", "Item deleted", map[string]audit.Value{})
events := logger.Events("item:1")
fmt.Printf("Total events: %d\n", len(events))
fmt.Printf("Last action: %s\n", events[len(events)-1].Action)
}
Output: Total events: 2 Last action: delete
func (*Logger) Events ¶
Events retrieves audit events for a key, optionally filtering by specific payload fields. If no fields are specified, all events for the key are returned. When fields are provided, only events containing at least one of those fields are returned, with their payloads filtered to include only the requested fields.
Example ¶
package main
import (
"fmt"
"github.com/w0rng/audit"
)
func main() {
logger := audit.New()
logger.Create("order:1", "user", "Order created", map[string]audit.Value{
"status": audit.PlainValue("pending"),
"total": audit.PlainValue(100.50),
})
logger.Update("order:1", "user", "Status updated", map[string]audit.Value{
"status": audit.PlainValue("paid"),
})
// Get only status-related events
events := logger.Events("order:1", "status")
fmt.Printf("Found %d status events\n", len(events))
}
Output: Found 2 status events
Example (MultipleFields) ¶
package main
import (
"fmt"
"github.com/w0rng/audit"
)
func main() {
logger := audit.New()
logger.Create("user:1", "admin", "User created", map[string]audit.Value{
"email": audit.PlainValue("user@example.com"),
"role": audit.PlainValue("editor"),
"status": audit.PlainValue("active"),
})
logger.Update("user:1", "admin", "Role updated", map[string]audit.Value{
"role": audit.PlainValue("admin"),
})
logger.Update("user:1", "system", "Status changed", map[string]audit.Value{
"status": audit.PlainValue("inactive"),
})
// Get events that have either role or status fields
events := logger.Events("user:1", "role", "status")
fmt.Printf("Events with role or status: %d\n", len(events))
}
Output: Events with role or status: 3
Example (NoFilter) ¶
package main
import (
"fmt"
"github.com/w0rng/audit"
)
func main() {
logger := audit.New()
logger.Create("item:1", "user", "Created", map[string]audit.Value{
"name": audit.PlainValue("Widget"),
})
logger.Update("item:1", "user", "Updated", map[string]audit.Value{
"price": audit.PlainValue(29.99),
})
// Get all events (no filter)
events := logger.Events("item:1")
fmt.Printf("Total events: %d\n", len(events))
}
Output: Total events: 2
func (*Logger) LogChange ¶
func (l *Logger) LogChange(key string, action Action, author, description string, payload map[string]Value)
LogChange records a new audit event for the given key with the specified action, author, description, and payload. This is the core logging method used by Create, Update, and Delete convenience methods.
func (*Logger) Logs ¶
Logs returns the complete change history for a key with field-level state transitions. It reconstructs the state over time, tracking before/after values for each field.
Example ¶
package main
import (
"fmt"
"github.com/w0rng/audit"
)
func main() {
logger := audit.New()
logger.Create("item:1", "alice", "Item created", map[string]audit.Value{
"color": audit.PlainValue("red"),
})
logger.Update("item:1", "bob", "Color changed", map[string]audit.Value{
"color": audit.PlainValue("blue"),
})
changes := logger.Logs("item:1")
for _, change := range changes {
fmt.Printf("%s by %s\n", change.Description, change.Author)
for _, field := range change.Fields {
fmt.Printf(" %s: %v -> %v\n", field.Field, field.From, field.To)
}
}
}
Output: Item created by alice color: <nil> -> red Color changed by bob color: red -> blue
func (*Logger) Update ¶
Example ¶
package main
import (
"fmt"
"github.com/w0rng/audit"
)
func main() {
logger := audit.New()
logger.Create("order:1", "user", "Order created", map[string]audit.Value{
"status": audit.PlainValue("pending"),
})
logger.Update("order:1", "admin", "Order approved", map[string]audit.Value{
"status": audit.PlainValue("approved"),
})
events := logger.Events("order:1")
fmt.Printf("Total events: %d\n", len(events))
}
Output: Total events: 2
type Option ¶
type Option func(*Logger)
Option is a function that configures a Logger.
func WithStorage ¶
WithStorage sets a custom storage implementation for the logger. If not specified, NewInMemoryStorage() is used by default.
Example ¶
package main
import (
"fmt"
"github.com/w0rng/audit"
)
func main() {
// Create a custom storage implementation
customStorage := audit.NewInMemoryStorage()
// Use WithStorage option to configure logger
logger := audit.New(audit.WithStorage(customStorage))
logger.Create("order:1", "user", "Order created", map[string]audit.Value{
"status": audit.PlainValue("pending"),
})
events := logger.Events("order:1")
fmt.Printf("Logged %d event(s) with custom storage\n", len(events))
}
Output: Logged 1 event(s) with custom storage
type Storage ¶
type Storage interface {
// Store appends an event to the storage for the given key
Store(key string, event Event)
// Get retrieves all events for a given key.
// Returns an empty slice if key doesn't exist.
Get(key string) []Event
// Has checks if any events exist for a given key
Has(key string) bool
// Clear removes all events for a given key
Clear(key string)
}
Storage defines the interface for storing and retrieving audit events. Implementations must be safe for concurrent access.
type Value ¶
func HiddenValue ¶
func HiddenValue() Value
HiddenValue creates a Value with Hidden=true to mask sensitive data in logs. Hidden values are displayed as "***" to prevent exposure of passwords, tokens, etc.
Example ¶
package main
import (
"fmt"
"github.com/w0rng/audit"
)
func main() {
const hideField = "password"
logger := audit.New()
logger.Create("user:1", "admin", "User created", map[string]audit.Value{
"email": audit.PlainValue("user@example.com"),
hideField: audit.HiddenValue(),
})
changes := logger.Logs("user:1")
for _, field := range changes[0].Fields {
if field.Field == hideField {
fmt.Printf("%s: %v -> %v\n", field.Field, field.From, field.To)
}
}
}
Output: password: *** -> ***
func PlainValue ¶
PlainValue creates a Value with the given data visible in logs. Use this for non-sensitive data that can be safely logged.
Example ¶
package main
import (
"fmt"
"github.com/w0rng/audit"
)
func main() {
logger := audit.New()
logger.Create("config:1", "admin", "Config updated", map[string]audit.Value{
"timeout": audit.PlainValue(30),
"enabled": audit.PlainValue(true),
"name": audit.PlainValue("production"),
})
events := logger.Events("config:1")
fmt.Printf("Logged %d event(s)\n", len(events))
}
Output: Logged 1 event(s)
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
basic
command
|
|
|
custom_storage
command
|
|
|
slog_integration
command
|
|
|
internal
|
|
|
be
Package be provides minimal assertions for Go tests.
|
Package be provides minimal assertions for Go tests. |
|
Package slog provides integration between Go's structured logging (slog) and audit logging.
|
Package slog provides integration between Go's structured logging (slog) and audit logging. |