# 🎚️ Log level: chọn đúng mức để không ngập log

By Better Wise

> Sign in to view the full content of this lesson.

Bài trước bạn đã dựng log có cấu trúc với các field như `timestamp`, `level`, `service`, `request_id`. Bài này trả lời câu hỏi ngay sau đó: field `level` nên mang giá trị nào, và ai quyết định dòng log nào thực sự được ghi ra.

## Bạn sẽ làm được gì sau bài này

- Phân biệt được `debug`, `info`, `warn`, `error` bằng tiêu chí rõ ràng, không bằng cảm giác.
- Gán đúng mức cho một tình huống bất kỳ trong app của bạn.
- Đặt ngưỡng log phù hợp cho `dev`, `staging`, `production`.
- Nhận ra dấu hiệu ngưỡng đặt sai trước khi đồng đội phàn nàn.

## Bốn mức log và cái ngưỡng lọc chúng

3 giờ sáng, bạn mở log của service thanh toán. Bốn mươi nghìn dòng. Trong đó có đúng một dòng nói database timeout, còn 39.999 dòng là "Calling getUserById". Bạn cuộn mỏi tay. Đó là lúc log level chứng minh giá trị của nó.

Mỗi dòng log có một mức. Logger so sánh mức đó với **ngưỡng** (threshold) đang cấu hình: chỉ dòng có mức _____ ngưỡng mới được ghi ra. Thứ tự từ thấp đến cao là `debug`, `info`, `warn`, `error`.

| Level | Dùng khi nào | Ví dụ | Ở production |
| --- | --- | --- | --- |
| `debug` | Chi tiết chỉ dev cần đọc | payload request, câu query, giá trị biến | Tắt |
| `info` | Sự kiện nghiệp vụ bình thường, đọc lại thấy có nghĩa | đơn hàng tạo xong, user đăng nhập, job chạy xong | Bật |
| `warn` | Bất thường nhưng app vẫn tự xử lý được | gọi API đối tác chậm, retry lần 1, cache miss tăng | Bật |
| `error` | Thao tác thất bại và cần có người xem | mất kết nối DB, unhandled exception, thanh toán fail | Bật, kèm alert |

Mức `debug` chỉ dev cần đọc và thường bị tắt ở production: _____. Mức `warn` dành cho bất thường mà app vẫn xử lý được: _____.

**Bài tập 1.** Chọn level cho bốn dòng sau: (a) user đăng nhập thành công, (b) gọi API vận chuyển timeout, đã retry và thành công ở lần 2, (c) không kết nối được Redis, (d) in ra `req.body` để debug.

**Xem gợi ý đáp án**

(a) `info`, đây là sự kiện nghiệp vụ bình thường. (b) `warn`, có bất thường nhưng hệ thống tự xử lý xong. (c) `error`, app không tự xử lý được và cần người xem. (d) `debug`, chỉ phục vụ việc phát triển.

## Gán mức cho tình huống thật

Quy tắc gọn nhất tôi dùng: **`error` là "cần ai đó làm gì đó"**, không phải "có exception". Rất nhiều app log `error` cho mọi thứ bắt được trong `try/catch`, kể cả khi đã fallback thành công. Kết quả là error rate trên dashboard luôn đỏ, alert kêu suốt, và đến lúc có sự cố thật thì không ai còn tin.

Vài ranh giới dễ nhầm:

- **Lỗi do người dùng nhập sai** (validation, 400, sai mật khẩu): log ở mức _____ (hoặc warn), không phải error. Đây là vận hành bình thường của app.
- **`warn` là "tôi xử lý được nhưng bạn nên biết"**: retry, fallback, chậm bất thường.
- **`info` không được nằm trong vòng lặp**. Log một dòng `info` cho cả lô 500 item, kèm `count`, thay vì 500 dòng.
- **Một dòng log, một level.** Đừng nhét stack trace vào `info` rồi tự nhủ là "cho gọn".

```mermaid
flowchart TD
    Start["Có một tình huống cần log"] --> Q1{"Việc chính có thất bại?"}
    Q1 -->|"Có"| Q2{"Do người dùng nhập sai?"}
    Q2 -->|"Có"| Info["ℹ️<br/>info hoặc warn"]
    Q2 -->|"Không"| Err["🚨<br/>error"]
    Q1 -->|"Không"| Q3{"Chỉ dev cần đọc?"}
    Q3 -->|"Có"| Dbg["🔍<br/>debug"]
    Q3 -->|"Không"| Q4{"Bất thường nhưng app tự xử lý được?"}
    Q4 -->|"Có"| Warn["⚠️<br/>warn"]
    Q4 -->|"Không"| Info
```

**Bài tập 2.** Một job đồng bộ chạy mỗi 5 phút. Với 500 bản ghi, 3 bản ghi lỗi và được đẩy vào hàng đợi thử lại. Bạn sẽ log mấy dòng, ở mức nào?

**Xem gợi ý đáp án**

Hai dòng. Một dòng `info` cho cả lô: `{"msg":"sync_batch_done","total":500,"failed":3,"duration_ms":4200}`. Một dòng `warn` cho phần thử lại: `{"msg":"sync_records_retry_queued","count":3,"reason":"validation"}`. Không log 3 dòng `error` riêng lẻ, vì job vẫn xử lý được và chưa cần ai can thiệp.

## Đặt ngưỡng theo từng môi trường

Cùng một codebase, ba ngưỡng khác nhau. Điều này nên lấy từ biến môi trường, đừng hard-code.

```js
const LOG_LEVEL =
  process.env.LOG_LEVEL ??
  (process.env.NODE_ENV === "production" ? "info" : "debug");
```

- **Dev**: `debug`. Bạn muốn thấy mọi thứ, kể cả payload.
- **Staging**: `debug` hoặc `info`, tùy nhu cầu kiểm thử.
- **Production**: _____. Chỉ khi điều tra sự cố mới bật `debug` tạm thời, có thời hạn, rồi tắt lại.

Dấu hiệu bạn đặt ngưỡng quá thấp ở production: log ra quá nhiều dòng mỗi phút, chi phí lưu trữ tăng, tìm một dòng lỗi mất vài phút, và alert cứ kêu vì `error` xuất hiện liên tục.

**Bài tập 3.** Service production của bạn đang ghi khoảng 20.000 dòng log mỗi phút, chủ yếu là `"Calling getUserById"`. Xử lý thế nào trong 10 phút?

**Xem gợi ý đáp án**

Đổi `LOG_LEVEL` sang `info` (nhiều khả năng các dòng kia đang là `debug` nhưng ngưỡng bị hạ), deploy lại hoặc dùng dynamic log level nếu có. Sau đó kiểm tra lại lượng log mỗi phút và chỉ giữ những dòng `info` thật sự có nghĩa khi đọc lại.

## Mẹo của người trực đêm

- Không chắc nên đặt mức nào? Hỏi: "3 giờ sáng, dòng này có đáng đánh thức tôi không?" Có thì `error`, không thì cùng lắm là `warn`.
- Lỗi lặp lại trong vòng lặp: log lần đầu kèm số lần đã gặp, đừng log từng lần.
- Đừng log `error` cho thứ đã `catch` và xử lý xong. Đó là cách nhanh nhất để alert mất uy tín.
- Nếu `warn` xuất hiện dày đặc, vấn đề nằm ở ngưỡng hoặc ở chỗ bạn gán mức, không phải ở hệ thống.
- Bật `debug` trên production thì đặt lịch tắt. Debug quên tắt là sự cố của tháng sau.

## Chốt lại và đi tiếp

Bốn mức, một ngưỡng, ba môi trường. `error` dành cho việc cần người xem, `warn` cho bất thường đã xử lý được, `info` cho sự kiện nghiệp vụ, `debug` cho chi tiết của dev và tắt ở production. Thứ tự level từ thấp đến cao là debug, info, warn, _____. Đặt ngưỡng qua biến môi trường để mỗi nơi chạy một mức phù hợp.

Biết gán mức rồi, bài tiếp theo mới là phần khó hơn: **log gì và không log gì**, tức là nội dung nào được phép bước vào dòng log đó.

Front: Log level là gì?
Back: Nhãn gắn cho mỗi dòng log, dùng để lọc dòng nào được ghi ra và dòng nào bị bỏ qua.

Front: Thứ tự các mức log từ thấp đến cao?
Back: debug, info, warn, error.

Front: Ngưỡng log (threshold) hoạt động thế nào?
Back: Logger chỉ ghi các dòng có level lớn hơn hoặc bằng ngưỡng đang cấu hình.

Front: Khi nào dùng debug?
Back: Khi cần chi tiết phục vụ phát triển: payload, câu query, giá trị biến.

Front: Khi nào dùng info?
Back: Khi ghi sự kiện nghiệp vụ bình thường mà đọc lại vẫn thấy có nghĩa.

Front: Khi nào dùng warn?
Back: Khi có bất thường nhưng app vẫn tự xử lý được, chưa cần ai can thiệp.

Front: Khi nào dùng error?
Back: Khi thao tác thất bại và cần có người xem, không chỉ vì có exception.

Front: Ngưỡng log thường đặt ở production là gì?
Back: info.

Front: Ngưỡng log thường đặt ở môi trường dev là gì?
Back: debug.

Front: Lỗi validation do người dùng nhập sai nên log mức nào?
Back: info hoặc warn, không phải error.

Front: Vì sao không log error cho việc đã catch và xử lý xong?
Back: Vì tạo ra error giả, làm alert nhiễu và làm mòn niềm tin vào log.

Front: Bật debug tạm thời trên production bằng cách nào?
Back: Đổi ngưỡng qua biến môi trường hoặc dynamic log level, có thời hạn rồi tắt lại.

Front: Dấu hiệu ngưỡng log đặt quá thấp ở production?
Back: Quá nhiều dòng log mỗi phút, khó tìm lỗi thật, tốn chi phí lưu trữ và alert nhiễu.

---

## Contents

Part of [🔭 Observability cho người mới: log, metric và cảnh báo](https://everwise.study/courses/observability-cho-nguoi-moi-log-metric-va-canh-bao.md)

- **📝 Log có cấu trúc và log level**
  - [🧱 Structured logging: log có cấu trúc và các field cần có](https://everwise.study/courses/observability-cho-nguoi-moi-log-metric-va-canh-bao/structured-logging-va-cac-field-can-co.md)
  - [🎚️ Log level: chọn đúng mức để không ngập log](https://everwise.study/courses/observability-cho-nguoi-moi-log-metric-va-canh-bao/log-level-chon-dung-muc.md) (sign-in required) — this lesson
  - [🚫 Log gì và không log gì](https://everwise.study/courses/observability-cho-nguoi-moi-log-metric-va-canh-bao/log-gi-va-khong-log-gi.md)
  - [✅ Checkpoint: Log có cấu trúc và log level](https://everwise.study/courses/observability-cho-nguoi-moi-log-metric-va-canh-bao/checkpoint-log-co-cau-truc-va-log-level.md)
- **📊 Metric cơ bản và dashboard**
  - [⏱️ Metric cơ bản: latency và error rate](https://everwise.study/courses/observability-cho-nguoi-moi-log-metric-va-canh-bao/metric-co-ban-latency-va-error-rate.md)
  - [🖥️ Dashboard: vài chỉ số đáng nhìn mỗi ngày](https://everwise.study/courses/observability-cho-nguoi-moi-log-metric-va-canh-bao/dashboard-vai-chi-so-dang-nhin.md)
  - [✅ Checkpoint: Metric cơ bản và dashboard](https://everwise.study/courses/observability-cho-nguoi-moi-log-metric-va-canh-bao/checkpoint-metric-co-ban-va-dashboard.md)
- **🔔 Alert ít nhiễu, tracing và nhờ AI thêm log**
  - [🚨 Alert không gây mệt mỏi](https://everwise.study/courses/observability-cho-nguoi-moi-log-metric-va-canh-bao/alert-khong-gay-met-moi.md)
  - [🧵 Tracing ở mức khái niệm](https://everwise.study/courses/observability-cho-nguoi-moi-log-metric-va-canh-bao/tracing-o-muc-khai-niem.md)
  - [🤖 Nhờ AI thêm log đúng chỗ](https://everwise.study/courses/observability-cho-nguoi-moi-log-metric-va-canh-bao/nho-ai-them-log-dung-cho.md) (premium content)
  - [✅ Checkpoint: Alert, tracing và AI hỗ trợ](https://everwise.study/courses/observability-cho-nguoi-moi-log-metric-va-canh-bao/checkpoint-alert-tracing-va-ai-ho-tro.md) (premium content)
- [Course cheatsheet](https://everwise.study/courses/observability-cho-nguoi-moi-log-metric-va-canh-bao/cheatsheet.md) (premium content)
