Bài 1: Logging cơ bản & Phân loại Log trong package log
Trong quá trình phát triển ứng dụng Go, nhiều người thường có thói quen sử dụng fmt.Println để in thông tin ra terminal. Tuy nhiên, đối với một backend server thực thụ, package log tiêu chuẩn là lựa chọn bắt buộc vì hai đặc tính cốt lõi:
-
Concurrency-safe (An toàn đa luồng): Hệ thống
logtiêu chuẩn của Go được bảo vệ ngầm định bởisync.Mutex. Khi hàng ngàn Goroutine xử lý request cùng ghi log một lúc,fmt.Printlncó thể khiến các chuỗi text bị đan xen, dính chặt vào nhau. Packagelogđảm bảo mỗi dòng thông điệp được ghi xuống một cách tuần tự và trọn vẹn. -
Tự động đính kèm Metadata: Các hàm của package
logtự động gắn thêm tiền tố ngày và giờ, giúp việc truy vết lỗi theo thời gian trở nên khả thi.
Thư viện chuẩn chia các hàm ghi log thành 3 nhóm cấp độ xử lý khác nhau.
1. Nhóm Print (Ghi nhận thông tin)
Gồm các hàm Print, Printf, và Println. Hoạt động tương tự như package fmt, dùng để ghi lại các luồng thông tin bình thường, sự kiện hệ thống hoặc các cảnh báo không làm gián đoạn chương trình.
Go
package main
import (
"log"
)
func main() {
// Ghi log cơ bản
log.Print("Hệ thống đang khởi động...")
// Ghi log có format (rất hữu ích khi cần chèn biến)
port := 8080
log.Printf("Server đang lắng nghe tại port: %d", port)
// Ghi log tự động ngắt dòng
log.Println("Kết nối Redis thành công.")
}
Kết quả ở Terminal:
Plaintext
2026/09/18 21:17:25 Hệ thống đang khởi động...
2026/09/18 21:17:25 Server đang lắng nghe tại port: 8080
2026/09/18 21:17:25 Kết nối Redis thành công.
2. Nhóm Fatal (Lỗi chí mạng - Dừng ngay lập tức)
Gồm Fatal, Fatalf, và Fatalln. Khi một trong các hàm này được gọi, Go sẽ in ra dòng log và sau đó lập tức gọi hàm os.Exit(1). Chương trình sẽ bị "giết" ngay tắp lự. Đặc biệt lưu ý: Tất cả các lệnh defer đang chờ đều sẽ bị bỏ qua và không được thực thi.
Ứng dụng thực tế: Chỉ nên dùng Fatal trong giai đoạn khởi động (bootstrap) của ứng dụng. Nếu server thiếu các tài nguyên sống còn để hoạt động (không đọc được biến môi trường, không kết nối được Database chính), việc dừng chạy ngay lập tức là lựa chọn an toàn nhất.
Go
package main
import (
"log"
"os"
)
func main() {
defer log.Println("Dòng log này sẽ KHÔNG BAO GIỜ được in ra")
// Giả lập đọc file cấu hình bị lỗi
_, err := os.Open("config.json")
if err != nil {
// Chương trình thoát ngay tại đây với exit code 1
log.Fatalf("Không thể khởi động do thiếu file config: %v", err)
}
log.Println("Hệ thống chạy bình thường")
}
3. Nhóm Panic (Lỗi nghiêm trọng - Có thể phục hồi)
Gồm Panic, Panicf, và Panicln. Hàm này sẽ in ra log thông báo, sau đó gọi hàm panic() ngầm định của Go. Trái ngược với Fatal, khi xảy ra Panic:
-
Chương trình không chết ngay lập tức.
-
Các lệnh
defervẫn được thực thi đầy đủ để dọn dẹp tài nguyên (đóng file, đóng kết nối mạng). -
Chương trình sẽ in ra stack trace (dấu vết gọi hàm) để bạn biết chính xác dòng code nào gây lỗi.
-
Bạn có thể dùng hàm
recover()bên trong một lệnhdeferđể "cứu" chương trình khỏi việc bị sập. Ở các HTTP Server, các web framework luôn có một lớp middleware ngầm định dùngrecover()để đảm bảo nếu một request bị lỗi panic (ví dụ truy cập mảng ngoài chỉ số index), chỉ request đó bị trả về lỗi 500, còn server vẫn tiếp tục phục vụ các người dùng khác.
Go
package main
import (
"log"
)
func divide(a, b int) int {
if b == 0 {
// Gọi Panic thay vì Fatal để luồng defer bên ngoài vẫn chạy được
log.Panicln("Lỗi toán học: Không thể chia cho 0!")
}
return a / b
}
func main() {
defer log.Println("Dòng log defer NÀY SẼ ĐƯỢC IN RA để dọn dẹp trước khi chương trình dừng hẳn.")
result := divide(10, 0)
log.Println("Kết quả:", result)
}
Sử dụng đúng ba nhóm log này giúp định hình rõ hành vi của hệ thống khi đối mặt với các tình huống bất thường, tránh việc lạm dụng Fatal làm sập server oan uổng khi đang xử lý các tác vụ nghiệp vụ.
All rights reserved