Logging best practices
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
Effective logging gives you visibility into application behavior without requiring a debugger or manual reproduction. Poor logging either floods storage with noise or provides no useful information when incidents occur. The goal is structured, leveled, contextual log output that helps you diagnose problems quickly in production.
Use Log Levels Consistently
Log levels categorize message importance. Define team conventions for what each level means and enforce them in code review.
| Level | When to use | Example |
| DEBUG | Internal state useful during development | "Cache miss for key=user:42" |
| INFO | Normal operational events | "Server started on port 8080" |
| WARNING | Unexpected but recoverable situations | "Retry attempt 2/3 for payment API" |
| ERROR | Failed operations that need investigation | "Database connection refused after 3 retries" |
| CRITICAL | System-wide failures requiring immediate action | "Out of disk space on /data volume" |
Use Structured Logging
Plain text logs are hard to search and aggregate. Structured logging (JSON format) makes logs machine-parseable for tools like ELK, Datadog, and CloudWatch.
In Go, use slog (standard library since Go 1.21):
Include Context in Every Log Line
A log message without context is useless during incident response. Always include identifiers that let you trace the event.
Essential context fields:
- Request ID or trace ID — correlate logs across services
- User or tenant ID — identify who is affected
- Operation name — what the code was trying to do
- Duration — how long the operation took
Do Not Log Sensitive Data
Never log passwords, API keys, tokens, credit card numbers, or personal health information. Sanitize or redact before logging.
Set Appropriate Log Retention and Rotation
Logs consume disk space. Configure rotation to prevent disk exhaustion.
For Kubernetes, log to stdout and let the container runtime handle rotation. Use a centralized log aggregator (ELK, Loki, Datadog) for search and retention policies.
Avoid Common Anti-Patterns
Common Pitfalls
- Logging at DEBUG level in production and flooding storage with gigabytes of noise per hour.
- Using
print()instead of a logging framework, losing level filtering, rotation, and structured output. - Logging inside tight loops without rate limiting, which can degrade application performance.
- Including sensitive data (passwords, tokens, PII) in log messages that end up in shared log aggregators.
- Not including correlation IDs, making it impossible to trace a request across multiple services.
Summary
- Use consistent log levels with team-agreed definitions for each level.
- Use structured (JSON) logging for machine-parseable output.
- Include request IDs, user IDs, and operation context in every log line.
- Never log sensitive data — redact tokens, passwords, and PII.
- Configure log rotation and retention to prevent disk exhaustion.
- Use lazy formatting (
logger.info("msg %s", val)) to avoid unnecessary string construction.
Related reading
- Logging data on device and retrieving the log
- Logging HikariCP Spring boot
- Logging request/response messages when using HttpClient
- Logging requests being served by tensorflow serving model
- Logging uncaught exceptions in Python
- Logging with Retrofit 2
- Logging within pytest tests
- Logs in Kubernetes Pod not showing up

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.