How to Collect and Centralize Logs in Golang Applications
This article summarizes my takeaways after reading How to collect, standardize, and centralize Golang logs | Datadog.
When handling logging, there are typically a few approaches to consider:
- Pass the logger as a parameter whenever logging is needed.
- Uniformly encapsulate it within the context, retrieving it when needed.
- Wrap the logger in a package and expose a variable for other packages to use.
Obviously, the first approach quickly runs into issues: your function parameters will start looking rather strange. For example:
func (p *Post) CreatePost(name string, content string, logger *log.Logger) {
///
if err != nil {
logger.Fatal("error!")
}
}
Having to pass a logger into the business logic of CreatePost easily pollutes your function signature, and you might end up passing the logger down through multiple layers. The biggest advantage of this approach, however, is that dependencies are explicit—you can clearly tell from the function signature that a logger is used inside, making it very easy to mock the logger during testing.
We can adopt the second approach: explicitly pass a context into functions and store the logger inside it. When needed, retrieve it with log.Logger.FromContext(ctx). I haven’t thought of any particularly obvious drawbacks to this yet.
const (
ContextKeyLogger = "logger"
)
func main() {
ctx := context.Background()
loggerCtx := context.WithValue(ctx, ContextKeyLogger, log.Logger)
}
The third approach is what I currently use: wrap the logger into a package and expose a variable for other packages to consume. The biggest benefit of this is convenience—it’s plug-and-play. The downside is that testing becomes less straightforward. Because it isn’t parameterized, the logger is hard to mock. (Perhaps there are other good solutions?)
package logging
var Logger *log.Logger
func init() {
Logger = &log.Logger{
// your setting
}
}
Next, let’s look at the recommendations from the article:
- Recommended to use logrus. Logrus is a great logging package that seamlessly supports Go’s standard
log, making migration painless. With its hook mechanism, you can easily integrate with third-party reporting platforms (like Datadog, Sentry, etc.). - Use JSON as the log format: JSON is easy to parse, supported across all major languages, and straightforward for third-party platforms to analyze. It is highly recommended. In logrus, you can directly set the output using
SetFormatter(&logrus.JSONFormatter{}). - Use a unified interface.
- Avoid calling the logger inside goroutines whenever possible. Aside from concurrency concerns, the logger’s internal implementation might also spawn goroutines, making the overall behavior harder to predict and manage.
- Save logs to local files even if you use third-party platforms. This prevents logs from being lost due to network issues and ensures you can always retrieve them.
- If using a cluster-like architecture, you might need something like
syslogto centralize logs onto a dedicated log server. - If using container services like Docker, you may need to capture Docker’s STDOUT.
Miscellaneous
Regarding log.Fatal() and log.Panic(): Fatal quietly calls os.Exit(1) under the hood (alright, I know it’s in the docs, but many people probably haven’t read them), while Panic quietly calls panic. Be especially careful when using them.
Related Posts
- When a Measure Becomes a Target: From the Window Tax to Pull Request Counts I once wrote a script to tally how many PRs I contributed in a quarter, how many reviews I left, and how many tickets I closed, hoping to use numbers to prove my output to my manager. My manager simply remarked that performance isn't just about output. Years later, I finally understood—when a measure becomes a target, it ceases to be a good measure. From the British window tax and the Hanoi rat bounty to evaluating developers by PR counts today, the underlying mechanism is exactly the same.
- Using Cloudflare Images for Image Storage and Transformation Putting an image on a webpage is the simplest task in frontend development. But doing it properly—including resizing, generating multiple formats, and withstanding heavy traffic—is actually an entire end-to-end solution. Eventually, I offloaded everything to Cloudflare Images, keeping only a single original image.
- Stop Using AWS Access Keys Access Keys are an easily overlooked security risk in AWS. By pairing OIDC with IAM Roles, GitHub Actions can securely operate AWS resources without storing any secrets.
- Database Primary Keys: AUTO_INCREMENT, UUID, and UUIDv7 Backend developers often face the choice of primary keys: should you use auto-increment or UUID? What about collisions? How does UUIDv7 compare to created_at + index in performance? Here are the design decisions and benchmark results from testing 20 million rows.