https://betterstack.com/community/guides/logging/go/zap/
Đầu tiên chúng ta sẽ bắt đầu với code demo đơn giản:
package main import ( "go.uber.org/zap" ) func main() { logger := zap.Must(zap.NewProduction()) defer logger.Sync() logger.Info("Hello from Zap logger!") }
you must create a zap.Logger instance before you can begin to write logs. The NewProduction()
method returns a Logger
configured to log to the standard error in JSON format, and its minimum log level is set to INFO
và output bạn nhận được là:
{"level":"info","ts":1684092708.7246346,"caller":"zap/main.go:12","msg":"Hello from Zap logger!"}
Mình sẽ giải thích chút về output log trên:
{"level":"info"
:level
: This indicates the severity level of the log message. Common levels includedebug
,info
,warn
,error
, etc. In this case,info
suggests that the message is informational.- This is useful for filtering logs. For example, in a production environment, you might only want to see
warn
anderror
level messages.
"ts":1684092708.7246346
:ts
: Stands for timestamp. It represents the time at which the log message was generated.- The number
1684092708.7246346
is a Unix timestamp in seconds with fractional part for millisecond precision. It denotes the specific time in seconds since the Unix epoch (January 1, 1970). This precise timestamp can be crucial for debugging, especially when looking at the sequence of events.
"caller":"zap/main.go:12"
:caller
: This part of the log indicates where in the code the log message was generated."zap/main.go:12"
means the log message was generated in themain.go
file (presumably of thezap
package or project) on line 12. This information is very helpful in larger projects for quickly locating the source of a log message.
"msg":"Hello from Zap logger!"
:msg
: This is the actual log message.- The message
"Hello from Zap logger!"
is a human-readable string that provides information about what the log is about. In this case, it seems to be a simple test or demonstration message.
You can also utilize the NewDevelopment()
preset to create a Logger
that is more optimized for use in development environments. This means logging at the DEBUG
level and using a more human-friendly format:
Bạn sẽ replace 1 chút:
logger := zap.Must(zap.NewDevelopment())
và output nó sẽ như thế này:
2023-05-14T20:42:39.137+0100 INFO zap/main.go:12 Hello from Zap logger!
Setting up a global logger
Chúng ta sẽ luôn muốn sử dụng init fuction để create Zap Log.
package main import ( "go.uber.org/zap" ) func init() { zap.ReplaceGlobals(zap.Must(zap.NewProduction())) } func main() { zap.L().Info("Hello from Zap!") }
This method replaces the global logger accessible through zap.L()
with a functional Logger
instance so that you can use it directly just by importing the zap
package into your file.
Examining Zap’s logging API
Zap provides two primary APIs for logging:
– The first is the low-level Logger
type that provides a structured way to log messages
– The SugaredLogger
type which represents a more laid-back approach to logging
the low-level Logger
type
func main() { logger := zap.Must(zap.NewProduction()) defer logger.Sync() logger.Info("User logged in", zap.String("username", "johndoe"), zap.Int("userid", 123456), zap.String("provider", "google"), ) }
output:
{"level":"info","ts":1684094903.7353888,"caller":"zap/main.go:17","msg":"User logged in","username":"johndoe","userid":123456,"provider":"google"}
The second higher-level API is the SugaredLogger
func main() { logger := zap.Must(zap.NewProduction()) defer logger.Sync() sugar := logger.Sugar() sugar.Info("Hello from Zap logger!") sugar.Infoln( "Hello from Zap logger!", ) sugar.Infof( "Hello from Zap logger! The time is %s", time.Now().Format("03:04 AM"), ) sugar.Infow("User logged in", "username", "johndoe", "userid", 123456, zap.String("provider", "google"), ) }
output:
{"level":"info","ts":1684147807.960761,"caller":"zap/main.go:17","msg":"Hello from Zap logger!"}
{"level":"info","ts":1684147807.960845,"caller":"zap/main.go:18","msg":"Hello from Zap logger!"}
{"level":"info","ts":1684147807.960909,"caller":"zap/main.go:21","msg":"Hello from Zap logger! The time is 11:50 AM"}
{"level":"info","ts":1684148355.2692218,"caller":"zap/main.go:25","msg":"User logged in","username":"johndoe","userid":123456,"provider":"google"}
Structured Logger: sử dụng Zap’s structured logger. API dài dòng hơn nhưng cũng cấp strongly-typed, structured logging (cấu trúc). mỗi log entry (mỗi khi bạn khai báo log) là một message đã được cấu trúc với các trường được defined rõ ràng.
Pros:
- Structured Logging: The fields are explicitly defined, which makes it easier for log parsing and indexing tools to process and query logs.
- Performance: Typically faster than the Sugared logger because it avoids reflection and type conversions.
- Type Safety: Helps catch errors at compile time, as each field requires a specific type.
Cons:
- Verbosity: More verbose to write, as each field needs to be explicitly mentioned.
- Less Flexible: Doesn’t support formatting log messages with printf-style syntax.
Sugared Logger: sử dụng Zap’s Sugared Logger. Cách thì nó sẽ link động hơn, giảm sử dài dòng, đơn giản hơn, và friendly hơn với developer.
Pros:
- Ease of Use: More straightforward syntax, especially for developers familiar with standard Go loggers.
- Flexibility: Supports printf-style formatting, which can be more intuitive for constructing messages.
- Compatibility: Makes it easier to migrate existing code that uses standard Go logging to Zap.
Cons:
- Performance: Slightly slower than the structured logger due to the use of reflection and runtime type assertions.
- Less Structured: Printf-style and infow-style logs are less structured, which could be a downside for parsing and indexing.
Build logger
Mục tiêu của mình là sẽ build logger và chúng ta có thể dễ dàng ghi log ở bất cứ đâu trong project của chúng ta.
/* Refer to: nimtechnology.com Maintainers: Nim */ package logger import ( "compress/gzip" "fmt" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" "io" "os" "strings" "sync" "time" ) // Global logger instance var Log *zap.Logger var SugaredLog *zap.SugaredLogger // Mutex for file lock to prevent concurrent file access var fileLock sync.Mutex // InitLogger initializes the logger with file rotation and timestamped logs. func InitLogger() { // Get the log level from the environment variable logLevelStr := os.Getenv("LOG_LEVEL") if logLevelStr == "" { logLevelStr = "INFO" // Default to INFO if LOG_LEVEL is not set } // Convert the log level string to a zap log level var level zapcore.Level switch strings.ToUpper(logLevelStr) { case "DEBUG": level = zap.DebugLevel case "INFO": level = zap.InfoLevel case "WARN": level = zap.WarnLevel case "ERROR": level = zap.ErrorLevel default: level = zap.InfoLevel // Default to INFO if the level is not recognized } // Create a file writer with log rotation file := zapcore.AddSync(&lumberjack.Logger{ Filename: "logs/app.log", MaxSize: 5, // Megabytes MaxBackups: 10, // Max number of backups MaxAge: 14, // Days Compress: true, // Enable compression }) // Set log level logLevel := zap.NewAtomicLevelAt(level) // Define encoder configuration encoderCfg := zap.NewProductionEncoderConfig() encoderCfg.TimeKey = "timestamp" encoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder encoderCfg.CallerKey = "caller" // Create console and file encoders consoleEncoder := zapcore.NewConsoleEncoder(encoderCfg) fileEncoder := zapcore.NewJSONEncoder(encoderCfg) // Core setup for logging to both stdout and file core := zapcore.NewTee( zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), logLevel), zapcore.NewCore(fileEncoder, file, logLevel), ) // Create a logger instance with AddCallerSkip(1) to skip the logger package stack frame Log = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1)) SugaredLog = Log.Sugar() // Create a sugared logger for easier logging } // TimestampedLumberjackWriter wraps lumberjack.Logger and adds timestamp-based file rotation. type TimestampedLumberjackWriter struct { *lumberjack.Logger baseFilename string } // NewTimestampedLumberjackWriter creates a new TimestampedLumberjackWriter. func NewTimestampedLumberjackWriter(filename string, maxSize, maxBackups, maxAge int, compress bool) *TimestampedLumberjackWriter { return &TimestampedLumberjackWriter{ Logger: &lumberjack.Logger{ Filename: filename, MaxSize: maxSize, MaxBackups: maxBackups, MaxAge: maxAge, Compress: compress, }, baseFilename: filename, } } // Write writes to the original log file and rotates it with a timestamp when it's closed. func (t *TimestampedLumberjackWriter) Write(p []byte) (n int, err error) { fileLock.Lock() // Lock the file access defer fileLock.Unlock() n, err = t.Logger.Write(p) if err != nil { return n, err } // Create timestamped rotated filename timestamp := time.Now().Format("2006-01-02_15-04-05") rotatedFilename := fmt.Sprintf("%s.%s.gz", t.baseFilename, timestamp) // Attempt to compress and rename the log file with retry mechanism err = t.compressAndRename(rotatedFilename) return n, err } // compressAndRename handles the renaming and compressing of the log file. func (t *TimestampedLumberjackWriter) compressAndRename(newFilename string) error { var err error for i := 0; i < 3; i++ { err = t.tryCompressAndRename(newFilename) if err == nil { return nil } time.Sleep(time.Second * 1) // Retry after a second } return err } // tryCompressAndRename performs the actual compression and renaming. func (t *TimestampedLumberjackWriter) tryCompressAndRename(newFilename string) error { // Open the current log file currentFile, err := os.Open(t.baseFilename) if err != nil { return err } defer currentFile.Close() // Create a temporary file tmpFile, err := os.Create(newFilename + ".tmp") if err != nil { return err } defer tmpFile.Close() // Rename the current log file to temporary file err = os.Rename(t.baseFilename, tmpFile.Name()) if err != nil { return err } // Compress the temporary file err = compressFile(tmpFile.Name()) if err != nil { return err } // Finally, rename the temporary file to the desired final filename err = os.Rename(tmpFile.Name(), newFilename) return err } // compressFile compresses the log file to gzip format. func compressFile(filename string) error { // Open the renamed log file file, err := os.Open(filename) if err != nil { return err } defer file.Close() // Create a gzip file with a .gz extension compressedFile, err := os.Create(filename + ".gz") if err != nil { return err } defer compressedFile.Close() // Compress the log file gzWriter := gzip.NewWriter(compressedFile) defer gzWriter.Close() // Copy the contents of the original file into the gzip file _, err = io.Copy(gzWriter, file) return err } // Wrapper functions for Infof, Debugf, Warnf, and Errorf func Infof(format string, args ...interface{}) { SugaredLog.Infof(format, args...) } func Debugf(format string, args ...interface{}) { SugaredLog.Debugf(format, args...) } func Warnf(format string, args ...interface{}) { SugaredLog.Warnf(format, args...) } func Errorf(format string, args ...interface{}) { SugaredLog.Errorf(format, args...) } // Function to log with structured logging func Info(msg string, fields ...zap.Field) { Log.Info(msg, fields...) } func Debug(msg string, fields ...zap.Field) { Log.Debug(msg, fields...) } func Warn(msg string, fields ...zap.Field) { Log.Warn(msg, fields...) } func Error(msg string, fields ...zap.Field) { Log.Error(msg, fields...) } // ZapWriter implements io.Writer, forwarding logs to a Zap logger. type ZapWriter struct { logger *zap.Logger level zapcore.Level } // NewZapWriter creates a new ZapWriter instance. func NewZapWriter(logger *zap.Logger, level zapcore.Level) *ZapWriter { return &ZapWriter{logger: logger, level: level} } // Write satisfies io.Writer interface, forwarding output to the Zap logger. func (z *ZapWriter) Write(p []byte) (n int, err error) { msg := strings.TrimSpace(string(p)) switch z.level { case zapcore.DebugLevel: z.logger.Debug(msg) case zapcore.InfoLevel: z.logger.Info(msg) case zapcore.WarnLevel: z.logger.Warn(msg) case zapcore.ErrorLevel: z.logger.Error(msg) default: z.logger.Info(msg) } return len(p), nil }
Tiếp đến ở hàm main thì bạn sẽ cần gọi InitLogger lên 1 lần
package main import ( "learn_zap_log/logger" "os" ) func init() { // Set log level to DEBUG os.Setenv("LOG_LEVEL", "DEBUG") logger.InitLogger() } func main() { Hoge() }
mình test việc gọi log trong file hope.go
package main import ( "go.uber.org/zap" "learn_zap_log/logger" ) func Hoge() { url := "http://example.com" logger.Info("This is a function in some other package "+url, zap.String("key", "value"), zap.Int("count", 42)) logger.Debug("This is a debug message") logger.Warn("This is a warning message") logger.Error("This is an error message") }
Để sử dụng đc log debug thì bạn phải thêm biến môi trường là LOG_LEVEL = DEBUG
Nó tạo ra folder là logs và lưu file ở đó.
