Logging Standards
Required Package
All logging MUST use go.uber.org/zap. Never use:
fmt.Print* statements for logging
- Standard library
log.* package
log/slog package
Logger Injection
Services and handlers receive the logger via dependency injection:
type Service struct {
logger *zap.Logger
}
func NewService(logger *zap.Logger) *Service {
return &Service{logger: logger}
}
Log Levels
| Level |
Method |
When to Use |
| Debug |
logger.Debug() |
Detailed diagnostic info, disabled in production |
| Info |
logger.Info() |
Normal operations, state changes, request handling |
| Warn |
logger.Warn() |
Unexpected but recoverable situations |
| Error |
logger.Error() |
Failures requiring attention, operation failed |
| Fatal |
logger.Fatal() |
Unrecoverable errors, exits program |
Structured Fields
Use zap field constructors, not string interpolation:
// Good - structured with typed fields
logger.Info("order processed",
zap.String("order_id", order.ID),
zap.String("user_id", user.ID),
zap.Int64("amount", order.Amount),
)
// Bad - string interpolation
logger.Info(fmt.Sprintf("order %s processed for user %s", order.ID, user.ID))
Zap Field Constructors
| Constructor |
Use For |
zap.String("key", val) |
String values |
zap.Int("key", val) |
Integers |
zap.Int64("key", val) |
64-bit integers |
zap.Uint64("key", val) |
Unsigned 64-bit integers |
zap.Float64("key", val) |
Floating point |
zap.Bool("key", val) |
Booleans |
zap.Duration("key", val) |
time.Duration values |
zap.Time("key", val) |
time.Time values |
zap.Error(err) |
Error values (key is "error") |
zap.Any("key", val) |
Any type (use sparingly, prefer typed) |
Contextual Fields
Every log entry should include relevant context:
| Context |
Example Fields |
| Request |
zap.String("request_id", id), zap.String("method", method) |
| Entity |
zap.String("order_id", id), zap.Uint64("chain_id", chainID) |
| Operation |
zap.String("operation", op), zap.String("action", action) |
Error Logging
Error logs MUST include:
- The error via
zap.Error(err)
- Enough context to debug without reproducing
- Operation that failed
// Good
logger.Error("failed to process deposit",
zap.Error(err),
zap.String("deposit_id", deposit.ID),
zap.String("pool_address", pool.Address),
zap.Int64("amount", deposit.Amount),
)
// Bad - no context
logger.Error("deposit failed", zap.Error(err))
Fatal Logging
Use logger.Fatal() only for unrecoverable startup errors:
logger.Fatal("failed to connect to database", zap.Error(err))
Sensitive Data
NEVER log:
- Passwords, tokens, API keys, or secrets
- Private keys or seed phrases
- Full request/response bodies with sensitive fields
- PII without proper redaction
// Bad
logger.Info("user login", zap.String("password", password))
logger.Debug("request body", zap.Any("body", reqBody))
// Good
logger.Info("user login", zap.String("user_id", userID))
logger.Debug("request received", zap.Int("content_length", len(reqBody)))
Review Checklist
1---2name: logging-standards3description: Zap logging standards for Go services. Use when writing, generating, or reviewing code that includes logging.4---5
6# Logging Standards
7
8## Required Package
9
10All logging MUST use **`go.uber.org/zap`**. Never use:
11- `fmt.Print*` statements for logging
12- Standard library `log.*` package
13- `log/slog` package
14
15## Logger Injection
16
17Services and handlers receive the logger via dependency injection:
18
19```go
20type Service struct {
21 logger *zap.Logger
22}
23
24func NewService(logger *zap.Logger) *Service {
25 return &Service{logger: logger}
26}
27```
28
29## Log Levels
30
31| Level | Method | When to Use |
32|-------|--------|-------------|
33| **Debug** | `logger.Debug()` | Detailed diagnostic info, disabled in production |
34| **Info** | `logger.Info()` | Normal operations, state changes, request handling |
35| **Warn** | `logger.Warn()` | Unexpected but recoverable situations |
36| **Error** | `logger.Error()` | Failures requiring attention, operation failed |
37| **Fatal** | `logger.Fatal()` | Unrecoverable errors, exits program |
38
39## Structured Fields
40
41Use zap field constructors, not string interpolation:
42
43```go
44// Good - structured with typed fields
45logger.Info("order processed",
46 zap.String("order_id", order.ID),
47 zap.String("user_id", user.ID),
48 zap.Int64("amount", order.Amount),
49)
50
51// Bad - string interpolation
52logger.Info(fmt.Sprintf("order %s processed for user %s", order.ID, user.ID))
53```
54
55## Zap Field Constructors
56
57| Constructor | Use For |
58|-------------|---------|
59| `zap.String("key", val)` | String values |
60| `zap.Int("key", val)` | Integers |
61| `zap.Int64("key", val)` | 64-bit integers |
62| `zap.Uint64("key", val)` | Unsigned 64-bit integers |
63| `zap.Float64("key", val)` | Floating point |
64| `zap.Bool("key", val)` | Booleans |
65| `zap.Duration("key", val)` | `time.Duration` values |
66| `zap.Time("key", val)` | `time.Time` values |
67| `zap.Error(err)` | Error values (key is "error") |
68| `zap.Any("key", val)` | Any type (use sparingly, prefer typed) |
69
70## Contextual Fields
71
72Every log entry should include relevant context:
73
74| Context | Example Fields |
75|---------|---------------|
76| **Request** | `zap.String("request_id", id)`, `zap.String("method", method)` |
77| **Entity** | `zap.String("order_id", id)`, `zap.Uint64("chain_id", chainID)` |
78| **Operation** | `zap.String("operation", op)`, `zap.String("action", action)` |
79
80## Error Logging
81
82Error logs MUST include:
83- The error via `zap.Error(err)`
84- Enough context to debug without reproducing
85- Operation that failed
86
87```go
88// Good
89logger.Error("failed to process deposit",
90 zap.Error(err),
91 zap.String("deposit_id", deposit.ID),
92 zap.String("pool_address", pool.Address),
93 zap.Int64("amount", deposit.Amount),
94)
95
96// Bad - no context
97logger.Error("deposit failed", zap.Error(err))
98```
99
100## Fatal Logging
101
102Use `logger.Fatal()` only for unrecoverable startup errors:
103
104```go
105logger.Fatal("failed to connect to database", zap.Error(err))
106```
107
108## Sensitive Data
109
110NEVER log:
111- Passwords, tokens, API keys, or secrets
112- Private keys or seed phrases
113- Full request/response bodies with sensitive fields
114- PII without proper redaction
115
116```go
117// Bad
118logger.Info("user login", zap.String("password", password))
119logger.Debug("request body", zap.Any("body", reqBody))
120
121// Good
122logger.Info("user login", zap.String("user_id", userID))
123logger.Debug("request received", zap.Int("content_length", len(reqBody)))
124```
125
126## Review Checklist
127
128- [ ] No `fmt.Print*` statements for logging
129- [ ] No standard library `log.*` statements
130- [ ] `go.uber.org/zap` used consistently
131- [ ] Logger injected via `*zap.Logger` parameter
132- [ ] Appropriate log levels used
133- [ ] Typed zap field constructors used (not `zap.Any`)
134- [ ] Contextual fields included (IDs, operation names)
135- [ ] Error logs include `zap.Error(err)` and context
136- [ ] No sensitive data exposure