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.
package logger import ( "context" "fmt" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" "log" "os" "runtime/debug" "sync" ) // refer to: https://betterstack.com/community/guides/logging/go/zap/#final-thoughts // https://github.com/betterstack-community/go-logging/blob/zap/logger/logger.go type ctxKey struct{} var once sync.Once var Log *zap.Logger // InitLogger replaces the init function for manual initialization func InitLogger() { once.Do(func() { stdout := zapcore.AddSync(os.Stdout) file := zapcore.AddSync(&lumberjack.Logger{ Filename: "logs/app.log", MaxSize: 5, // megabytes MaxBackups: 10, MaxAge: 14, // days Compress: true, }) level := zap.InfoLevel levelEnv := os.Getenv("LOG_LEVEL") if levelEnv != "" { levelFromEnv, err := zapcore.ParseLevel(levelEnv) if err != nil { log.Println(fmt.Errorf("invalid LOG_LEVEL '%s', defaulting to INFO: %w", levelEnv, err)) } else { level = levelFromEnv } } logLevel := zap.NewAtomicLevelAt(level) encoderCfg := zap.NewProductionEncoderConfig() encoderCfg.TimeKey = "timestamp" encoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder encoderCfg.CallerKey = "caller" consoleEncoder := zapcore.NewConsoleEncoder(encoderCfg) fileEncoder := zapcore.NewJSONEncoder(encoderCfg) var gitRevision string buildInfo, ok := debug.ReadBuildInfo() if ok { for _, v := range buildInfo.Settings { if v.Key == "vcs.revision" { gitRevision = v.Value break } } } core := zapcore.NewTee( zapcore.NewCore(consoleEncoder, stdout, logLevel), zapcore.NewCore(fileEncoder, file, logLevel).With( []zapcore.Field{ zap.String("git_revision", gitRevision), zap.String("go_version", buildInfo.GoVersion), }, ), ) Log = zap.New(core, zap.AddCaller()) }) } func Info(msg string, fields ...zap.Field) { Log.WithOptions(zap.AddCallerSkip(1)).Info(msg, fields...) } func Error(msg string, fields ...zap.Field) { Log.WithOptions(zap.AddCallerSkip(1)).Error(msg, fields...) } func Debug(msg string, fields ...zap.Field) { Log.WithOptions(zap.AddCallerSkip(1)).Debug(msg, fields...) } func Warn(msg string, fields ...zap.Field) { Log.WithOptions(zap.AddCallerSkip(1)).Warn(msg, fields...) } // Add other logging level methods as needed (e.g., Debug, Warn) // FromCtx returns the Logger associated with the ctx. // If no logger is associated, the default logger is returned. func FromCtx(ctx context.Context) *zap.Logger { if l, ok := ctx.Value(ctxKey{}).(*zap.Logger); ok { return l } return Log } // WithCtx returns a copy of ctx with the Logger attached. func WithCtx(ctx context.Context, l *zap.Logger) context.Context { return context.WithValue(ctx, ctxKey{}, l) }
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 ở đó.