3.1 KiB
3.1 KiB
Production Debugging
Production Debugging Checklist
When paged for a production issue:
Step 1: Capture Immediately (don't restart!)
Capture all profiles before restarting the process. The curl commands in pprof.md can be used targeting your production server address. At minimum, capture: goroutine dump (?debug=2), heap, CPU (30s), and mutex profiles.
Step 2: System Metrics
ps aux | grep myapp
lsof -p PID | wc -l # file descriptors
ss -s # socket summary
netstat -an | grep ESTABLISHED | wc -l
Step 3: Analyze Locally
Download the captured .prof files and analyze with go tool pprof (see pprof.md).
Logging & Observability
Strategic Log Placement
Place logs at component boundaries, not sprinkled randomly. The goal is to see data entering and exiting each layer, so you can identify exactly which component corrupts or drops it:
// 1. Function entry/exit with key parameters
func ProcessOrder(ctx context.Context, orderID string) error {
log.Printf("ProcessOrder: start orderID=%s", orderID)
defer log.Printf("ProcessOrder: done orderID=%s", orderID)
// ...
}
// 2. Before and after external calls
log.Printf("calling payment API for order %s", orderID)
resp, err := paymentClient.Charge(ctx, req)
if err != nil {
log.Printf("payment API: err=%v", err)
} else {
log.Printf("payment API: status=%d", resp.StatusCode)
}
// 3. At decision points
if user.IsAdmin {
log.Printf("admin path for user %s", user.ID)
}
Structured Logging (Go 1.21+)
import "log/slog"
slog.Info("processing request",
"method", r.Method,
"path", r.URL.Path,
"user_id", userID,
)
slog.Error("database query failed",
"err", err,
"query", query,
"duration_ms", elapsed.Milliseconds(),
)
Request ID Tracing
type ctxKey string
func WithRequestID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, ctxKey("request_id"), id)
}
func RequestID(ctx context.Context) string {
id, _ := ctx.Value(ctxKey("request_id")).(string)
return id
}
Network & HTTP Debugging
HTTP Client Issues
// 1. HTTP clients MUST set timeouts — default http.Client has NO timeout
client := &http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
DialContext: (&net.Dialer{Timeout: 5 * time.Second}).DialContext,
TLSHandshakeTimeout: 5 * time.Second,
IdleConnTimeout: 90 * time.Second,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
},
}
// 2. Response body MUST be closed
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// 3. Read body on error status (for error messages from server)
if resp.StatusCode >= 400 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("API error %d: %s", resp.StatusCode, body)
}
// 4. Dump full request/response for debugging
import "net/http/httputil"
dump, _ := httputil.DumpRequestOut(req, true)
log.Printf("request:\n%s", dump)
dump, _ = httputil.DumpResponse(resp, true)
log.Printf("response:\n%s", dump)