For years my Go logging was log.Printf with a string, a colon, and whatever value seemed relevant at the time. It works right up until you are paging through ten thousand lines at three in the morning trying to find the one request that went wrong, and every line is shaped differently.
I switched the service to zap this week and the difference is not subtle. Logs come out as JSON with proper fields, which means I can pipe them into anything and ask real questions instead of inventing fragile grep patterns.
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("request completed",
zap.String("path", req.URL.Path),
zap.Int("status", code),
zap.Duration("took", time.Since(start)),
)
The thing I underrated is the discipline it forces. Once a field is a field, you stop smuggling context into free-form strings and start naming it. request_id, user_id, took: suddenly every line is queryable on the same keys, and a slow-request hunt becomes a filter rather than an archaeology dig.
The cost is that the console output is less pretty in development, so I keep the dev config on the console encoder and let production emit JSON. Small ceremony, large payoff. I should have done this years ago instead of admiring everyone else's dashboards.