Nếu là một lập trình viên golang thì không thể không biết đến package log mặc định. Với ưu điểm hàm dễ sử dụng, log là ưu tiên hàng đầu đối với những lập trình viên mới vào nghề. Tuy nhiên có nhiều nhược điểm không thể không kể đến:
- Log căn bản chỉ có Print, không hỗ trợ log nhiều tầng như Info, Debug.
- Đối với error, chỉ có panic với Fatal giúp thoát chương trình khi gặp lỗi, không có log Error để chỉ hiển thị mà không thoát chương trình.
- Thiếu khả năng Format, Custom logging, ví dụ như Format datetime hiển thị log.
1. Giới thiệu uber-go zap
Uber zap là một thư viện logging của golang có cấu trúc, nhiều tầng (bao gồm Print, Debug, Error,…), tốc độ nhanh.
Uber zap có 2 loại: Logger và Sugared Logger. Sugared Logger tốc độ chậm hơn Logger nhưng hỗ trợ nhiều kiểu log hơn, như hỗ trợ log kiểu printf. Nếu bạn làm một dự án lớn nơi từng microsecond hay object allocate bớt đi đều đáng giá thì logger là một lựa chọn hoàn hảo. Còn nếu muốn nhiều chức năng, không quá quan trọng tốc độ chênh lệch trên thì nên dùng Sugared Logger.
So sánh tốc độ và objects allocate của các package logging trong golang
Ở đây op: số lượng iteration chương trình chạy qua trước khi kết thúc. Ví dụ for i:=0;i<n;i++
thì n đây chính là số iteration. ns: nanosecond, 1 nanosecond = 10-9 second (tức = 1/1 tỷ giây).
Có thể thấy dễ dàng tốc độ xử lý với object allocate của uber zap vượt trội hơn hẳn so với các package logging khác của golang.
2. Cài đặt zap
Đầu tiên ta cài đặt package:
go get -u go.uber.org/zap
2.1 Logger
func main() {
logger,_ := zap.NewProduction() //Khởi tạo zap
defer logger.Sync() //Xả hết buffer ra
_,err := strconv.Atoi("ntf")
logger.Error(
"Error convert string to int..",
zap.String("string", "ntf"),
zap.Error(err)) //Log error khi convert lỗi
n,err := strconv.Atoi("12")
logger.Info("Success..",
zap.String("convertSuccess", "12"),
zap.Int("Result", n)) //Log thông tin convert thành công
}
Kết quả trả ra
Ở đây ta đã có đầy đủ thông tin với cấu trúc rõ ràng: log level error, thời điểm log (ở đây đang bị mã hóa), dòng code bắt đầu gọi đến (caller), message (msg), thông tin hiển thị (“string”:“ntf”,“error”:lỗi trả về).
2.2 Sugared Logger
Giờ thử thay thế đoạn Logger phía trên bằng Sugared Logger
func main() {
logger,_ := zap.NewProduction() //Khởi tạo zap
sugar := logger.Sugar()
defer logger.Sync() //Xả hết buffer ra
_,err := strconv.Atoi("ntf")
sugar.Errorw(
"Error convert string to int..",
"string", "ntf",
"error",err) //Log error khi convert lỗi
n,err := strconv.Atoi("12")
sugar.Infow("Success..",
"convertSuccess", "12",
"Result", n) //Log thông tin convert thành công
}
Kết quả cũng cho ra tương tự
Một chức năng có ở Sugared Logger mà Logger không có là printf style
logger,_ := zap.NewProduction() //Khởi tạo zap
sugar := logger.Sugar()
defer logger.Sync() //Xả hết buffer ra
_,err := strconv.Atoi("ntf")
sugar.Errorf(
"Error convert string: %s to int.. This error is: %s","ntf",err)
_,err = strconv.Atoi("12")
sugar.Infof("Convert success %s to int", "12")
Kết quả
Có thể thấy Logger chỉ hỗ trợ hiển thị log có cấu trúc trong khi Sugared Log hỗ trợ cả log cấu trúc lẫn printf style.
3. Custom logging
Log phía trên có nhược điểm lớn đó là thời điểm log đã bị mã hóa, cách viết liền mạch khó đọc. Ta cần format lại cho dễ đọc, có thể bớt đi field không cần thiết như stacktrace.
Để có thể custom được ta cần import package:
import(
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func ConfigZap() *zap.SugaredLogger{
cfg := zap.Config{
Encoding: "json", //encode kiểu json hoặc console
Level: zap.NewAtomicLevelAt(zap.InfoLevel), //chọn InfoLevel có thể log ở cả 3 level
OutputPaths: []string{"stderr"},
EncoderConfig: zapcore.EncoderConfig{ //Cấu hình logging, sẽ không có stacktracekey
MessageKey: "message",
TimeKey: "time",
LevelKey: "level",
CallerKey: "caller",
EncodeCaller: zapcore.FullCallerEncoder, //Lấy dòng code bắt đầu log
EncodeLevel: CustomLevelEncoder, //Format cách hiển thị level log
EncodeTime: SyslogTimeEncoder, //Format hiển thị thời điểm log
},
}
logger, _ := cfg.Build() //Build ra Logger
return logger.Sugar() //Trả về logger hoặc Sugaredlogger, ở đây ta chọn trả về Logger
}
func SyslogTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006-01-02 15:04:05"))
}
func CustomLevelEncoder(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString("[" + level.CapitalString() + "]")
}
func main() {
sugarLogger := ConfigZap()
sugarLogger.Infow("Get the time now with format","time",time.Now().Format("2006-January-02"))
sugarLogger.Infof("Today is :%s",time.Now().Format("2006-January-02"))
}
Kết quả trả ra
Ta có thể chuyển sang hiển thị kiểu console
4. Viết vào trong file thay thế cho console
Để viết vào trong file ta sử dụng zap.New(zapcore.Core,options…)
zapcore.Core
bao gồm 3 thành phần zapcore.Encoder
, zapcore.WriteSyncer
, zapcore.LogLevel
4.1 Khởi động
Giờ ta bắt đầu thử viết đoạn log cơ bản (chưa format gì) vào file
func SugarLog() *zap.SugaredLogger{
writerSyncer := getLogWriter()
encoder := getEncoder()
core := zapcore.NewCore(encoder, writerSyncer, zapcore.DebugLevel)
logger := zap.New(core)
return logger.Sugar()
}
func getEncoder() zapcore.Encoder {
return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}
func getLogWriter() zapcore.WriteSyncer {
file, _ := os.Create("./test.log")
return zapcore.AddSync(file)
}
func main() {
sugarLogger := SugarLog()
sugarLogger.Infof("Today is :%s",time.Now().Format("2006-January-02"))
}
Kết quả hiển thị: file test.log đã được tạo ra và chương trình viết đoạn log vào trong file
4.2 Custom log trong file
Giữ nguyên đoạn code trên, ta có thể custom log bằng cách thay đổi zapcore.Encoder
func getEncoder() zapcore.Encoder {
return zapcore.NewJSONEncoder(zapcore.EncoderConfig{
MessageKey: "message",
TimeKey: "time",
LevelKey: "level",
CallerKey:"caller",
EncodeLevel: CustomLevelEncoder, //Format cách hiển thị level log
EncodeTime: SyslogTimeEncoder, //Format hiển thị thời điểm log
EncodeCaller: zapcore.ShortCallerEncoder, //Format caller
})
}
func SyslogTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006-01-02 15:04:05"))
}
func CustomLevelEncoder(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString("[" + level.CapitalString() + "]")
}
Kết quả trả ra:
{"level":"[INFO]","time":"2021-07-17 14:43:15","message":"Today is :2021-July-17"}
Khi thay đổi NewJSONEncoder
thành NewConsoleEncoder
kết quả như sau
2021-07-17 14:44:53 [INFO] Today is :2021-July-17
Tuy nhiên có một vấn đề: zapcore.Encoder
mặc dù đã được config CallerKey
không hiển thị “caller”. Để khắc phục, ta cần bổ sung thêm option zap.AddCaller()
vào trong zap.New()
:
func SugarLog() *zap.SugaredLogger{
writerSyncer := getLogWriter()
encoder := getEncoder()
core := zapcore.NewCore(encoder, writerSyncer, zapcore.DebugLevel)
logger := zap.New(core,zap.AddCaller())
return logger.Sugar()
}
Kết quả ConsoleEncoder
2021-07-17 14:52:35 [INFO] golangforteaching/main.go:10 Today is :2021-July-17
Kết quả JSONEncoder
{"level":"[INFO]","time":"2021-07-17 14:54:05","caller":"golangforteaching/main.go:10","message":"Today is :2021-July-17"}
5. Convert qua lại giữa Logger và Sugared Logger
Một điều cuối trước khi kết thúc bài viết là việc chuyển đổi từ logger sang SugaredLogger
hay ngược lại rất đơn giản và không hề tốn công. Hầu hết các ví dụ trong bài này ta đều dùng SugaredLogger
, mà được chuyển đổi từ Logger
sang chỉ với một hàm .Sugar().
Thử với ví dụ trên ta thay đổi SugaredLogger
thành Logger
func main() {
sugarLogger := SugarLog()
sugarLogger.Desugar().Info("Today is :"+time.Now().Format("2006-January-02"))
}
Vậy chỉ 2 hàm .Sugar()
và .Desugar()
là ta đã có thể convert thành công 2 kiểu logging của uber zap rồi.
6. Kết
Mong rằng những ví dụ trên giúp bạn hiểu tổng quan về uber-go zap và đem áp dụng vào trong các dự án để có một chương trình với performance tốt. Tuy khó hơn thư viện log mặc định của golang nhưng chỉ với những bước đầu custom hơi mất chút thời gian, cùng với làm quen nhiều thì mọi thứ sẽ trở thành bản năng.
Nguồn tham khảo:
https://programmersought.com/article/29732540594/
https://sunitc.dev/2019/05/27/adding-uber-go-zap-logger-to-golang-project/
Bình luận
Rất hay cảm ơn tác giả