Series CloudWatch #3: CloudWatch Logs – Truy vết lỗi Backend phân tán như một thám tử
Ở Bài 2, chúng ta đã thiết lập thành công còi báo động (Alarms). Nửa đêm, chuông điện thoại reo vang, email báo về: "Cảnh báo: CPU của Server Backend đang quá tải 99%!".
Anh em bật dậy mở máy tính. Cảnh báo cho ta biết hệ thống đang "bệnh", nhưng nó không hề nói cho ta biết "nguyên nhân gây bệnh". Là do một vòng lặp vô hạn trong code Golang? Do Database Vitess trả về lỗi quá chậm làm nghẽn kết nối? Hay do có ai đó đang spam request vào hệ thống bán vé tự động (AFC)?
Lúc này, nếu anh em có 10 con server chạy Microservices, việc SSH vào từng con để gõ lệnh tail -f đọc log là một cơn ác mộng. Bằng thời gian anh em mò ra lỗi, khách hàng đã dập máy từ lâu.
Đó là lúc chúng ta cần đến CloudWatch Logs – cuốn nhật ký vạn năng và kính lúp của một thám tử hệ thống.
1. Giải mã cấu trúc của CloudWatch Logs
Để không bị ngợp khi nhìn vào hàng triệu dòng log trên AWS, anh em cần hiểu cách CloudWatch tổ chức việc lưu trữ. Nó được thiết kế y hệt như một chiếc tủ hồ sơ trong văn phòng.
- Log Group (Ngăn tủ): Đây là cấp độ cao nhất, thường dùng để gom nhóm log của một dịch vụ hoặc một ứng dụng.
- Ví dụ: Anh em có một service xử lý giao dịch vé, anh em sẽ tạo một Log Group tên là /ecs/afc-transaction-service.
- Log Stream (Tập hồ sơ): Bên trong mỗi ngăn tủ sẽ có nhiều tập hồ sơ. Mỗi tập hồ sơ thường đại diện cho một nguồn phát sinh log cụ thể (như một con Server EC2, một Docker Container, hoặc một Lambda function).
- Ví dụ: Nếu service AFC chạy trên 3 server khác nhau, anh em sẽ có 3 Log Streams nằm gọn trong Log Group trên.
- Log Event (Tờ giấy ghi chép): Đây chính là từng dòng log thực tế mà code của anh em in ra (ví dụ lệnh fmt.Println trong Golang hay console.log trong Node.js), kèm theo timestamp (thời gian chính xác đến từng mili-giây).
Nhờ cấu trúc này, mọi log của hệ thống phân tán đều được gom về một mối, xếp gọn gàng để chờ anh em đến khám nghiệm.
2. Làm sao để đẩy Log từ Server lên CloudWatch?
Nếu dùng các dịch vụ Serverless của AWS (như Lambda), log sẽ tự động bay lên CloudWatch. Nhưng nếu anh em đang chạy ứng dụng Backend trên máy chủ ảo (EC2) bằng Docker, anh em phải "dạy" cho hệ thống cách gửi log đi.
Có nhiều cách, nhưng với các kỹ sư chuộng sự tinh gọn, cách tuyệt vời nhất là dùng trực tiếp Logging Driver của Docker.
Thay vì để Docker ghi log ra file trên ổ cứng server, anh em chỉ cần sửa lại file docker-compose.yml một chút:
version: '3.8'
services:
backend-afc:
image: myusername/afc-transaction-service:latest
ports:
- "8080:8080"
logging:
driver: awslogs
options:
awslogs-region: ap-southeast-1 # Khu vực AWS (VD: Singapore)
awslogs-group: /docker/afc-service # Tên Log Group
awslogs-create-group: "true" # Tự động tạo nếu chưa có
Chỉ với vài dòng cấu hình, toàn bộ những gì ứng dụng in ra màn hình console (stdout/stderr) sẽ được Docker âm thầm đóng gói và gửi thẳng lên CloudWatch Logs. Ổ cứng server của anh em sẽ luôn sạch sẽ và không bao giờ bị đầy vì rác log!
3. Vũ khí tối thượng: CloudWatch Logs Insights
Đây là phần "ăn tiền" nhất của bài học hôm nay.
Khi hệ thống có cả triệu dòng log, việc cuộn chuột tìm kiếm một chữ "ERROR" là bất khả thi. AWS cung cấp một công cụ tên là Logs Insights. Nó cho phép anh em viết các câu truy vấn (giống hệt viết SQL) để bới lông tìm vết trong đống log khổng lồ chỉ mất vài giây.
Hãy vào giao diện CloudWatch > chọn Logs Insights > Chọn Log Group của dự án.
Giả sử hệ thống báo có "Orphan Transactions" (Giao dịch bị mồ côi do lỗi đồng bộ data offline). Anh em cần tìm xem ID thiết bị nào đang văng lỗi. Hãy gõ câu truy vấn sau:
fields @timestamp, @message
| filter @message like /ERROR/
| filter @message like /Orphan Transaction/
| sort @timestamp desc
| limit 20
Giải thích cú pháp của Thám tử:
- fields @timestamp, @message: Chỉ hiển thị 2 cột là Thời gian và Nội dung log.
- filter @message like /ERROR/: (Bước lọc 1) Chỉ nhặt ra những dòng log có chứa chữ "ERROR".
- filter @message like /Orphan Transaction/: (Bước lọc 2) Trong đống lỗi đó, tìm đúng chữ "Orphan Transaction".
- sort @timestamp desc: Sắp xếp từ mới nhất đến cũ nhất.
- limit 20: Lấy 20 kết quả đầu tiên cho dễ nhìn.
Chỉ mất 2 giây chạy (Run query), bùm! Toàn bộ danh sách thiết bị đang lỗi và nguyên nhân chi tiết sẽ hiện ra rõ mồn một. Từ đó, anh em biết ngay phải sửa đoạn code Golang nào hoặc kiểm tra thiết bị trạm nào.
4. Mẹo nhỏ (Best Practices) khi ghi Log
Để CloudWatch Logs phát huy tối đa sức mạnh, bản thân ứng dụng của anh em phải ghi log sao cho "chuẩn":
- Ghi log dưới dạng JSON: Thay vì in ra một chuỗi text dài thò lò [INFO] 2026-07-03 - User 123 bought ticket, hãy in ra JSON: {"level":"info", "user_id": 123, "action": "bought_ticket"}. Khi lên Logs Insights, anh em có thể dễ dàng truy vấn filter user_id = 123 cực kỳ lợi hại.
- Gắn Trace ID: Khi làm Microservices, một request từ ngoài vào có thể đi qua 4-5 service khác nhau. Hãy tạo một mã Trace ID duy nhất cho request đó và đính kèm vào mọi dòng log. Khi có lỗi, anh em chỉ cần search cái Trace ID đó trên CloudWatch là ra toàn bộ hành trình của request xuyên qua các service.
Tổng kết Bài 3
Hôm nay anh em đã biết cách sử dụng CloudWatch Logs để gom toàn bộ nhật ký hệ thống về một mối, và dùng Logs Insights để truy vấn lỗi nhanh như một hacker. Từ nay, anh em không cần phải vất vả SSH vào từng server để đọc log nữa rồi nhé.
Chúng ta đã có:
- Metrics: Bắt mạch hệ thống.
- Alarms: Báo động khi có biến.
- ogs: Tìm kiếm nguyên nhân.
Nhưng nếu chỉ dừng lại ở việc "Báo động rồi để con người vào sửa", thì chưa gọi là Tự động hóa hoàn toàn. Nếu service sập, sao hệ thống không tự động khởi động lại nó? Nếu có một file mã nguồn độc hại được tải lên, sao hệ thống không tự động cách ly?
Câu trả lời nằm ở khái niệm Event-Driven (Kiến trúc hướng sự kiện). Ở Bài 4: CloudWatch Events (EventBridge) – Tự động hóa phản ứng, tôi sẽ hướng dẫn anh em cách biến CloudWatch thành một "cỗ máy phản xạ" có khả năng tự động giải quyết vấn đề trước cả khi anh em kịp mở laptop lên. Chờ nhé!
All rights reserved