·

Tiếng Việt: Log analysis and error pattern recognition

Log analysis and error pattern recognition

Một hệ thống production trung bình tạo ra hàng trăm nghìn đến hàng triệu dòng log mỗi ngày. Không QA Engineer nào — dù senior đến đâu — có thể đọc hết khối lượng đó bằng mắt và tìm ra pattern lỗi (error pattern) một cách đáng tin cậy. Đây chính xác là lớp việc mà LLM (Large Language Model — mô hình ngôn ngữ lớn) làm tốt: quét văn bản khối lượng lớn, nhận dạng pattern lệch khỏi baseline, và tổng hợp một câu chuyện nhân-quả từ hàng trăm dòng log rời rạc.

Nhưng có một cái bẫy mà rất nhiều QA Engineer rơi vào: đổ nguyên khối log vào AI và hỏi "có gì bất thường không?" mà không cho AI biết "bình thường" là gì. Kết quả là AI bịa ra anomaly (điểm bất thường) không tồn tại, hoặc bỏ lỡ pattern thật vì nó không có baseline để so sánh. Bài này sẽ đi qua bốn kỹ năng cốt lõi: quét log tìm anomaly có baseline rõ ràng, correlate (liên kết) lỗi qua nhiều service/environment, tách signal khỏi noise trong test output, và — quan trọng nhất về lâu dài — xây dựng một prompt library (thư viện prompt) tái sử dụng cho các loại lỗi bạn gặp lại thường xuyên nhất.

Làm Sao Dùng AI Để Quét Khối Lượng Log Lớn Tìm Anomaly và Pattern Lỗi?

AI không "biết" hệ thống của bạn nên hoạt động ra sao ở trạng thái bình thường. Nếu bạn không cung cấp baseline (trạng thái hành vi bình thường), model sẽ dùng kiến thức chung về "log điển hình trông như thế nào" — và điều đó dẫn đến false positive (báo động giả) tràn lan: nó sẽ gắn cờ mọi WARN log là "đáng ngờ" dù team bạn coi đó là chuyện thường ngày.

Mẹo: Luôn coi việc mô tả baseline là bước bắt buộc trước khi paste log vào AI, không phải bước tùy chọn. Một quy tắc thực dụng: nếu bạn không thể tự trả lời câu hỏi "cái gì được coi là bình thường ở đây?" bằng một câu, đừng hỏi AI tìm anomaly — hãy tự đi tìm baseline trước.

Định Nghĩa "Bình Thường" Trước Khi Yêu Cầu Tìm Anomaly

Baseline behavior cần được viết ra tường minh: tần suất lỗi thông thường là bao nhiêu, latency (thời gian phản hồi) trung bình/p95 là gì, những mã lỗi nào là "bình thường" (ví dụ 429 rate-limit khi traffic cao là dự kiến), và những pattern lặp lại nào đã được biết và chấp nhận (ví dụ retry 1 lần rồi thành công không phải bug).

Dưới đây là ví dụ thực tế về cách cấu trúc một yêu cầu quét anomaly hoàn chỉnh, với baseline được khai báo rõ trước khi log được đưa vào.

Baseline Behavior

Baseline behavior của service `order-service` trong giờ cao điểm (9h-11h, 19h-21h):
- Error rate bình thường: 0.3% - 0.8% tổng số request
- Latency p95 bình thường: 180ms - 320ms
- Log level WARN xuất hiện ~40-60 lần/phút do rate-limit throttling (429), đây là hành vi
  dự kiến, KHÔNG phải bug
- DB connection pool: 20 connection, thường dùng 8-14 connection cùng lúc
- Retry pattern bình thường: request timeout lần 1 -> retry tự động -> thành công.
  Đây được coi là bình thường nếu tỷ lệ retry-thành-công > 95%
- Deploy gần nhất: 2026-07-08 08:30 UTC (feature flag mới: enable_split_payment)

Anomaly Scan Request

Bạn là một AI hỗ trợ phân tích log cho team QA. Dưới đây là baseline behavior của
hệ thống (phần "Baseline Behavior") và log thực tế thu thập trong khoảng
09:00-11:00 UTC ngày 2026-07-08 (phần "Logs").

Nhiệm vụ:
1. So sánh log thực tế với baseline. Chỉ gắn cờ những gì THỰC SỰ lệch khỏi baseline
   đã mô tả — không gắn cờ những pattern đã được khai báo là "bình thường".
2. Với mỗi anomaly tìm được, cho biết: thời điểm bắt đầu, mức độ nghiêm trọng
   (tăng bao nhiêu % so với baseline), và dòng log làm chứng cứ (trích dẫn nguyên văn).
3. Nếu một anomaly có khả năng liên quan đến deploy gần nhất (feature flag
   enable_split_payment), hãy nêu rõ giả thuyết đó và mức độ tự tin (thấp/trung bình/cao).
4. Không suy diễn nguyên nhân gốc nếu log không có chứng cứ trực tiếp — nói rõ
   "cần thêm log/metric X để xác nhận" thay vì đoán.

Baseline Behavior:
[dán baseline ở trên]

Logs:
[dán log thô]

Logs

2026-07-08T09:02:11.442Z INFO  order-service req=a91f status=200 latency_ms=210 pool_used=9
2026-07-08T09:02:14.108Z WARN  order-service req=a922 status=429 latency_ms=12 reason=rate_limit
2026-07-08T09:07:40.951Z ERROR order-service req=b1a0 status=500 latency_ms=8400 pool_used=20 msg="connection pool exhausted, waited 8000ms"
2026-07-08T09:07:41.203Z ERROR order-service req=b1a1 status=500 latency_ms=8390 pool_used=20 msg="connection pool exhausted, waited 8000ms"
2026-07-08T09:07:42.980Z ERROR order-service req=b1a4 status=500 latency_ms=8410 pool_used=20 msg="connection pool exhausted, waited 8000ms" feature_flag=enable_split_payment
2026-07-08T09:08:03.117Z WARN  order-service req=b201 status=429 latency_ms=14 reason=rate_limit
2026-07-08T09:15:22.301Z ERROR order-service req=c330 status=500 latency_ms=8100 pool_used=20 msg="connection pool exhausted, waited 8000ms" feature_flag=enable_split_payment

Với input này, một AI được brief đúng baseline sẽ trả lời khác hẳn so với AI không có baseline: nó sẽ bỏ qua hai dòng WARN 429 (đã được khai báo là bình thường), và tập trung đúng vào cụm lỗi "connection pool exhausted" xuất hiện từ 09:07 — đúng lúc sau deploy — với pool_used luôn chạm mức tối đa 20/20. Đây là anomaly thật, có bằng chứng, có mối liên hệ thời gian với feature flag mới.

Quét Khối Lượng Lớn Với Chiến Lược Grep-First

Log thật không dài vài chục dòng như ví dụ trên — nó có thể là vài triệu dòng mỗi giờ, vượt xa context window (giới hạn số token AI xử lý được trong một lần) của bất kỳ model nào. Chiến lược thực dụng là grep-first: dùng công cụ dòng lệnh để thu hẹp log về vài nghìn dòng "nghi vấn" trước khi đưa cho AI, thay vì cố nhồi toàn bộ log thô vào.

Quy trình gợi ý:

  1. Dùng grep -iE "error|exception|fatal|timeout|5[0-9]{2}" để lọc ra các dòng có khả năng là lỗi.
  2. Dùng awk hoặc script nhỏ để đếm số dòng lỗi theo từng khung 1 phút, tìm cửa sổ thời gian có đột biến (spike).
  3. Chỉ trích xuất log trong cửa sổ đột biến đó (kèm 2-3 phút trước/sau để có ngữ cảnh) rồi mới đưa cho AI.
grep -iE "error|exception|fatal|timeout|pool exhausted|5[0-9]{2}" order-service.log > suspects.log

awk '{print substr($1,1,16)}' suspects.log | sort | uniq -c | sort -rn | head -20

awk -v start="2026-07-08T09:03" -v end="2026-07-08T09:22" \
  '$1 >= start && $1 <= end' order-service.log > window.log

Sau ba bước này, window.log thường chỉ còn vài trăm đến vài nghìn dòng — đủ nhỏ để đưa nguyên vào AI mà không cần tóm tắt trước, và đủ đầy đủ ngữ cảnh để AI không bỏ lỡ pattern.

Phát Hiện Anomaly Theo Thời Gian (Temporal Anomaly Detection)

Nhiều bug production không nằm ở "có lỗi hay không" mà ở "lỗi có đang tăng tốc không". Một service báo lỗi đều đều 2 lần/phút suốt 3 ngày không đáng lo bằng một service đột nhiên nhảy từ 2 lần/phút lên 200 lần/phút trong 90 giây. AI rất giỏi nhận diện dạng tăng tốc này nếu bạn yêu cầu nó nhìn theo trục thời gian thay vì chỉ đếm tổng.

Log dưới đây được sắp theo thời gian (timestamp ISO 8601, UTC). Hãy:
1. Nhóm số lượng dòng ERROR theo từng cửa sổ 30 giây.
2. Tìm điểm mà tốc độ lỗi tăng > 5 lần so với cửa sổ 30 giây liền trước (temporal spike).
3. Với mỗi spike, liệt kê request_id hoặc req của các dòng lỗi đầu tiên trong spike đó —
   đây thường là "bệnh nhân số 0" giúp xác định request nào kích hoạt sự cố.
4. Không gộp các spike cách nhau hơn 5 phút thành một sự cố — coi là hai sự cố riêng.

Logs

2026-07-08T09:07:38.900Z INFO  order-service req=b199 status=200 latency_ms=190
2026-07-08T09:07:40.951Z ERROR order-service req=b1a0 status=500 latency_ms=8400
2026-07-08T09:07:41.203Z ERROR order-service req=b1a1 status=500 latency_ms=8390
2026-07-08T09:07:41.560Z ERROR order-service req=b1a2 status=500 latency_ms=8410
2026-07-08T09:07:41.900Z ERROR order-service req=b1a3 status=500 latency_ms=8420
2026-07-08T09:07:42.100Z ERROR order-service req=b1a4 status=500 latency_ms=8410
2026-07-08T09:07:42.560Z ERROR order-service req=b1a5 status=500 latency_ms=8399
2026-07-08T09:08:10.220Z INFO  order-service req=b210 status=200 latency_ms=205

Cửa sổ 30 giây từ 09:07:30 đến 09:08:00 có 6 dòng ERROR liên tiếp — so với cửa sổ liền trước gần như bằng 0 — đây là spike rõ rệt. req=b1a0 là request đầu tiên trong cụm này, nên nó là điểm khởi đầu đáng điều tra kỹ nhất (log request đó chi tiết hơn, payload gửi lên, người dùng nào gọi).

Làm Sao Correlate Lỗi Qua Nhiều Service và Environment Với AI?

Bug hiếm khi nằm trọn trong một service ở hệ thống microservices. Một request đi qua api-gateway, rồi payments-service, rồi notification-service — và log của mỗi service nằm ở một nơi khác nhau, format khác nhau, timezone có khi cũng khác nhau. Con người rất chậm khi phải nhảy qua nhảy lại giữa 3-4 tab log để ghép câu chuyện lại. AI có thể làm việc này trong một lần nếu bạn đưa đủ log của các service liên quan cùng một request, kèm identifier để nó nối đúng dòng.

Mẹo: Đừng để AI tự "đoán" dòng log nào của service B tương ứng với dòng log nào của service A dựa vào timestamp gần nhau — luôn ưu tiên correlation ID hoặc request ID tường minh. Correlate theo timestamp là phương án cuối cùng, không phải mặc định, vì hai request khác nhau vẫn có thể xảy ra cùng thời điểm.

Xây Dựng Multi-Service Log Canvas

"Log canvas" ở đây nghĩa là bạn tập hợp log của tất cả service liên quan đến một luồng nghiệp vụ, gắn nhãn rõ service nào là service nào, rồi đưa tất cả vào cùng một prompt. Ví dụ luồng thanh toán đi qua 3 service: api-gateway nhận request, payments-service xử lý giao dịch, notification-service gửi email xác nhận.

Service A: api-gateway (handles incoming requests)

2026-07-08T09:14:02.113Z INFO  [api-gateway] req_id=7f3a9c2e method=POST path=/v1/payments/charge status=202 latency_ms=48
2026-07-08T09:14:32.501Z ERROR [api-gateway] req_id=7f3a9c2e method=POST path=/v1/payments/charge status=504 latency_ms=30388 msg="upstream timeout calling payments-service"

Service B: payments-service (processes payment operations)

2026-07-08T09:14:02.140Z INFO  [payments-service] correlation_id=7f3a9c2e event=charge.received amount=149000 currency=VND merchant_id=MCH-2291
2026-07-08T09:14:20.883Z WARN  [payments-service] correlation_id=7f3a9c2e event=charge.retry attempt=2 reason="db_lock_timeout table=ledger_entries"
2026-07-08T09:14:31.980Z WARN  [payments-service] correlation_id=7f3a9c2e event=charge.retry attempt=3 reason="db_lock_timeout table=ledger_entries"
2026-07-08T09:14:32.005Z ERROR [payments-service] correlation_id=7f3a9c2e event=charge.failed reason="lock wait timeout exceeded" duration_ms=29865

Service C: notification-service (sends emails/webhooks after payment)

2026-07-08T09:14:32.100Z INFO  [notification-service] correlation_id=7f3a9c2e event=email.skipped reason="upstream_charge_status=failed"
2026-07-08T09:14:32.114Z INFO  [notification-service] correlation_id=7f3a9c2e event=webhook.sent target="https://merchant.example.com/webhook" payload_status=failed

Correlation Analysis Request

Dưới đây là log của 3 service (api-gateway, payments-service, notification-service)
cho cùng một request, được nối bởi req_id / correlation_id = 7f3a9c2e.

Hãy:
1. Dựng lại timeline đầy đủ của request này qua cả 3 service, theo đúng thứ tự
   thời gian thực tế (không theo thứ tự tôi dán log).
2. Xác định service nào là nguồn gốc của lỗi (root cause service), và service nào
   chỉ đang phản ứng lại lỗi đó (downstream effect).
3. Giải thích tại sao api-gateway trả về 504 (timeout) trong khi payments-service
   trả về lỗi cụ thể hơn (lock wait timeout) — đây có phải cùng một sự cố hay hai
   sự cố độc lập trùng thời điểm?
4. Đề xuất: timeout hiện tại của api-gateway khi gọi payments-service là bao nhiêu
   (suy ra từ log), và có nên thay đổi giá trị này không, dựa trên latency thực tế
   quan sát được ở payments-service?

Service A: api-gateway (log dán ở trên)
Service B: payments-service (log dán ở trên)
Service C: notification-service (log dán ở trên)

Dùng Correlation ID và Request ID Để Tăng Độ Chính Xác

Correlation ID (mã tương quan) là giá trị được sinh ra một lần ở service đầu tiên nhận request, rồi được truyền xuyên suốt qua header (thường là X-Correlation-Id hoặc X-Request-Id) tới mọi service downstream, và mọi service đều log lại giá trị này. Nếu hệ thống của bạn chưa có correlation ID, đây là khoản đầu tư hạ tầng đáng làm trước khi đầu tư vào AI log analysis — vì không có nó, AI (và cả con người) buộc phải suy đoán qua timestamp, vốn không đáng tin khi traffic cao.

Khi correlation ID có sẵn, việc trích lọc trước khi đưa cho AI cũng đơn giản hơn nhiều — chỉ cần một lệnh grep:

CID="7f3a9c2e"
grep "$CID" api-gateway.log payments-service.log notification-service.log \
  | sort -t: -k2 > correlated-timeline.log

Correlated Log Lines — By Service

Kết quả sau khi AI dựng lại timeline đúng thứ tự thời gian thực (không phải thứ tự bạn dán vào), gộp theo service để dễ đối chiếu:

api-gateway

09:14:02.113  INFO   charge accepted, forwarded to payments-service
09:14:32.501  ERROR  upstream timeout after 30388ms — returned 504 to client

payments-service

09:14:02.140  INFO   charge.received amount=149000 VND
09:14:20.883  WARN   retry #2 due to db_lock_timeout on ledger_entries
09:14:31.980  WARN   retry #3 due to db_lock_timeout on ledger_entries
09:14:32.005  ERROR  charge.failed after 29865ms — lock wait timeout exceeded

notification-service

09:14:32.100  INFO   email.skipped — upstream charge status = failed
09:14:32.114  INFO   webhook.sent to merchant with payload_status=failed

Nhìn timeline theo cách này, root cause hiện rõ: payments-service bị nghẽn ở tầng DB (lock wait timeout trên bảng ledger_entries), mất 29.865ms để thất bại — vượt ngưỡng timeout 30.000ms mà api-gateway đặt ra cho lời gọi upstream, nên api-gateway trả 504 gần như đúng lúc payments-service tự báo lỗi. Đây là MỘT sự cố, không phải hai: DB lock là nguyên nhân gốc, timeout ở gateway và email bị skip ở notification-service chỉ là hệ quả lan truyền (cascading failure).

So Sánh Environment Để Tìm Bug Đặc Thù Theo Environment

Một dạng bug khó chịu là "chỉ xảy ra ở staging, không xảy ra ở production" hoặc ngược lại — thường do khác biệt cấu hình (connection pool size, timeout, feature flag, phiên bản dependency) mà không ai nhớ hết. Cách hiệu quả để AI giúp ở đây là đưa log của CẢ HAI environment cho cùng một hành động, và yêu cầu nó tìm điểm khác biệt về hành vi lẫn cấu hình ngầm suy ra từ log.

Environment A Logs (where the bug occurs)

2026-07-08T10:02:11.001Z INFO  [staging] db_pool_config max_connections=10
2026-07-08T10:02:15.442Z ERROR [staging] req=s901 msg="connection pool exhausted, waited 5000ms" pool_used=10
2026-07-08T10:02:15.443Z ERROR [staging] req=s902 msg="connection pool exhausted, waited 5000ms" pool_used=10

Environment B Logs (where the bug does not occur)

2026-07-08T10:02:11.001Z INFO  [production] db_pool_config max_connections=50
2026-07-08T10:02:15.442Z INFO  [production] req=p901 status=200 latency_ms=95 pool_used=22
2026-07-08T10:02:15.443Z INFO  [production] req=p902 status=200 latency_ms=88 pool_used=23
So sánh log của hai environment cho cùng một luồng nghiệp vụ (staging bị lỗi,
production không bị lỗi ở cùng khung thời gian và traffic tương đương).

Hãy:
1. Liệt kê mọi khác biệt về CẤU HÌNH suy ra được trực tiếp từ log (không suy diễn
   ngoài log) — ví dụ giá trị connection pool, timeout, feature flag nếu log có in ra.
2. Đánh giá khác biệt nào có khả năng cao nhất là nguyên nhân gây lỗi chỉ xảy ra ở
   staging, dựa trên tỷ lệ traffic/pool_used quan sát được.
3. Đề xuất một thay đổi cấu hình cụ thể (giá trị số) để staging phản ánh đúng hành
   vi production, kèm rủi ro nếu áp dụng thay đổi đó.

Chỉ với hai đoạn log ngắn này, AI sẽ chỉ ra ngay: staging có max_connections=10 trong khi production có max_connections=50, và ở cùng mức traffic (2 request đồng thời trong ví dụ, nhưng thực tế thường là hàng chục), staging chạm giới hạn pool còn production còn dư nhiều. Đây không phải là bug logic — là bug cấu hình environment, loại bug mà log so sánh song song phát hiện nhanh hơn nhiều so với đọc code.

Mẹo: Khi so sánh hai environment, luôn đưa log ở CÙNG một khung thời gian tương đối (ví dụ "phút đầu tiên sau khi bắt đầu load test") và cùng loại traffic, chứ không phải log ngẫu nhiên ở hai thời điểm khác nhau — nếu không AI sẽ so sánh táo với cam và đưa ra kết luận sai.

Làm Sao Dùng AI Để Tách Signal Khỏi Noise Trong Test Output Dài Dòng?

Verbose test output (log test chi tiết) từ một CI pipeline chạy hàng nghìn test case có thể dài hàng chục nghìn dòng — phần lớn là log PASS vô nghĩa, log setup/teardown, log framework in ra mà không ai cần. Signal thật (test nào fail, vì sao) có khi chỉ nằm trong 20 dòng giữa một biển 20.000 dòng. Đây là bài toán AI xử lý cực tốt vì bản chất là information extraction (trích xuất thông tin) từ văn bản dài — miễn là bạn ra yêu cầu đúng để nó không nhầm giữa "test fail" và "hệ thống crash".

Mẹo: Luôn yêu cầu AI trích dẫn nguyên văn dòng log làm bằng chứng cho mỗi kết luận, đừng chấp nhận tóm tắt suông. Nếu AI không trích được dòng cụ thể, khả năng cao nó đang bịa (hallucinate).

Nhận Diện Signal Trong Test Failure Output

Dưới đây là toàn bộ output của một lần chạy test suite (pytest). Output này dài
và có nhiều nhiễu (log INFO của framework, log setup fixture, deprecation warning
không liên quan).

Hãy trích xuất CHỈ những thông tin sau, bỏ qua toàn bộ phần còn lại:
1. Danh sách test case FAIL (tên đầy đủ, không viết tắt).
2. Với mỗi test FAIL, dòng assertion hoặc exception message chính xác gây ra fail
   (trích nguyên văn, không diễn giải).
3. Số lượng test PASS/FAIL/SKIP tổng.
4. Nếu có test nào fail với cùng loại exception, hãy nhóm chúng lại — đây có thể
   là một nguyên nhân gốc chung.

Không tóm tắt các phần INFO/WARNING không liên quan đến kết quả test.

Raw Test Output

============================= test session starts ==============================
platform linux -- Python 3.11.6, pytest-7.4.3
collected 42 items

tests/test_checkout.py::test_apply_valid_coupon PASSED                   [  2%]
tests/test_checkout.py::test_apply_expired_coupon PASSED                 [  4%]
tests/test_checkout.py::test_cart_total_with_tax FAILED                  [  7%]
tests/test_checkout.py::test_cart_total_no_tax PASSED                    [  9%]
tests/test_payment.py::test_charge_success PASSED                       [ 11%]
tests/test_payment.py::test_charge_declined_card FAILED                 [ 14%]
tests/test_payment.py::test_charge_timeout_retry FAILED                 [ 16%]
tests/test_shipping.py::test_calc_shipping_domestic PASSED              [ 19%]
...
=================================== FAILURES ===================================
_____________________________ test_cart_total_with_tax _________________________
    def test_cart_total_with_tax():
        cart = build_cart(items=[item_a, item_b])
>       assert cart.total == Decimal("109.00")
E       AssertionError: assert Decimal('108.99') == Decimal('109.00')
tests/test_checkout.py:88: AssertionError
_____________________________ test_charge_declined_card ________________________
    def test_charge_declined_card():
        result = payment_client.charge(card=DECLINED_TEST_CARD, amount=5000)
>       assert result.status == "declined"
E       AssertionError: assert 'error' == 'declined'
tests/test_payment.py:44: AssertionError
_____________________________ test_charge_timeout_retry _________________________
    def test_charge_timeout_retry():
        with mock_upstream_delay(ms=6000):
            result = payment_client.charge(card=VALID_TEST_CARD, amount=5000)
>       assert result.status == "success"
E       ConnectionError: upstream timed out after 5000ms
tests/test_payment.py:61: ConnectionError
=========================== 3 failed, 38 passed, 1 skipped in 14.22s ===========================

Failing Tests

Sau khi AI xử lý, output đáng dùng chỉ còn phần cốt lõi:

Tổng kết: 3 FAILED, 38 PASSED, 1 SKIPPED (42 test)

1. test_checkout.py::test_cart_total_with_tax
   AssertionError: assert Decimal('108.99') == Decimal('109.00')
   -> Lỗi rounding (chênh 0.01), không liên quan đến 2 test dưới.

2. test_payment.py::test_charge_declined_card
   AssertionError: assert 'error' == 'declined'
   -> API trả 'error' thay vì 'declined' cho thẻ bị từ chối — có thể là lỗi mapping status.

3. test_payment.py::test_charge_timeout_retry
   ConnectionError: upstream timed out after 5000ms
   -> Test giả lập delay 6000ms nhưng timeout client là 5000ms — có thể do thay đổi
      config timeout gần đây, KHÔNG liên quan đến 2 test trên.

Nhóm theo nguyên nhân: 3 test fail là 3 nguyên nhân độc lập, không có nguyên nhân
gốc chung — cần fix riêng từng cái.

Phân Biệt Test Failure Với System Failure

Một sai lầm phổ biến khi giao log CI cho AI (hoặc cho người) là gộp chung "test tự nó fail vì assertion sai" với "toàn bộ test run sập vì hệ thống lỗi" (ví dụ: mất kết nối DB, container OOM, network timeout ở tầng CI runner). Đây là hai loại vấn đề cần hành động hoàn toàn khác nhau — cái đầu là bug ở code/test, cái sau là vấn đề hạ tầng CI. Luôn yêu cầu AI phân loại rõ hai nhóm này thay vì liệt kê chung một danh sách "test bị fail".

Trong output test dưới đây, hãy phân loại RÕ RÀNG hai nhóm khác nhau, không gộp chung:

Nhóm 1 — Test Failure (logic đúng, nhưng test tự fail do assertion/exception nghiệp vụ):
  test chạy hết, chỉ là kết quả không như mong đợi.

Nhóm 2 — System/Infrastructure Failure (test không hoàn thành được vì lý do hạ tầng):
  timeout kết nối tới service phụ trợ, OOM (out of memory), container bị kill,
  lỗi setup/teardown fixture do DB không khởi động được, v.v.

Với Nhóm 2, đừng coi đây là "bug trong code được test" — đây là dấu hiệu môi trường
test không ổn định, cần báo riêng cho team infra/CI, không nên tạo bug ticket cho
feature.

Việc phân loại này giúp tránh tình huống rất phổ biến: một QA Engineer log 5 bug ticket cho 5 test fail, nhưng thực ra chỉ có 2 là bug thật, còn 3 là do CI runner hết RAM — gây lãng phí thời gian của cả dev team.

Dọn Dẹp Verbose Parallel Test Output

Khi test chạy song song (parallel, ví dụ pytest-xdist với nhiều worker gw0, gw1, gw2), log của các worker bị xen kẽ (interleaved) lộn xộn theo thời gian thực thi, không theo thứ tự file test. Đọc bằng mắt gần như bất khả thi khi có > 4 worker. AI xử lý việc này tốt nếu bạn yêu cầu nó nhóm lại theo worker và theo test trước khi phân tích.

Parallel Test Output

[gw0] PASSED tests/test_a.py::test_1
[gw2] PASSED tests/test_c.py::test_5
[gw1] FAILED tests/test_b.py::test_3
[gw0] PASSED tests/test_a.py::test_2
[gw3] PASSED tests/test_d.py::test_7
[gw1] Traceback (most recent call last):
[gw1]   File "test_b.py", line 22, in test_3
[gw1]     assert response.status_code == 200
[gw1] AssertionError: assert 503 == 200
[gw2] FAILED tests/test_c.py::test_6
[gw2] Traceback (most recent call last):
[gw2]   File "test_c.py", line 15, in test_6
[gw2]     assert response.status_code == 200
[gw2] AssertionError: assert 503 == 200
[gw3] PASSED tests/test_d.py::test_8
Output test parallel dưới đây bị xen kẽ giữa nhiều worker (gw0-gw3), khó đọc theo
thứ tự gốc. Hãy:
1. Nhóm lại output theo từng worker, giữ nguyên thứ tự trong worker đó.
2. Liệt kê riêng các test FAILED kèm traceback đầy đủ của từng test đó.
3. Kiểm tra: có bao nhiêu test fail với CÙNG exception/status code? Nếu có ≥ 2 test
   fail giống nhau (ví dụ cùng assert 503 == 200) ở các worker khác nhau, đây rất
   có khả năng là MỘT sự cố chung (ví dụ một service phụ trợ bị quá tải khi chạy
   song song), không phải 2 bug độc lập trong 2 test khác nhau.

Với ví dụ trên, cả test_3 (worker gw1) và test_6 (worker gw2) đều fail với cùng lý do assert 503 == 200 — dấu hiệu rõ của một service downstream bị quá tải khi nhiều worker gọi đồng thời, chứ không phải lỗi logic riêng của hai test case. Đây chính là giá trị của việc nhóm theo nguyên nhân thay vì báo cáo từng test độc lập.

Xây Dựng CI Failure Triage Report

Triage (phân loại độ ưu tiên/khẩn cấp) một CI pipeline fail thường cần trả lời nhanh: build này fail vì code mới hay vì flaky test cũ, có nên block merge không, ai cần được tag vào. AI làm tốt việc này nếu bạn đưa cả log CI VÀ diff code gần nhất cùng lúc — vì rất nhiều lần "test tự nhiên fail" thực ra liên quan trực tiếp đến dòng code vừa đổi.

CI Pipeline Output Summary

Pipeline: build-and-test #4821
Branch: feature/split-payment-flow
Trigger: PR #392 (merge vào main)
Duration: 6m42s
Status: FAILED

Stage [unit-tests]: PASSED (312/312)
Stage [integration-tests]: FAILED (2 failed, 118 passed)
  - test_payment_flow.py::test_split_payment_two_cards FAILED
  - test_payment_flow.py::test_split_payment_refund FAILED
Stage [e2e-tests]: SKIPPED (do integration-tests failed)

Recent Code Changes

diff --git a/services/payments/split_payment.py b/services/payments/split_payment.py
index 3a91cd2..7be0f1a 100644
--- a/services/payments/split_payment.py
+++ b/services/payments/split_payment.py
@@ -40,7 +40,7 @@ def split_charge(amount, cards):
     shares = allocate_shares(amount, cards)
-    for card, share in shares.items():
+    for card, share in shares.items():  # BUG risk: dict order not guaranteed pre-3.7 style assumption removed
         result = charge_card(card, share)
-        if not result.success:
+        if not result.success and result.status != "pending":
             rollback_previous_charges(results_so_far)
             raise SplitPaymentError(card, result.reason)
Đây là output CI của một pipeline fail (phần "CI Pipeline Output Summary") và
diff code vừa được merge vào PR gây ra lần chạy này (phần "Recent Code Changes").

Hãy viết một CI Failure Triage Report ngắn gồm:
1. Kết luận: fail này có khả năng do code mới trong diff, hay là flaky test không
   liên quan? Cho mức độ tự tin và lý do.
2. Nếu do code mới: chỉ đúng dòng diff nghi vấn nhất và giải thích cơ chế gây fail
   (liên hệ trực tiếp giữa thay đổi code và tên test fail).
3. Khuyến nghị hành động: BLOCK merge / merge nhưng theo dõi / merge an toàn.
4. Người cần được tag review (dựa trên tên file/module bị đổi, không cần đoán tên
   người thật, chỉ cần chỉ ra "cần owner của module split_payment.py").

Trong ví dụ này, dòng diff thay đổi điều kiện if not result.success thành if not result.success and result.status != "pending" là ứng viên rất đáng nghi: nó thay đổi logic rollback, và tên test fail (test_split_payment_two_cards, test_split_payment_refund) khớp thẳng với module vừa đổi (split_payment.py). Một CI triage report tốt sẽ kết luận "khả năng cao do code mới, khuyến nghị BLOCK merge, cần owner module payments review điều kiện status != 'pending' mới thêm" — chứ không chỉ nói suông "2 test fail, cần fix".

Làm Sao Xây Dựng Reusable Log Analysis Prompt Cho Các Loại Lỗi Thường Gặp Nhất?

Đây là phần quan trọng nhất về lâu dài. Viết một prompt phân tích log tốt cho một sự cố cụ thể là việc một lần. Nhưng nếu team bạn liên tục gặp lại 4-5 dạng sự cố giống nhau (timeout khi gọi service X, connection pool exhausted, race condition ở checkout, lỗi rounding số tiền...), việc viết lại prompt từ đầu mỗi lần là lãng phí năng lực. Giá trị thật nằm ở việc xây dựng một prompt library — các template đã được kiểm chứng, ai trong team cũng dùng được, và càng dùng càng được cải thiện.

Mẹo: Coi prompt library của team như một phần của test suite — nó cần được version control (quản lý phiên bản), review, và cải tiến dựa trên dữ liệu thật, không phải viết một lần rồi bỏ quên trong một file Google Doc không ai cập nhật.

Xác Định Các Loại Lỗi Tần Suất Cao Của Bạn

Trước khi viết prompt tái sử dụng, hãy làm một việc rất đơn giản nhưng ít ai làm: nhìn lại 20-30 incident/bug gần nhất và phân loại chúng. Thường thì 80% sự cố rơi vào một số ít nhóm nguyên nhân lặp lại: timeout giữa service, hết connection/resource pool, lỗi tính toán số (rounding, đơn vị tiền tệ), race condition khi ghi đồng thời, và lỗi cấu hình lệch giữa environment. Mỗi nhóm này nên có một prompt template riêng, vì bằng chứng cần thu thập và câu hỏi cần hỏi AI là khác nhau cho từng nhóm.

Cấu Trúc Của Một Reusable Log Analysis Prompt

Một prompt tái sử dụng tốt có 3 phần rõ ràng: System Context (phần cố định, mô tả hệ thống — viết một lần, dùng lại mãi), Evidence (phần biến đổi — điền lại cho từng sự cố cụ thể), và Analysis Request (phần cố định — yêu cầu phân tích, output format mong muốn). Tách rõ 3 phần này giúp bất kỳ ai trong team, kể cả người mới, cũng điền đúng chỗ cần điền mà không phải nghĩ lại cấu trúc prompt từ đầu.

System Context (fixed)

Hệ thống: order-service (Node.js, PostgreSQL 14, connection pool qua pg-pool).
Kiến trúc: api-gateway -> order-service -> payments-service -> ledger DB.
Retry policy hiện tại: 3 lần retry, exponential backoff 500ms/1s/2s.
Timeout hiện tại: api-gateway chờ order-service tối đa 10s; order-service chờ
payments-service tối đa 8s.
Connection pool: order-service có max_connections=20 tới PostgreSQL chính.
Lịch sử: đã có 3 sự cố "connection pool exhausted" trong 2 tháng qua, đều xảy ra
vào giờ cao điểm (9-11h hoặc 19-21h).

Evidence (variable — fill in for this incident)

Failure Message

[PASTE_FAILURE_MESSAGE_HERE]
Ví dụ: "Error: pool exhausted, waited 8000ms for connection (order-service, req=xxx)"

Application Logs (time window: [PASTE_START_TIME] to [PASTE_END_TIME])

[PASTE_LOGS_HERE — ưu tiên log đã lọc qua grep-first, không quá 2000 dòng]

Database Server Metrics (if available)

[PASTE_DB_METRICS_HERE]
Ví dụ: active_connections, idle_connections, longest_running_query_ms,
lock_wait_count trong cùng khung thời gian.

Analysis Request (fixed)

Dựa trên System Context, Failure Message, Application Logs, và Database Server
Metrics ở trên, hãy:

1. Xác nhận đây có phải cùng dạng "connection pool exhausted" như 3 sự cố lịch sử
   đã ghi nhận, hay là một biến thể mới có đặc điểm khác.
2. Liệt kê các giả thuyết nguyên nhân gốc theo thứ tự khả năng, MỖI giả thuyết kèm
   bằng chứng cụ thể ủng hộ (trích dòng log/metric) và bằng chứng (nếu có) phản bác.
3. Đề xuất 1-2 hành động chẩn đoán tiếp theo cụ thể nếu bằng chứng hiện tại CHƯA
   đủ để kết luận chắc chắn (ví dụ: "cần thêm slow query log trong khung 09:07-09:15").
4. KHÔNG đề xuất giải pháp code cụ thể ở bước này — chỉ tập trung xác định nguyên
   nhân gốc trước.

Với cấu trúc này, một Senior QA Engineer chỉ cần dán 3 khối "Evidence" cho từng sự cố mới, còn System Context và Analysis Request giữ nguyên — giảm thời gian chuẩn bị prompt từ 15-20 phút xuống 2-3 phút, và quan trọng hơn: đảm bảo chất lượng phân tích đồng nhất giữa các lần, không phụ thuộc vào việc ai đó có nhớ hỏi đủ câu hay không.

Lưu Trữ và Chia Sẻ Prompt Library

Đừng để prompt template sống trong tin nhắn Slack cá nhân hoặc note riêng của một người. Cách làm bền vững: tạo một thư mục prompts/ (hoặc prompt-library/) ngay trong repo của team QA, mỗi file là một template cho một loại sự cố (prompts/connection-pool-exhausted.md, prompts/timeout-cascade.md, prompts/rounding-error.md...), và quản lý qua git như mọi tài sản kỹ thuật khác. Điều này mang lại ba lợi ích: có version history (lịch sử phiên bản) để biết template đã thay đổi ra sao, có thể review qua pull request khi ai đó cải tiến template, và bất kỳ ai join team cũng tìm được prompt đúng ngay trong repo mà không cần hỏi người cũ.

qa-tooling/
  prompts/
    connection-pool-exhausted.md
    timeout-cascade-multi-service.md
    rounding-error-currency.md
    race-condition-checkout.md
    environment-config-drift.md
    ci-failure-triage.md
    README.md   <- mô tả khi nào dùng template nào

Cải Tiến Chất Lượng Prompt Qua Thời Gian

Một prompt template không nên đứng yên. Cách cải tiến hiệu quả nhất là làm "retro" (rút kinh nghiệm) ngắn sau mỗi lần phân tích AI cho ra kết quả sai hoặc thiếu — ghi lại chính xác template nào đã dùng, bằng chứng nào đã đưa, và AI đã sai ở đâu. Theo thời gian, những retro nhỏ này tích lũy thành các cải tiến rất cụ thể cho template, thay vì "cảm giác chung là AI đôi khi không chính xác".

Ví dụ thực tế về một vòng lặp cải tiến, sau khi template timeout-cascade-multi-service.md cho ra một phân tích sai:

Prompt Template Used

(Bản v1 của template timeout-cascade-multi-service.md)

System Context: 3 service api-gateway -> payments-service -> ledger DB, timeout
gateway = 30s.

Analysis Request: "So sánh log 3 service, xác định service nào gây timeout, và
đề xuất nguyên nhân gốc."

Evidence That Was Provided

[api-gateway] 09:14:02.113 forwarded request
[api-gateway] 09:14:32.501 ERROR upstream timeout after 30388ms
[payments-service] 14:14:02.140 charge.received
[payments-service] 14:14:32.005 ERROR charge.failed after 29865ms

What Went Wrong in the Analysis

AI kết luận: "Có sự lệch pha lớn giữa api-gateway (log ở 09:14) và payments-service
(log ở 14:14) — có khả năng đây là 2 sự cố không liên quan xảy ra cách nhau 5 giờ,
cần điều tra riêng."

Đây là kết luận SAI. Lý do thật: api-gateway log theo UTC, còn payments-service
log theo giờ local (UTC+5, không ghi rõ trong log). AI không được thông báo về sự
khác biệt timezone giữa 2 service nên đã hiểu nhầm 2 log cùng một request là 2 sự
cố cách nhau 5 giờ.

Sửa template: thêm vào System Context (fixed) một dòng bắt buộc:
"api-gateway log theo UTC. payments-service log theo UTC+5 (Asia/Jakarta local
time) — LUÔN chuẩn hóa timestamp về UTC trước khi so sánh giữa 2 service này."

Sau khi thêm dòng này vào template v2, AI tự động quy đổi giờ trước khi so sánh
và dựng lại đúng timeline một-sự-cố như ở phần correlation phía trên.

Đây chính xác là lý do prompt library cần được xem như tài sản sống: lỗi timezone này chỉ cần bị bắt và sửa MỘT LẦN ở cấp template, và từ đó mọi phân tích tương lai dùng template timeout-cascade-multi-service.md sẽ tự động tránh được cái bẫy tương tự — thay vì mỗi QA Engineer trong team tự học lại bài học đó một cách đau đớn qua từng sự cố riêng lẻ.

Mẹo: Mỗi khi phát hiện AI phân tích sai vì thiếu một thông tin ngầm định (timezone, đơn vị đo, quy ước tên field...), đừng chỉ sửa ở lần phân tích đó — luôn hỏi "thông tin này có nên nằm cố định trong System Context của template không?" và cập nhật template ngay, để cả team không lặp lại cùng lỗi.