Uber zap logger with go: structured logs
In this blog post, we will explore a custom logger implementation in Go using the zap library (https://github.com/uber-go/zap). The zap library is a high-performance logging library for Go that provides both structured and leveled logging. Our custom logger will demonstrate how to create a logger with different log levels, custom formatting, and additional functionality such as custom methods and log fields.
Structured Logging in general
In the modern world of software development, logging plays a crucial role in monitoring, debugging, and analyzing applications. As systems grow more complex and distributed, it becomes increasingly important to have a robust logging mechanism that can effectively manage and process log data. Traditional logging, which primarily focuses on human-readable plain text log messages, may fall short in addressing these challenges. Enter structured logging, a powerful and efficient alternative that has gained popularity in recent years.
Structured logging is an approach to log management that emphasizes the use of structured, machine-readable data rather than plain text messages. Instead of relying on the format of the log message itself, structured logging captures log events as key-value pairs, allowing for easier parsing, filtering, and analysis of log data. This method offers numerous advantages over traditional logging, including improved query capabilities, better integration with log analysis tools, and greater flexibility in log data processing and storage.
Key benefits of structured logging include:
- Easier log analysis: Since structured logs are composed of key-value pairs, it’s easier to search, filter, and analyze log data. This streamlined process enables developers to quickly identify patterns and issues within the application.
- Enhanced scalability: As applications grow in size and complexity, structured logging allows for more efficient log processing and storage, making it an ideal choice for distributed and large-scale systems.
- Improved integration with log analysis tools: Many modern log analysis tools and platforms, such as Elasticsearch, Logstash, and Kibana (ELK stack), are designed to work with structured log data, enabling seamless integration and more powerful log analysis capabilities.
- Customizable log formats: Structured logging provides flexibility in choosing the log format that best suits your needs. Common formats include JSON, XML, and CSV, among others.
- Consistency across systems: Using structured logs across multiple applications and services promotes consistency in log data, simplifying the process of aggregating and analyzing logs from different components of your system.
Code Overview
Imports and global variable
The code starts by importing the necessary packages, including go.uber.org/zap and go.uber.org/zap/zapcore. It also defines a global variable, DebugLevel, which will be used to set the logging level.
import (
"fmt"
"strings"
"go.uber.org/zap/zapcore"
"go.uber.org/zap"
)
var DebugLevel stringThe main function
In the main function, we set the value of the DebugLevel variable and create a test log message. We then create a new logger instance with the name "test" and log the messages using the Infof and Errorf methods.
func main() {
DebugLevel = "debug"
ErrorMessage := "test error"
log := NewLogger("test")
log.Infof("DebugLevel: %s", DebugLevel)
log.Errorf("Error: %s", ErrorMessage)
}The NewLogger function
This function creates a new logger instance with the specified name. It first sets the logging level based on the DebugLevel variable and then creates a zap.Config object with various configurations such as encoding, output paths, and encoder configurations.
func NewLogger(name string) Logger {
// ...
}The Logger struct and methods
The Logger struct is a wrapper around zap.SugaredLogger. It defines custom methods such as Printf, Noticef, Tracef, Fatalf, NewWith, and Write. These methods provide additional functionality and help in formatting log messages according to our requirements.
type Logger struct {
*zap.SugaredLogger
}And the whole code snippet
package main
import (
"fmt"
"strings"
"go.uber.org/zap/zapcore"
"go.uber.org/zap"
)
var DebugLevel string
func main() {
DebugLevel = "debug"
ErrorMessage := "test error"
log := NewLogger("test")
log.Infof("DebugLevel: %s", DebugLevel)
log.Errorf("Error: %s", ErrorMessage)
}
// NewLogger Create a new logger instance
func NewLogger(name string) Logger {
var l zapcore.Level
switch strings.ToLower(DebugLevel) {
case "debug":
l = zap.DebugLevel
case "info":
l = zap.InfoLevel
case "warn":
l = zap.WarnLevel
default:
l = zap.DebugLevel
}
zapConfig := zap.Config{
Level: zap.NewAtomicLevelAt(l),
Development: false,
DisableCaller: true,
DisableStacktrace: true,
Sampling: nil,
Encoding: "json",
EncoderConfig: zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: "name",
CallerKey: "caller",
MessageKey: "msg",
StacktraceKey: "stack",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: zapcore.FullCallerEncoder,
},
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
InitialFields: nil,
}
zapLogger, _ := zapConfig.Build()
log := Logger{
SugaredLogger: zapLogger.Sugar().With("source", name),
}
return log
}
// Logger is a wrapper for zap.SugaredLogger
type Logger struct {
*zap.SugaredLogger
}
// Printf is a wrapper for Infof
func (l *Logger) Printf(format string, v ...interface{}) {
l.Infof(format, v)
}
// Noticef is a wrapper for Info
func (l *Logger) Noticef(format string, v ...interface{}) {
str := fmt.Sprintf(format, v...)
l.Info(str)
}
// Tracef is a wrapper for Debug
func (l *Logger) Tracef(format string, v ...interface{}) {
str := fmt.Sprintf(format, v...)
l.Debug(str)
}
// Fatalf is a wrapper for DPanicf
func (l *Logger) Fatalf(format string, v ...interface{}) {
str := fmt.Sprintf(format, v...)
l.DPanicf(str)
}
// NewWith creates a new logger with the given key-value pairs
func (l *Logger) NewWith(p1, p2 string) *Logger {
return &Logger{SugaredLogger: l.SugaredLogger.With(p1, p2)}
}
// Write is a wrapper for Info
func (l *Logger) Write(p []byte) (n int, err error) {
l.Info(string(p))
return len(p), nil
}Conclusion
This custom logger implementation in Go using the zap library demonstrates how to create a flexible and extensible logger with various log levels, custom formatting, and additional functionality. You can easily adapt this code to your own requirements and improve your application’s logging capabilities.
If you enjoy reading medium articles and are interested in becoming a member, I would be happy to share my referral link with you!