Unexpected but recoverable: deprecated feature used, retry succeeded
Enabled
Error
Operation failed, requires operator attention
Enabled
Rules of thumb
:
If nobody should act on it, it's not Error — use Warn or Info
If it's only useful with a debugger attached, it's Debug
slog.Error
should always include an
"err"
attribute
slog
.
Error
(
"payment failed"
,
"err"
,
err
,
"order_id"
,
id
)
slog
.
Warn
(
"retry succeeded"
,
"attempt"
,
n
,
"endpoint"
,
url
)
slog
.
Info
(
"server started"
,
"addr"
,
addr
)
slog
.
Debug
(
"cache lookup"
,
"key"
,
key
,
"hit"
,
hit
)
Read
references/LEVELS-AND-CONTEXT.md
when choosing between Warn and Error or defining custom verbosity levels.
Request-Scoped Logging
Advisory
Derive loggers from context to carry request-scoped fields.
Use middleware to enrich a logger with request ID, user ID, or trace ID, then
pass the enriched logger downstream via context or as an explicit parameter:
func
middleware
(
next http
.
Handler
)
http
.
Handler
{
return
http
.
HandlerFunc
(
func
(
w http
.
ResponseWriter
,
r
*
http
.
Request
)
{
logger
:=
slog
.
With
(
"request_id"
,
requestID
(
r
)
)
ctx
:=
context
.
WithValue
(
r
.
Context
(
)
,
loggerKey
,
logger
)
next
.
ServeHTTP
(
w
,
r
.
WithContext
(
ctx
)
)
}
)
}
All subsequent log calls in that request carry
request_id
automatically.
Read
references/LOGGING-PATTERNS.md
when implementing logging middleware or passing loggers through context.
Log or Return, Not Both
Normative
Handle each error exactly once — either log it or return it.
Logging an error and then returning it causes duplicate noise as callers up the
stack also handle the error.
// Bad: logged here AND by every caller up the stack
if
err
!=
nil
{
slog
.
Error
(
"query failed"
,
"err"
,
err
)
return
fmt
.
Errorf
(
"query: %w"
,
err
)
}
// Good: wrap and return — let the caller decide
if
err
!=
nil
{
return
fmt
.
Errorf
(
"query: %w"
,
err
)
}
Exception
HTTP handlers and other top-of-stack boundaries may log detailed
errors server-side while returning a sanitized message to the client:
if
err
!=
nil
{
slog
.
Error
(
"checkout failed"
,
"err"
,
err
,
"user_id"
,
uid
)
http
.
Error
(
w
,
"internal error"
,
http
.
StatusInternalServerError
)
return
}
See
go-error-handling
for the full
handle-once pattern and error wrapping guidance.
What NOT to Log
Normative
Never log secrets, credentials, PII, or high-cardinality unbounded data.
Passwords, API keys, tokens, session IDs
Full credit card numbers, SSNs
Request/response bodies that may contain user data
Entire slices or maps of unbounded size
Read
references/LEVELS-AND-CONTEXT.md
when deciding what data is safe to include in log attributes.
Quick Reference
Do
Don't
slog.Info("msg", "key", val)
log.Printf("msg %v", val)
Static message + structured fields
fmt.Sprintf
in message
snake_case
keys
camelCase or inconsistent keys
Log OR return errors
Log AND return the same error
Derive logger from context
Create a new logger per call
Use
slog.Error
with
"err"
attr
slog.Info
for errors
Pre-check
Enabled()
on hot paths
Always allocate log args