Log là thành phần không thể thiếu trong mỗi ứng dụng. Có log ta mới biết ứng dụng đã và đang hoạt động thế nào, trạng thái ra làm sao, để từ đó ta có thể debug dễ dàng khi xảy ra lỗi. Trong khi các ngôn ngữ thông dịch cùng các framework đi kèm xử lý logging rất khỏe như PHP-Laravel, Ruby-Rails,... việc xử lý logging trong golang khá rườm rà và mất thời gian để người mới vận dụng nó. Bài viết sau đây sẽ mô tả cách xử lý logging phổ biến trong Golang đi cùng với các thư viện thông dụng

1. Tại sao ta cần phải log nghiêm túc

  • Có thể bạn chưa biết: Log là gì. Hiểu đơn gian Log là bản nhật ký hoạt động của ứng dụng. Hầu hết hoạt động của ứng dụng nên được xuất ra log (tất nhiên chỉ nên những hoạt động cần thiết thôi chứ tất cả thì cũng làm ứng dụng chạy chậm đi và tốn bộ nhớ). Log ứng dụng thường ở dạng text. 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.

Khi ta hiểu được log là gì rồi, tại sao lại phải log có tâm làm gì.
Ta có meme sau:

uc?id=13S8BKAHnZrunlCSSvc_LGn0XqFcWrBUf&export=download

Log kiểu như vậy có cũng như ko :v rất phiêu lưu. Vậy độ 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.

uc?id=131rtHoVy3Osfp3lrB9g4soc5EO1-GIdd&export=download

Thêm nữa, log không chỉ có tác dụng giảm thời gian bắt bug, mà còn giúp ta nhận diện được các vấn đề performance lẫn các lỗ hỗng tiềm năng để cải thiện và vá chúng.

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 mà sinh ra log.
  • 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

Tuy nhiên, không phải cái gì ta cũng được log ra, đặc biệt là các thông tin nhạy cảm như password, dữ liệu riêng tư của khách hàng. Nên lưu ý.

2. Logging trong Go

Ở 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: 2019/12/09 12:01:06 main.go:26: Starting the application...
INFO: 2019/12/09 12:01:06 main.go:27: Something noteworthy happened
WARNING: 2019/12/09 12:01:06 main.go:28: There is something you should know about
ERROR: 2019/12/09 12:01:06 main.go:29: Something went wrong

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 có nhiều star trên github, các bộ đó mới đủ sức mạnh để mà ta custom logging theo ý thích nhưng vẫn đảm tốc độ phát triể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

uc?id=1OmEPtwWdzs2L2Yuo8U6ambCOc7CbIHZb&export=download

Cài logrus khá đơn giản bằng lệnh sau:

go get "github.com/Sirupsen/logrus"

Logrus hoàn toàn tương thích vậy package log chuẩn 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 chuẩn.

package main

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

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

Chạy code này sẽ ra output sau:

INFO[0000] Hello world!

Rất dễ phải không.

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")
}

Output:

{"bar":"bar","foo":"foo","level":"info","msg":"Something happened","time":"2019-12-09T15:55:24+01:00"}

Config và sử dụng cũng rất đơn giản đúng không.

Log level 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":"2019-12-09T16:18:21+01:00"}
{"level":"warning","msg":"You should probably take a look at this.","time":"2019-12-09T16:18:21+01:00"}
{"level":"error","msg":"Something failed but I'm not quitting.","time":"2019-12-09T16:18:21+01:00"}

Để ý thì log debug sẽ không được in ra. Để in ra nó, ta thay đổi level mặc định của logrus:

log.SetLevel(log.DebugLevel)

4. Tổng kết:

Ở 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ì có đấy. Vì vậy sử dụng thành thạo một log package thông dụng là cần thiết để sử dụng nó ở bất kỳ golang framework nào mà bạn dùng (logrus được hỗ trợ bởi hầu hết các framework của go). Phía trên chỉ là cách sử dụng cơ bản thôi, 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. Hy vọng bài viết này sẽ giúp ích cho bạn ở vấn đề nhỏ này khi tiếp cận với golang.

Tham khảo: