+2

Sử dụng uber zap thay thế cho logging mặc định của golang

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.

c3ofqa451co54dnk9km0So 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().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/

https://pkg.go.dev/go.uber.org/zap


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí