ZeroLog is a high-performance, zero-allocation structured logging library for Go. It outputs logs in JSON format, making them easy to parse and analyze.
Designed for minimal overhead, it’s an excellent choice for modern applications where performance and scalability are key.
In this blog, we'll talk about ZeroLog, how to handle errors, some best practices, and more.
Getting Started with ZeroLog
To integrate ZeroLog into your project, first install the package:
ZeroLog's contextual logging allows you to attach additional fields to logs, making it easier to track important details for each log entry. For instance:
In this example, we add contextual data like component and status_code to the log, helping to give more insights about the event.
Additionally, ZeroLog integrates with the ctx package to propagate contextual data across requests. This is particularly useful for web applications built with frameworks like Echo or Gin, where you can pass request-specific data through the lifecycle of an HTTP request.
Using Hooks for Custom Logic
ZeroLog allows you to extend its functionality with hooks, which are functions triggered during log events. You can use hooks to dynamically modify the log entries, such as adding extra fields, enriching data, or redirecting logs.
Here’s an example of using a hook:
log.Logger = log.Logger.Hook(zerolog.HookFunc(func(e *zerolog.Event, level zerolog.Level, msg string) {
if level == zerolog.WarnLevel {
e.Str("alert", "true")
}
}))
log.Warn().Msg("This is a warning message")
In this example, we add an "alert" field to the log when a warning is logged. This enables dynamic behavior and customization of the log output.
Handling Errors and Warnings
Error and warning logs are vital for understanding application issues. ZeroLog excels at handling errors with structured logs. Here’s how you can log errors and warnings effectively:
err := fmt.Errorf("dependency failed")
log.Error().Err(err).Msg("Failed to load dependency")
This generates a structured log with the error message, level, and stack trace (if enabled). This makes it easier to pinpoint where the issue occurred and what went wrong in the application.
Formatting Logs
ZeroLog’s integration with fmt.Sprintf allows for familiar string formatting, making it easy for Go developers to format log messages:
log.Info().Msgf("Welcome, %s!", "foo")
This example uses Msgf, which is similar to fmt.Printf, allowing you to include formatted values directly in the log message.
To customize the time format in your logs, ZeroLog provides an easy way to set the time field format globally:
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Info().Msg("Custom time format")
In this case, the TimeFormatUnix format outputs timestamps as Unix timestamps, but you can adjust it to fit your needs.
Structured Logging with Headers and JSON
ZeroLog excels in structured logging, where logs are organized into fields. For instance, headers can be added to logs for better organization and context. Here’s how you can define a custom struct to represent headers and include it in your log entry:
type Header struct {
Key string `json:"key"`
Value string `json:"value"`
}
log.Info().Interface("headers", Header{Key: "Authorization", Value: "Bearer token"}).Msg("Request headers")
This makes it easy to log complex, structured data (like HTTP headers) directly in the log entries. You can easily extend this to log any structured data that’s important to your use case.
Zero Allocation and Performance
One of ZeroLog’s standout features is zero allocation, which ensures minimal impact on your application’s performance.
Unlike many logging libraries, ZeroLog doesn’t allocate memory for each log entry, reducing garbage collection overhead and making it an excellent choice for high-performance applications.
This feature is particularly useful in systems with high log throughput, where maintaining performance is critical.
Integrating ZeroLog with Go Web Frameworks
ZeroLog integrates easily with web frameworks like Echo and Gin for logging HTTP requests. Here's an example of how to add HTTP request logging in an Echo application:
In this example, every incoming HTTP request is logged with its HTTP method and URL path. This makes it easy to track and debug HTTP traffic in your web application.
Advanced ZeroLog Usage
Custom Logger Instances
In some cases, you might want to create different logger instances for different modules or contexts. This allows for more focused and contextual logging. Here's how you can do it:
By using With() to add a custom field (e.g., "module": "auth"), you create a logger that clearly identifies the context of the log. This is especially useful in larger applications where you need to separate logs by components.
Global Logger Configuration
For consistent logging across your entire application, you can configure a global logger that applies to all parts of your program:
This ensures that all log messages, regardless of where they come from, adhere to the same log level and include timestamps for better traceability.
Logging Events
For event-driven applications, ZeroLog provides an easy-to-use zerolog.event API that allows you to log events and capture specific actions. Here's how you can log an event like a user login:
In this example, a structured event is logged, which includes the action (user_login) as part of the log entry.
Testing ZeroLog
Testing logs is straightforward with ZeroLog. You can test log output by writing logs to an in-memory buffer and validating the content. Here's an example:
This approach is useful when you need to log-structured data, like a list of IDs, and want to keep the log message easy to process and analyze.
Error Handling and Fatal Logs
Handling errors and logging critical issues is a fundamental part of logging. ZeroLog makes it easy to log errors and fatal messages:
log.Fatal().Msg("Critical error occurred")
This logs a fatal error and immediately terminates the application. It’s ideal for situations where the application cannot continue running due to a critical issue.
For non-fatal errors, you can log errors with additional context:
log.Error().Err(fmt.Errorf("file not found")).Msg("Error logging")
This captures the error (in this case, a file not found) and includes it in the log output.
Dependencies and Hooks
ZeroLog makes it easy to enrich your logs with additional context, such as dependency details. For example, you can log information about external dependencies like Redis:
This ensures that all error-level logs are enriched with a critical field, making it clear when an error is more serious.
Working with ConsoleWriter
While ZeroLog defaults to JSON for structured logs, you can also use ConsoleWriter it for human-readable output. This is useful for local development or debugging when you prefer logs in a more readable format.
This example formats the log message with a human-readable timestamp and outputs it to the console. It’s an easy way to quickly view logs during development without dealing with raw JSON logs.
Logging Errors with ZeroLog
ZeroLog makes logging errors both efficient and straightforward, thanks to its structured logging capabilities. Here’s how you can leverage ZeroLog to handle error logs effectively in your Go applications.
Basic Error Logging
ZeroLog captures errors in a structured format, including error details and context, to help you diagnose issues easily:
package main
import (
"fmt"
"github.com/rs/zerolog/log"
)
func main() {
err := fmt.Errorf("file not found")
log.Error().Err(err).Msg("An error occurred while accessing the log file")
}
This produces a JSON-formatted output:
{
"level": "error",
"error": "file not found",
"message": "An error occurred while accessing the log file",
"time": "2024-11-21T10:00:00Z"
}
Adding Context to Error Logs
Enrich your error logs with additional fields to provide more context, making troubleshooting easier. For example:
This ensures that each module has access to a logger tailored to its needs, providing better flexibility.
Handling Fatal Errors
ZeroLog’s Fatal method logs critical errors and immediately exits the application. Use it with caution to ensure proper cleanup before the program terminates:
The Append method adds an array of values, providing detailed insights into the context of the failure.
Best Practices for Logging Errors
Use Constants for Uniformity: Define constants for log messages and field names to maintain consistency across logs.
Add Line Numbers: Always log the line number for easier debugging.
Redirect Critical Logs: Ensure error logs are written to dedicated files for better monitoring.
Use Context-Rich Logs: Include as much detail as possible using fields like headers or stack traces.
Simulate Printf-Style Logging: When necessary, format your logs dynamically with Msgf.
Use Modular Loggers: Use getLogger to create scoped loggers for specific components.
With Last9, we eliminated the toil. It just works.
— Matt Iselin, Head of SRE, Replit
Conclusion
ZeroLog brings efficiency, simplicity, and structure to logging in Go. Its zero-allocation design, rich API, and seamless integration make it an indispensable tool for modern applications.
ZeroLog's features like hooks, contextual logging, and custom encoders, help you create scalable and maintainable logging systems for your Go projects.
🤝
We’d love to hear your thoughts on SRE, reliability, observability, and monitoring! Join our SRE Discord community and connect with like-minded folks!
FAQs
What is ZeroLog? ZeroLog is a high-performance, zero-allocation structured logging library for Go. It helps developers log messages in a structured, JSON format with minimal overhead, making it an ideal choice for applications where performance is critical.
What format is the ZeroLog log? ZeroLog outputs logs in JSON format by default. This structured format makes it easy to parse and analyze log data in modern logging and monitoring systems.
What is the default level of ZeroLog? The default log level in ZeroLog is debuglevel. You can adjust the log level globally or per logger using zerolog.SetGlobalLevel() or specific logger configurations.
How do I write Golang ZeroLogs to Redis? To write ZeroLog logs to Redis, you can use a Redis client to send log messages to a Redis database:
How do I test that ZeroLog logger raised a log event of type error? You can test ZeroLog output by capturing logs in a buffer and asserting the content:
Which Logger is Best in Golang? The best logger depends on your use case:
ZeroLog: Best for performance and structured JSON logging.
Logrus: Ideal for flexibility and compatibility.
Zap: Great for high-speed applications requiring structured logging.
Why is it desirable to switch logging libraries easily? Switching libraries easily ensures flexibility to:
Adapt to changing project requirements.
Leverage performance or feature improvements in other libraries.
Maintain consistent log formats across multiple projects.
A common interface or wrapper makes transitions seamless.
What is the purpose of the blackbody radiation graph to be graphed using the below parameters? This question seems unrelated to ZeroLog, but for blackbody radiation, graphs are plotted using parameters like temperature and wavelength to study emitted radiation and energy distribution.
How to use ZeroLog to filter INFO logs to stdout and ERROR logs to stderr? You can configure separate loggers for stdout and stderr:
infoLogger := zerolog.New(os.Stdout).With().Timestamp().Logger()
errorLogger := zerolog.New(os.Stderr).With().Timestamp().Logger()
infoLogger.Info().Msg("This goes to stdout")
errorLogger.Error().Msg("This goes to stderr")
How do you integrate ZeroLog with an existing Go application for structured logging? Follow these steps to integrate ZeroLog:
Install the ZeroLog library.
Replace your existing logging calls with ZeroLog's API.
Prathamesh works as an evangelist at Last9, runs SRE stories - where SRE and DevOps folks share their stories, and maintains o11y.wiki - a glossary of all terms related to observability.