How to collect and centralize logs in golang app
This article summarizes insights drawn from How to collect, standardize, and centralize Golang logs | Datadog.
When using logs, there are several key points to keep in mind:
- Treat logs as parameters to be passed; whenever you need to use a log, pass it as an argument.
- Standardize by encapsulating logs in a context, and retrieve them from the context when needed.
- Wrap the logger in a package and expose a variable for use by other packages.
Clearly, the first approach will soon lead to issues, as your parameters will become quite convoluted. For example:
func (p *Post) CreatePost(name string, content string, logger *log.Logger) {
///
if err != nil {
logger.Fatal("error!")
}
}
A CreatePost function logic that requires passing a logger can easily clutter your parameters and may lead to situations where the logger is passed through multiple layers. The main benefit of this approach is that the dependency is transparent; it’s clear from the function signature that a logger will be used, and testing becomes straightforward as you can easily mock the logger.
Alternatively, we can adopt the second approach, explicitly passing a context in main, with the logger embedded within it. When needed, we can retrieve it using log.Logger.FromContext(ctx). So far, I haven’t identified any significant drawbacks.
const (
ContextKeyLogger = "logger"
)
func main() {
ctx := context.Background()
loggerCtx := context.WithValue(ctx, ContextKeyLogger, log.Logger)
}
The third approach, which I currently use, involves wrapping the logger into a package and exposing a variable for other packages to utilize. The primary advantage here is convenience and plug-and-play functionality. However, the downside is that testing becomes more challenging since the logger is not parameterized and is difficult to mock. (Perhaps there are other good solutions?)
package logging
var Logger *log.Logger
func init() {
Logger = &log.Logger{
// your setting
}
}
Now, let’s explore the recommendations from this article:
- It is recommended to use logrus. Logrus is a highly effective logging package that fully supports the native
log, allowing for a seamless transition. With its hook mechanism, you can easily integrate third-party reporting platforms (like Datadog, Sentry, etc.). - Use JSON as the log file format: JSON is easy to parse and widely supported across major programming languages, making it highly analyzable for third-party platforms. It’s highly recommended; in logrus, you can set the output format directly with
SetFormatter(&logrus.JSONFormatter{}). - Utilize a unified interface.
- Avoid calling the logger within goroutines. Beyond concurrency concerns, the internal implementation of the logger may also invoke goroutines, complicating the overall mechanism.
- Store logs as local files, even if you are using other platforms. This ensures that network issues do not lead to the loss of records, and you can always locate log files.
- In a clustered architecture, you may need to centralize logs on a single logging server using a
syslogapproach. - If using container services like Docker, you may need to listen to Docker’s STDOUT.
Additional Notes
log.Fatal() and log.Panic() have distinct behaviors; Fatal will secretly call os.Exit(1). I know the documentation mentions this, but many people might overlook it, while Panic secretly calls panic, so be particularly cautious when using them.
Related Posts
- When Measurement Becomes the Goal: From Window Tax to PR Counts I once wrote a small tool to count how many PRs I had contributed in a quarter, how many reviews I had left, and how many tickets I had closed, hoping to use the numbers to prove my output to my manager. My manager simply said performance is not judged by output alone. Only years later did I understand—when measurement becomes the goal, it is no longer a good measure. From Britain’s window tax and the Hanoi rat bounty to evaluating developers by PR count today, the mechanism is exactly the same.
- Using Cloudflare Images for Image Storage and Transformation Putting an image on a web page is the easiest thing in frontend. But doing it properly — resizing, generating every format, holding up under traffic — turns into a whole solution of its own. I ended up handing all of it to Cloudflare Images and keeping just one original.
- Stop Using Access Keys Already Access Keys are an easily overlooked security risk on AWS. Use OIDC with IAM Roles so GitHub Actions can securely access AWS resources without any secrets.
- Database Primary Keys: AUTO_INCREMENT, UUID, and UUIDv7 Backend developers often have to decide on a primary key: auto increment or UUID? What about collisions? How much faster is UUIDv7 compared with created_at + index? After benchmarking 20 million rows and looking at the design trade-offs, this post gives you the answer.