Introduction: Why Logging in Go is Not Just fmt.Println
Logging is the eyes and ears of any application running in production. In the world of Go (Golang), the approach to logging has undergone significant evolution: from simple console output via fmt.Println to powerful structured loggers capable of producing JSON-compatible records for centralized log collection systems (ELK, Grafana Loki, Datadog).
In this article, we will break down best practices for logging in Go in practice, not in theory. You will learn how to configure logging levels, use structured logs, avoid "noise," and handle errors correctly. All code examples are ready to use in your projects.
1. The Standard log Library: When It's Enough and When It's Not
Go provides a built-in log package from the standard library. It is simple but has significant limitations.
Basic Example
package main
import ( "log" "os")
func main() { // Configuring output to a file file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) defer file.Close() log.SetOutput(file)
log.Println("Application started") log.Printf("User %s authorized", "admin") log.Fatal("Critical error: failed to connect to DB")}Pros:
- No need to install third-party dependencies.
- Simplicity of use for small scripts or CLI utilities.
Cons:
- No logging levels (only Print, Fatal, Panic).
- Fixed output format (date, time, message).
- No structured fields (key-value).
- Poorly parsed by log collection systems.
Conclusion: for production services, microservice architecture, or any application requiring monitoring, the standard log is not suitable. Switch to structured logging.
2. Structured Logging with zerolog: The Modern Standard
The zerolog library is one of the most popular and fastest loggers in Go. It supports logging levels, JSON output by default, and minimal memory allocation.
Installation
go get -u github.com/rs/zerolog/logConfiguration and Usage Example
package main
import ( "os" "time"
"github.com/rs/zerolog" "github.com/rs/zerolog/log")
func main() { // Configuring time format zerolog.TimeFieldFormat = time.RFC3339
// Console output with colors (for development) log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339})
// For production — JSON to stdout // log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger()
log.Info().Msg("Server started on port 8080") log.Debug().Str("module", "auth").Msg("User login attempt") log.Error().Err(err).Str("user_id", "12345").Msg("Authorization error") log.Fatal().Int("attempt", 3).Msg("Maximum number of attempts exceeded")}Logging Levels in zerolog
- Trace (
log.Trace()) — for deep debugging. - Debug (
log.Debug()) — debug information. - Info (
log.Info()) — normal events. - Warn (
log.Warn()) — warnings (not errors). - Error (
log.Error()) — errors requiring attention. - Fatal (
log.Fatal()) — fatal errors (terminates the program). - Panic (
log.Panic()) — panic with stack trace output.
Best practice: in production, set a minimum