Nhật ký ứng dụng bằng Logging

Khi ứng dụng Backend chạy trong môi trường thực tế (Production), bạn không thể theo dõi lỗi bằng cách mở cửa sổ Terminal của máy cá nhân. Nếu hệ thống gặp sự cố, cách duy nhất để chẩn đoán và khắc phục là xem lại Nhật ký (Logs) của ứng dụng. NestJS cung cấp sẵn một lớp Logger cực kỳ mạnh mẽ để in nhật ký có màu sắc, định dạng rõ ràng kèm theo ngữ cảnh (Context) cụ thể của từng dòng code. Tài liệu này hướng dẫn cách sử dụng Logger và cách ra lệnh cho Claude Code ghi log hệ thống tối ưu.

1. Tại sao không nên dùng console.log()?

Nhiều bạn mới học lập trình thường có thói quen dùng console.log() để in thông tin lỗi ra màn hình. Tuy nhiên, trong dự án thực tế, đây là thói quen xấu vì:
  • Thiếu ngữ cảnh: Không biết dòng log đó xuất phát từ file nào, module nào.
  • Không phân chia cấp độ lỗi: Tất cả log đều hiển thị giống nhau, không phân biệt được đâu là thông tin thông thường (INFO), cảnh báo (WARN) hay lỗi nghiêm trọng (ERROR).
  • Khó tắt/bật: Không thể tắt bớt log ở chế độ sản xuất để giảm dung lượng lưu trữ.

2. Các cấp độ Log (Log Levels) trong NestJS

Lớp Logger của NestJS chia nhật ký thành 5 cấp độ quan trọng giảm dần:
Cấp độ LogPhương thứcMục đích sử dụng
errorlogger.error()Lỗi nghiêm trọng làm gián đoạn tính năng (ví dụ: mất kết nối DB, API bên thứ ba bị sập).
warnlogger.warn()Cảnh báo hành vi bất thường nhưng chưa làm sập ứng dụng (ví dụ: thử đăng nhập sai nhiều lần).
loglogger.log()Thông tin thông thường về tiến trình hoạt động (ví dụ: Khởi động server thành công, bắt đầu đồng bộ dữ liệu).
debuglogger.debug()Thông tin chi tiết phục vụ quá trình dò lỗi trong lúc lập trình (ví dụ: biến X đang nhận giá trị Y).
verboselogger.verbose()Nhật ký chi tiết nhất của toàn bộ luồng chạy (ít khi dùng).

3. Cách sử dụng Logger tích hợp trong NestJS

Để sử dụng, bạn chỉ cần tạo một thực thể (instance) của Logger và truyền tên Class hiện tại vào để định danh ngữ cảnh (Context).
src/products/products.service.ts
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Product } from './entities/product.entity';

@Injectable()
export class ProductsService {
  // Tạo Logger riêng cho ProductsService để dễ phân loại log
  private readonly logger = new Logger(ProductsService.name);

  constructor(
    @InjectRepository(Product)
    private readonly productRepository: Repository<Product>,
  ) {}

  async findOne(id: number) {
    this.logger.log(`Bắt đầu truy vấn tìm kiếm sản phẩm ID #${id}`);

    const product = await this.productRepository.findOneBy({ id });
    
    if (!product) {
      // Ghi log cảnh báo kèm thông tin cụ thể
      this.logger.warn(`Không tìm thấy sản phẩm có ID #${id} trong cơ sở dữ liệu`);
      throw new NotFoundException(`Sản phẩm #${id} không tồn tại`);
    }

    return product;
  }

  async deleteProduct(id: number) {
    try {
      const product = await this.findOne(id);
      await this.productRepository.remove(product);
      this.logger.log(`Xóa thành công sản phẩm ID #${id}`);
    } catch (error) {
      // Ghi log lỗi nghiêm trọng kèm stack trace để dò lỗi
      this.logger.error(
        `Thất bại khi xóa sản phẩm ID #${id}. Chi tiết lỗi: ${error.message}`,
        error.stack
      );
      throw error;
    }
  }
}
Nhật ký in ra màn hình terminal sẽ có định dạng trực quan:
[Nest] 23456  - 03/06/2026, 08:30:15 AM     LOG [ProductsService] Bắt đầu truy vấn tìm kiếm sản phẩm ID #12
[Nest] 23456  - 03/06/2026, 08:30:16 AM    WARN [ProductsService] Không tìm thấy sản phẩm có ID #12 trong cơ sở dữ liệu

4. Hướng dẫn viết Prompt cho Claude Code tích hợp Logging

Hãy luôn yêu cầu Claude Code thêm các khối xử lý lỗi try-catch và ghi log rõ ràng ở những hàm thực thi nghiệp vụ quan trọng.

Prompt mẫu tích hợp Logger tự động:

Hãy hoàn thiện hàm 'processPayment' trong file 'src/payments/payments.service.ts' sử dụng TypeORM:
1. Hãy tạo một Logger cá nhân hóa với ngữ cảnh (context) là 'PaymentsService'.
2. Khi bắt đầu hàm, ghi log ở cấp độ 'log' (INFO) thông báo: "Bắt đầu xử lý thanh toán cho đơn hàng #id".
3. Đặt logic thanh toán vào trong khối 'try-catch':
   - Nếu thanh toán thành công, ghi log: "Thanh toán thành công đơn hàng #id, mã giao dịch: transaction_code".
   - Nếu xảy ra lỗi (ví dụ: tài khoản không đủ số dư), hãy ghi log ở cấp độ 'error' kèm thông báo lỗi của hệ thống và stack trace để phục vụ việc debug. Sau đó, ném ra lỗi 'BadRequestException' cho Client.