Logging
Nội dung bài · 5 mục
- 1.Khái niệm
- 2.Ví dụ
- 3.Thử ngay
- 4.Lỗi hay gặp
- 5.Tóm tắt
Khách báo không đặt được hàng lúc 2 giờ sáng. Không ai ngồi xem server lúc đó. Thứ duy nhất còn lại để biết chuyện gì đã xảy ra là log. Bài này hướng dẫn ghi log đúng cách trong Spring Boot.
Khái niệm
📝 Logging: ghi lại những gì ứng dụng đang làm, để tra cứu khi có sự cố.
📶 Log level: mức độ quan trọng của một dòng log, từ thấp tới cao là TRACE, DEBUG, INFO, WARN, ERROR.
| Level | Khi nào dùng |
|---|---|
| DEBUG | chi tiết để dò lỗi lúc dev |
| INFO | sự kiện bình thường: tạo đơn, đăng nhập |
| WARN | bất thường nhưng vẫn chạy được |
| ERROR | lỗi làm hỏng một thao tác |
Ví dụ
// ProductsController.java
package com.shop.api;
import org.slf4j.*;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/products")
class ProductsController {
private static final Logger log =
LoggerFactory.getLogger(
ProductsController.class);
@GetMapping("/{id}")
public ResponseEntity<Integer> getById(
@PathVariable int id) {
log.debug("Bắt đầu tìm sản phẩm {}", id);
if (id > 100) {
log.warn("Không tìm thấy sản phẩm {}", id);
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(id);
}
}LoggervàLoggerFactoryđến từ SLF4J, thư viện log có sẵn trong Spring Boot. Mỗi class tạo một loggerstatic finalcho riêng mình.- Tên class truyền vào
getLoggerđược ghi kèm mỗi dòng log để biết log đến từ đâu. {}là placeholder: giá trị củaidđược điền vào đúng vị trí đó.application.propertiesquy định level thấp nhất được ghi. Mặc định làINFO, nên dònglog.debugkhông hiện. Thêmlogging.level.com.shop.api=DEBUGthì nó hiện.
Thử ngay
Giữ các file cũ, thay ProductsController.java theo phần Ví dụ, rồi chạy lại
server và gọi:
curl -i http://localhost:5000/api/products/999Đoán trước khi chạy: getById gọi cả log.debug và log.warn. Cửa sổ
đang chạy server hiện mấy dòng log mới của ProductsController?
Xem kết quả
WARN ... com.shop.api.ProductsController
: Không tìm thấy sản phẩm 999Chỉ một dòng WARN của getById. Phần đầu dòng (thời gian, số tiến trình,
tên thread) đã được lược bớt. log.debug thấp hơn mức INFO mặc định nên
bị bỏ qua. Lần gọi đầu có kèm ba dòng INFO báo khởi tạo
DispatcherServlet, bỏ qua được.
Lỗi hay gặp
Ghép chuỗi vào log. Dòng log vẫn đọc được, nhưng chuỗi luôn bị ghép, kể
cả khi level đó đang tắt. Với {}, SLF4J chỉ điền giá trị khi dòng log thật
sự được ghi.
// SAI — chuỗi được ghép dù DEBUG đang tắt
log.debug("Bắt đầu tìm sản phẩm " + id);Cách đúng là truyền giá trị qua placeholder như ở phần Ví dụ:
log.debug("Bắt đầu tìm sản phẩm {}", id).
Ghi dữ liệu nhạy cảm. Mật khẩu, số thẻ, token không bao giờ được ghi vào log, vì nhiều người và nhiều hệ thống đọc được log.
Tóm tắt
- Tạo logger bằng
LoggerFactory.getLogger(TênClass.class)để ghi log. - Chọn level theo mức độ: INFO cho sự kiện thường, WARN cho bất thường, ERROR cho lỗi.
- Dùng placeholder
{}thay vì ghép chuỗi. - Level thấp nhất được ghi đặt bằng
logging.level.*trongapplication.properties.
Tự kiểm tra
0/3 câuThanh toán thất bại vì cổng thanh toán trả lỗi, đơn hàng không tạo được. Nên ghi log level nào?
Mức log thấp nhất đặt là WARN. Dòng nào sẽ được ghi?
Cách viết log nào tốt nhất?