Tôi nhận ra là làm devops nên log mọi thứ

Hồi mới đi làm (nói đúng ra là lúc từ Dev chuyển sang DevOps hơn), Tôi mắc phải thói quen chạy scripts chay, tức là gõ bash deploy.sh, ngồi nhìn process. Xong. Tắt terminal. Một lúc sau, hệ thống ngỏm. Tôi mở lại terminal, cuộn lên… mất sạch. Log đâu? Bằng chứng đâu?

Nói với sếp kiểu: “Ơ, lúc nãy em chạy nó… xanh mà”. Chắc anh em trải qua thấy quen =)))

Cũng không thể phủ nhận được lúc đó mình non, tư duy hệ thống chưa có. Chỉ biết “triển khai xong là được”. Và rõ là không phải cách làm việc chuyên nghiệp. Trách nhiệm cốt lõi của DevOps/SRE là đảm bảo observability. Nên lúc đó tôi mới biết anh em chạy một lệnh, anh em phải để lại log.

Nên tính ra anh em nào học mà biết những mindset như vậy thì cũng tiết kiệm được kha khá sai lầm đấy vì đa số sai lầm đều đã được mắc rồi. Và đến đây thì nhiều anh em có thể đã biết dựng stack để logging và monitoring là ổn thôi, nhưng bài viết này dành cho các bạn chưa đụng nhiều hệ thống nhé : D

f8d6bdc7-72ce-467e-b5f0-1c0801421f4b

Sai lầm khi tin vào output trên màn hình

Nhiều anh em biết mấy cái cơ bản:

  • command > output.log: Chuyển hướng stdout, ghi đè file.
  • command >> output.log: Chuyển hướng stdout, thêm vào file.

Cái này ổn, nhưng nó có một vấn đề là Lệnh chạy 10 phút, màn hình terminal đứng im 10 phút. Anh em không biết nó đang làm gì, nó sống hay chết, nó chạy đến đâu. Chúng ta cần cả hai: Vừa xem được output ngay lập tức, vừa phải lưu lại bằng chứng trong file log.

The tee Command

Đây là lúc cái lệnh tee vào việc. Cái tên nó nói lên tất cả, nó như cái “ống nước hình chữ T” trong đời thực. Nó nhận input vào (từ stdin) và chia dòng chảy đó ra làm hai:

  1. Một dòng chảy thẳng ra màn hình (stdout).
  2. Một dòng chảy rẽ vào file log mà anh em chỉ định.

Cú pháp cơ bản: Anh em không chạy your-command nữa, mà anh em pipe | nó qua tee:

# Thay vì chạy: ls -la
# Hãy chạy:
ls -la | tee file_list.txt

Lúc này, anh em vừa thấy kết quả ls -la trên màn hình, vừa có một file file_list.txt y hệt.

Nhưng cũng như > , tee mặc định là ghi đè. Chạy lần nữa là mất log cũ. Trong thực tế, 99% thời gian chúng ta muốn ghi nối. Dùng cờ -a :

# Ping Google, vừa xem vừa lưu log
ping 8.8.8.8 | tee -a network.log

Gotcha!

Tôi nghĩ đây mới là thứ mà nhiều người vấp ngã.

Anh em thử chạy một lệnh cố ý lỗi xem:

ls /thu_muc/khong_he/ton_tai | tee -a app.log

Anh em sẽ thấy.

  • Trên màn hình: Văng ra lỗi ls: cannot access '/thu_muc/khong_he/ton_tai': No such file or directory.
  • Trong file app.log: Mở ra xem. Trắng trơn, không một dấu vết!

Vì trong thế giới Unix, một process nó show bằng hai loại:

  1. stdout (standard output, file descriptor 1): Loại 1 show bình thường, báo cáo thành công, in ra kết quả.
  2. stderr (standard error, file descriptor 2): Loại 2 chuyên… la làng, báo lỗi, cảnh báo.

Cái dấu pipe | và thằng tee mặc định nó chỉ hứng show stdout (dòng 1) thôi. Còn lỗi (stderr, dòng 2) nó văng thẳng ra màn hình và… bay hơi.

Đây là sai lầm khi viết script automation hay pipeline CI/CD. Chúng ta cần log cả lỗi lẫn thành công. Log mà không có lỗi thì khác gì báo cáo láo chứ.

2>&1 | tee -a

Để đảm bảo khả năng quan sát toàn diện, việc ghi lại cả hai là điều quan trọng.

your-command 2>&1 | tee -a your_log_file.log

Phân tích 2>&1 này:

  • 2>: Lấy dòng stderr (file descriptor số 2).
  • &1: Chuyển hướng đến cùng nơi với stdout (file descriptor số 1).

Nói nôm na là Gom tất cả lỗi (2) đổ chung vào dòng output chuẩn (1).

Sau khi gom chung lại, chúng ta pipe | rồi đẩy qua tee -a. Giờ thì tee hứng được tất cả.

Thử lại lệnh lỗi lúc nãy:

ls /thu_muc/khong_he/ton_tai 2>&1 | tee -a app.log

Bây giờ thì lỗi vừa hiện ra màn hình, vừa nằm gọn trong app.log. Đây mới gọi là logging có trách nhiệm.

Đừng bao giờ chạy script chay

Khi anh em viết script automation (deploy, backup, sync…) đừng bao giờ chạy nó một mình. Hãy gom nó lại.

Ví dụ Anti-Pattern: Anh em có một file crontab chạy backup mỗi đêm: 0 1 * * * /opt/scripts/backup_db.sh Script backup_db.sh này mà lỗi, anh em “ngủ” tới sáng, không ai biết gì.

Best Practice : Hãy bọc lệnh gọi script đó bằng 2>&1| tee -a.

Tôi viết lại một ví dụ script mới dùng để gọi wrapper script và ghi log đúng chuẩn:

#!/bin/bash
# file: /opt/automation/run_daily_sync.sh
# Script này tự log các bước thực thi của nó

logfile="/var/log/automation/sync.log"

# Đảm bảo thư mục log tồn tại
mkdir -p "$(dirname "$logfile")"

# Dùng { ... } để group các command
# Mọi output (stdout + stderr) của khối này sẽ được xử lý
{
  echo "=== SYNC JOB STARTED: $(date) ==="

  echo "Step 1: Syncing user data from API..."
  # Giả lập lệnh chạy script Python
  /usr/bin/python3 /opt/scripts/sync_users.py

  echo "Step 2: Syncing product catalog (legacy)..."
  # Giả lập lệnh chạy script PHP
  /usr/bin/php /var/www/html/artisan sync:products

  echo "Step 3: Running a fake error command (để test log lỗi)..."
  # Lệnh này chắc chắn lỗi
  ls /path/that/does/not/exist

  echo "=== SYNC JOB FINISHED: $(date) ==="
  echo "" # Thêm dòng trắng cho dễ đọc

} 2>&1 | tee -a "$logfile"
  1. Script này bọc toàn bộ logic chính (Step 1, 2, 3) vào trong một khối { ... }.
  2. Nó áp dụng công thức 2>&1| tee -a "$logfile" cho cả khối.
  3. Bất kể sync_users.py (Step 1) hay artisan sync:products (Step 2) thành công, chúng ta đều thấy và đều lưu lại được.
  4. Quan trọng nhất: Khi ls (Step 3) văng lỗi, lỗi đó được 2>&1 gom lại và tee ghi vào file log. Chúng ta không bỏ sót bất cứ thứ gì.

Thông tin nổi bật

Báo cáo quan trọng

Chia sẻ bài viết:
Theo dõi
Thông báo của
0 Góp ý
Được bỏ phiếu nhiều nhất
Mới nhất Cũ nhất
Phản hồi nội tuyến
Xem tất cả bình luận