Oak is a command-line tool that automatically generates LogValue() methods for Go structs to integrate with Go's log/slog package. It provides structured logging with built-in support for sensitive data redaction and field exclusion.
- 🚀 Seamless Integration: Works with
go generateworkflow - 🔒 Security First: Automatic redaction of sensitive fields
- ⚙️ Configurable: YAML-based configuration for flexible field handling
- 🎯 Type-Aware: Intelligent mapping to appropriate slog functions
- 📦 Package-Friendly: Processes entire packages or individual files
- 🔧 Pointer Safe: Handles nil pointers gracefully
- 🏷️ Tag Support: Respects struct tags for field control
go install github.com/stuckinforloop/oak/cmd/oak@latest- Create an
oak.yamlconfiguration file in your project root:
packages:
- ./internal/booking
redactKeys:
- password
- secret
- creditcard
redactMessage: "[REDACTED]"- Add the generate directive to your Go structs:
package booking
import "time"
//go:generate oak
type Reservation struct {
ID int
GuestName string
Email string
Password string // Will be redacted (matches redactKeys)
CreditCard string `log:"redact"` // Will be redacted (explicit tag)
CheckIn time.Time
Notes string `log:"-"` // Will be excluded from logs
IsConfirmed bool
Amount float64
ContactInfo *ContactInfo // Pointer handled safely
}
//go:generate oak
type ContactInfo struct {
Phone string
Address string
}- Run code generation:
go generate ./...
# or
oak ./...- Use the generated LogValue methods:
reservation := Reservation{
ID: 123,
GuestName: "John Doe",
Email: "[email protected]",
Password: "secret123",
CreditCard: "4111-1111-1111-1111",
CheckIn: time.Now(),
Notes: "Private notes",
IsConfirmed: true,
Amount: 299.99,
ContactInfo: &ContactInfo{
Phone: "+1-555-0123",
Address: "123 Main St",
},
}
slog.Info("Processing reservation", "reservation", reservation)Output:
Processing reservation reservation.ID=123 reservation.GuestName="John Doe" reservation.Email="[email protected]" reservation.Password="[REDACTED]" reservation.CreditCard="[REDACTED]" reservation.CheckIn="2024-01-15T10:30:00Z" reservation.IsConfirmed=true reservation.Amount=299.99 reservation.ContactInfo.Phone="+1-555-0123" reservation.ContactInfo.Address="123 Main St"
Note: The Notes field is completely excluded, and sensitive fields are redacted.
# Process current directory based on oak.yaml
oak
# Process all packages recursively
oak ./...
# Process specific package
oak ./internal/booking
oak --package ./internal/booking
# Process specific file
oak --source ./internal/booking/booking.go
# Show help
oak --help
# Show version
oak --versionOak uses an oak.yaml file in your project root for configuration:
# List of packages to scan for //go:generate oak directives
packages:
- ./internal/booking
- ./pkg/user
- ./cmd/server
# List of field names to automatically redact (case-insensitive)
redactKeys:
- password
- secret
- token
- apikey
- api_key
- privatekey
- private_key
- creditcard
- ssn
# Message to use for redacted fields
redactMessage: "[REDACTED]"Oak supports struct tags for fine-grained control:
type User struct {
ID int
Username string
Password string `log:"redact"` // Explicitly redact this field
Notes string `log:"-"` // Exclude this field from logs
Email string // Normal logging
}Oak intelligently maps Go types to appropriate slog functions:
- Integers (
int,int8,int16,int32,int64,uint, etc.) →slog.Int64 - Strings (
string) →slog.String - Booleans (
bool) →slog.Bool - Floats (
float32,float64) →slog.Float64 - Complex types (structs, slices, maps, interfaces) →
slog.Any - Pointers → Handled with nil checks, logging "null" for nil values
For the struct above, Oak generates:
// Code generated by oak. DO NOT EDIT.
package booking
import "log/slog"
// LogValue implements slog.LogValuer for Reservation
func (r Reservation) LogValue() slog.Value {
return slog.GroupValue(
slog.Int64("ID", int64(r.ID)),
slog.String("GuestName", r.GuestName),
slog.String("Email", r.Email),
slog.String("Password", "[REDACTED]"),
slog.String("CreditCard", "[REDACTED]"),
slog.Any("CheckIn", r.CheckIn),
slog.Bool("IsConfirmed", r.IsConfirmed),
slog.Float64("Amount", r.Amount),
func() slog.Attr {
if r.ContactInfo == nil {
return slog.String("ContactInfo", "null")
}
return slog.Any("ContactInfo", *r.ContactInfo)
}(),
)
}Oak works seamlessly with Go's go generate tool:
- Add
//go:generate oakcomments to your structs - Run
go generate ./...as part of your build process - Generated files are automatically created/updated
- Go 1.21+ (for
log/slogsupport) oak.yamlconfiguration file in project root
MIT License - see LICENSE file for details.