Golang Logging

Golang Logging

1. Log là gì?

Hiểu một cách đơn giản thì log là những thứ dùng để lưu vết, những thông tin được thông báo, lưu lại trong quá trình hoạt động của một ứng dụng. Ví dụ: user A đang đăng nhập thì ứng dụng sẽ ghi thêm 1 dòng trong file log rằng: user A đã đăng nhập thành công hay thất bại.

Ghi log để làm gì? Dĩ nhiên là để có thể xem lại các thông tin hoạt động của ứng dụng trong quá khứ nhằm nhiều mục đích như debug, check health, xem info, xem lỗi, warning,…

Nhưng ghi log là ghi gì? Cách đơn giản nhất là ghi lại toàn bộ thông tin nào bạn nghĩ là cần thiết để phục vụ cho các mục đích bên trên.

Một dòng log tiêu chuẩn thường có dạng như sau:

  • Timestamp: thời gian xảy ra sự kiện.
  • Log level: mức độ của log. Thường có ít nhất 3 loại: debug - log chỉ hiện thông để gỡ lỗi, info - log chỉ để log ra thông tin hoạt động, error - log đã xảy ra lỗi.
  • Contextual data: tất cả dữ liệu cần thiết để ta biết sự kiện xảy ra ở đâu và có thể dùng chúng để tái diễn sự kiện

Độ chi tiết và chính xác của log tỉ lệ nghịch với thời gian bắt bug của bạn. Làm app bất kỳ mà ko có log cũng giống như lúc cháy nhà mà chả biết cháy từ đâu ra.

2. Logging trong Golang

Ở go có một thư viện chuẩn tương tự như các ngôn ngữ khác để log một dòng text ra console. Tuy nhiên để log ra được các dòng log tiêu chuẩn:

INFO: 2022/02/09 12:01:06 main.go:26: Starting the application...
INFO: 2022/02/09 12:01:06 main.go:27: Something noteworthy happened
WARNING: 2022/02/09 12:01:06 main.go:28: There is something you should know about
ERROR: 2022/02/09 12:01:06 main.go:29: Some error

Ta phải lập trình lại từ đầu rất mất thời gian, chưa kể khi cần custom, thêm chi tiết hoặc tái sử dụng lại package lại phải tinh chỉnh riêng nữa, càng tốn thời gian hơn.

Các ngôn ngữ thông dịch thì bộ xử lý logging thường đi kèm riêng với framework, còn go thì không như vậy, bộ logging đi kèm với các framework của nó phải nói là khá cùi. Bởi vậy ta nên sử dụng thư viện logging phổ biến , package logging phổ biến nhất trong cộng đồng Golang là logrus. Sau đây là cách sử dụng cơ bản của nó.

3. Logrus

Cách cài logrus:

go get "github.com/Sirupsen/logrus"

Logrus hoàn toàn tương thích với package log của golang, do đó ta có thể thay thế log import ở bất cứ đâu với logrus.

Ví dụ đơn giản sau với logrus với cú pháp y hệt package log có sẵn của golang:

package main

import (
  log "github.com/sirupsen/logrus"
)

func main() {
    log.Println("Hello world!")
}

Chạy đoạn code này sẽ ra output như sau

time="2022-02-20T18:44:40+07:00" level=info msg="Hello world!"

JSON với Logrus

Logrus sử dụng rất tốt cho các log có cấu trúc, ví dụ như dạng JSON. Để log ở dạng JSON có ưu điểm đó là các service bên ngoài có thể dễ dàng phân tích cú pháp log của chúng ta, và từ đó lấy thông tin 1 cách dễ dàng.

package main

import (
	log "github.com/sirupsen/logrus"
)

func main() {
	log.SetFormatter(&log.JSONFormatter{})
	log.WithFields(
		log.Fields{
			"foo": "foo",
			"bar": "bar",
		},
	).Info("Something happened")
}

Chạy chương trình trên ta được output:

{"bar":"bar","foo":"foo","level":"info","msg":"Something happened","time":"2022-02-20T18:52:39+07:00"}

Log levels trong logrus

Ở logrus đã có sẵn các hàm cho các log level. Logrus mặc định có bảy level: Trace, Debug, Info, Warn, Error, Fatal, và Panic. Mức độ của log ta có thể hiểu như sau:

log.Trace("Something very low level.")
log.Debug("Useful debugging information.")
log.Info("Something noteworthy happened!")
log.Warn("You should probably take a look at this.")
log.Error("Something failed but I'm not quitting.")
// Calls os.Exit(1) after logging
log.Fatal("Bye.")
// Calls panic() after logging
log.Panic("I'm bailing.")

Bằng cách setting log level cho một logger, ta có thể nhận diện loại log cần thiết, phân chia chúng ra từng file riêng biệt, cả phân chia theo môi trường nữa, rất thuận tiện cho việc đọc và định vị. Mặc đinh thì logrus sẽ log từ Info trở lên (Warn, Error, Fatal, hoặc Panic).

package main

import (
    log "github.com/sirupsen/logrus"
)

func main() {
    log.SetFormatter(&log.JSONFormatter{})

    log.Debug("Useful debugging information.")
    log.Info("Something noteworthy happened!")
    log.Warn("You should probably take a look at this.")
    log.Error("Something failed but I'm not quitting.")
}

Output:

{"level":"info","msg":"Something noteworthy happened!","time":"2022-02-20T18:55:11+07:00"}
{"level":"warning","msg":"You should probably take a look at this.","time":"2022-02-20T18:55:11+07:00"}
{"level":"error","msg":"Something failed but I'm not quitting.","time":"2022-02-20T18:55:11+07:00"}

Đoạn code trên sẽ không hiển thị log debug, để in ra nó, ta thay đổi level mặc định của logrus:

package main

import (
	log "github.com/sirupsen/logrus"
)

func main() {
	log.SetFormatter(&log.JSONFormatter{})
	log.SetLevel(log.DebugLevel)

	log.Debug("Useful debugging information.")
	log.Info("Something noteworthy happened!")
	log.Warn("You should probably take a look at this.")
	log.Error("Something failed but I'm not quitting.")
}

Chạy lại chương trình:

{"level":"debug","msg":"Useful debugging information.","time":"2022-02-20T18:56:50+07:00"}
{"level":"info","msg":"Something noteworthy happened!","time":"2022-02-20T18:56:50+07:00"}
{"level":"warning","msg":"You should probably take a look at this.","time":"2022-02-20T18:56:50+07:00"}
{"level":"error","msg":"Something failed but I'm not quitting.","time":"2022-02-20T18:56:50+07:00"}

Túm lại

Ở các ngôn ngữ khác, đặc biệt là ngôn ngữ thông dịch, bạn có thể không gặp vấn đề với log, tuy nhiên với golang thì đó là một vấn đề đấy. Vì vậy sử dụng thành thạo một log package thông dụng là cần thiết. Trên đây chỉ là cách sử dụng cơ bản của logrus, ngoài ra còn nhiều chức năng cần thiết như xuất log ra file, phân chia log theo filter ra các file riêng biệt,... sẽ có hướng dẫn kỹ hơn ở Github logrus.

Tham khảo: