☰ Mục lục series
Tài liệu NestJS tiếng Việt
  1. Nhập môn (Overview)
    1. Giới thiệu
    2. First steps
    3. Controllers
    4. Providers
    5. Modules
    6. Middleware
    7. Exception filters
    8. Pipes
    9. Guards
    10. Interceptors
    11. Custom decorators
  2. Nền tảng (Fundamentals)
    1. Custom providers
    2. Asynchronous providers
    3. Dynamic modules
    4. Injection scopes
    5. Circular dependency
    6. Module reference
    7. Lazy-loading modules
    8. Execution context
    9. Lifecycle events
    10. Discovery service
    11. Platform agnosticism
    12. Testing
  3. Kỹ thuật (Techniques)
    1. Configuration
    2. Database (SQL)
    3. MongoDB
    4. Validation
    5. Caching
    6. Serialization
    7. Versioning
    8. Task scheduling
    9. Queues
    10. Logging
    11. Cookies
    12. Events
    13. Compression
    14. File upload
    15. Streaming files
    16. HTTP module
    17. Session
    18. MVC
    19. Performance (Fastify)
    20. Server-Sent Events
  4. Bảo mật (Security)
    1. Authentication
    2. Authorization
    3. Encryption & Hashing
    4. Helmet
    5. CORS
    6. CSRF
    7. Rate limiting
    8. Vòng đời request
  5. OpenAPI (Swagger)
    1. Introduction
    2. Types and parameters
    3. Operations
    4. Security
    5. Mapped types
    6. Decorators
    7. CLI plugin
    8. Other features
  6. Recipes
    1. REPL
    2. CRUD generator
    3. SWC (fast compiler)
    4. Passport (auth)
    5. Hot reload
    6. MikroORM
    7. Router module
    8. Health checks (Terminus)
    9. CQRS
    10. Prisma
    11. Serve static
    12. Commander
    13. Async local storage
  7. WebSockets
    1. Gateways
    2. Exception filters
    3. Pipes
    4. Guards
    5. Interceptors
    6. Adapters
  8. Microservices
    1. Overview
    2. Redis
    3. MQTT
    4. NATS
    5. RabbitMQ
    6. Kafka
    7. gRPC
    8. Custom transporters
    9. Exception filters
    10. Pipes
    11. Pre-request hooks
    12. Guards
    13. Interceptors
  9. GraphQL
    1. Quick start
    2. Resolvers
    3. Mutations
    4. Subscriptions
    5. Scalars
    6. Directives
    7. Interfaces
    8. Unions and enums
    9. Field middleware
    10. Mapped types
    11. Plugins
    12. Complexity
    13. Extensions
    14. CLI plugin
    15. Generating SDL
    16. Sharing models
    17. Other features
    18. Federation
  10. CLI & triển khai
    1. CLI overview
    2. Workspaces
    3. Libraries
    4. Usage
    5. Scripts
    6. Deployment
    7. Standalone apps
  11. FAQ
    1. Serverless
    2. HTTP adapter
    3. Keep-Alive connections
    4. Global path prefix
    5. Raw body
    6. Hybrid application
    7. HTTPS & multiple servers
    8. Common errors
  12. Phụ lục
    1. Bảng thuật ngữ
Kỹ thuật (Techniques)

Logger

Bản dịch từ docs.nestjs.com/techniques/logger

Nest đi kèm một logger dạng text được xây dựng sẵn, dùng trong quá trình bootstrap ứng dụng và ở nhiều tình huống khác như hiển thị các exception bị bắt (tức là logging ở cấp hệ thống). Tính năng này được cung cấp thông qua class Logger trong package @nestjs/common. Bạn có thể kiểm soát hoàn toàn hành vi của hệ thống logging, bao gồm bất kỳ điều nào sau đây:

  • tắt logging hoàn toàn
  • chỉ định mức độ chi tiết của log (ví dụ: hiển thị lỗi, cảnh báo, thông tin debug, v.v.)
  • cấu hình định dạng của log message (raw, json, có màu, v.v.)
  • ghi đè timestamp trong logger mặc định (ví dụ: dùng chuẩn ISO8601 làm định dạng ngày)
  • ghi đè hoàn toàn logger mặc định
  • tùy biến logger mặc định bằng cách extend nó
  • tận dụng dependency injection để đơn giản hóa việc compose và test ứng dụng

Bạn cũng có thể tận dụng logger built-in, hoặc tự tạo implementation riêng, để log các event và message ở cấp ứng dụng của mình.

Nếu ứng dụng của bạn cần tích hợp với hệ thống logging bên ngoài, ghi log tự động ra file, hoặc chuyển tiếp log đến một dịch vụ logging tập trung, bạn có thể triển khai một giải pháp logging hoàn toàn tùy chỉnh bằng một thư viện logging của Node.js. Một lựa chọn phổ biến là Pino, nổi tiếng vì hiệu năng cao và sự linh hoạt.

Tùy biến cơ bản (Basic customization)

Để tắt logging, đặt thuộc tính logger thành false trong object options (tùy chọn) của ứng dụng Nest, được truyền làm tham số thứ hai cho phương thức NestFactory.create().

const app = await NestFactory.create(AppModule, {
  logger: false,
});
await app.listen(process.env.PORT ?? 3000);

Để bật các mức log cụ thể, đặt thuộc tính logger thành một mảng chuỗi chỉ định các mức log cần hiển thị, như sau:

const app = await NestFactory.create(AppModule, {
  logger: ['error', 'warn'],
});
await app.listen(process.env.PORT ?? 3000);

Các giá trị trong mảng có thể là bất kỳ tổ hợp nào của 'log', 'fatal', 'error', 'warn', 'debug', và 'verbose'.

Gợi ý Các mức log trong Nest có tính “cascading” (kế thừa lẫn nhau). Nghĩa là khi bạn chỉ định một mức log cụ thể (như 'log'), nó sẽ tự động bao gồm tất cả các mức nghiêm trọng hơn (ví dụ 'warn', 'error', và 'fatal').

Để tắt output có màu, truyền object ConsoleLogger với thuộc tính colors đặt thành false làm giá trị của thuộc tính logger.

const app = await NestFactory.create(AppModule, {
  logger: new ConsoleLogger({
    colors: false,
  }),
});

Để cấu hình một tiền tố (prefix) cho mỗi log message, truyền object ConsoleLogger với thuộc tính prefix được đặt:

const app = await NestFactory.create(AppModule, {
  logger: new ConsoleLogger({
    prefix: 'MyApp', // Mặc định là "Nest"
  }),
});

Ghi chú người dịch Nếu bạn từng dùng winston hay pino trực tiếp trong Express/Go, ConsoleLogger ở đây đóng vai trò tương tự “logger instance” bạn tự cấu hình rồi truyền qua middleware — khác biệt là Nest coi nó như một phần cấu hình bootstrap của cả app (NestFactory.create) chứ không phải một middleware riêng lẻ bạn tự app.use().

Dưới đây là tất cả các tùy chọn có sẵn, được liệt kê trong bảng bên dưới:

Tùy chọn Mô tả Mặc định
logLevels Các mức log được bật. ['log', 'fatal', 'error', 'warn', 'debug', 'verbose']
timestamp Nếu bật, sẽ in ra timestamp (khoảng chênh lệch thời gian) giữa log message hiện tại và log message trước đó. Lưu ý: tùy chọn này không được dùng khi json được bật. false
prefix Tiền tố được dùng cho mỗi log message. Lưu ý: tùy chọn này không được dùng khi json được bật. Nest
json Nếu bật, sẽ in log message theo định dạng JSON. false
colors Nếu bật, sẽ in log message có màu. Mặc định là true nếu json bị tắt, false nếu ngược lại. true
context Context (ngữ cảnh) của logger. undefined
compact Nếu bật, sẽ in log message trên một dòng duy nhất, kể cả khi đó là một object có nhiều thuộc tính. Nếu đặt thành một số, n phần tử con trong cùng gộp lại trên một dòng miễn là tất cả thuộc tính vừa với breakLength. Các phần tử array ngắn cũng được nhóm lại với nhau. true
maxArrayLength Chỉ định số lượng phần tử Array, TypedArray, Map, Set, WeakMap, và WeakSet tối đa được đưa vào khi format. Đặt thành null hoặc Infinity để hiển thị tất cả phần tử. Đặt thành 0 hoặc số âm để không hiển thị phần tử nào. Bị bỏ qua khi json được bật, màu bị tắt, và compact được đặt là true vì lúc đó nó tạo ra JSON output có thể parse được. 100
maxStringLength Chỉ định số ký tự tối đa được đưa vào khi format. Đặt thành null hoặc Infinity để hiển thị tất cả. Đặt thành 0 hoặc số âm để không hiển thị ký tự nào. Bị bỏ qua khi json được bật, màu bị tắt, và compact được đặt là true vì lúc đó nó tạo ra JSON output có thể parse được. 10000
sorted Nếu bật, sẽ sắp xếp các key khi format object. Cũng có thể là một hàm sắp xếp tùy chỉnh. Bị bỏ qua khi json được bật, màu bị tắt, và compact được đặt là true vì lúc đó nó tạo ra JSON output có thể parse được. false
depth Chỉ định số lần đệ quy khi format object. Hữu ích để inspect các object lớn. Để đệ quy đến giới hạn call stack tối đa, truyền Infinity hoặc null. Bị bỏ qua khi json được bật, màu bị tắt, và compact được đặt là true vì lúc đó nó tạo ra JSON output có thể parse được. 5
showHidden Nếu true, các symbol và thuộc tính non-enumerable của object cũng được đưa vào kết quả format. Các entry WeakMap và WeakSet cũng được đưa vào, cùng với các thuộc tính prototype do người dùng định nghĩa. false
breakLength Độ dài mà tại đó các giá trị đầu vào bị tách thành nhiều dòng. Đặt thành Infinity để format input trên một dòng duy nhất (kết hợp với “compact” đặt là true). Mặc định là Infinity khi “compact” là true, 80 nếu ngược lại. Bị bỏ qua khi json được bật, màu bị tắt, và compact được đặt là true vì lúc đó nó tạo ra JSON output có thể parse được. Infinity

Logging dạng JSON (JSON logging)

Logging dạng JSON rất quan trọng cho khả năng quan sát (observability) của ứng dụng hiện đại và cho việc tích hợp với các hệ thống quản lý log. Để bật JSON logging trong ứng dụng NestJS, cấu hình object ConsoleLogger với thuộc tính json đặt thành true. Sau đó, cung cấp cấu hình logger này làm giá trị cho thuộc tính logger khi tạo instance ứng dụng.

const app = await NestFactory.create(AppModule, {
  logger: new ConsoleLogger({
    json: true,
  }),
});

Cấu hình này xuất log theo định dạng JSON có cấu trúc, giúp việc tích hợp với các hệ thống bên ngoài như log aggregator và các nền tảng cloud dễ dàng hơn. Ví dụ, các nền tảng như AWS ECS (Elastic Container Service) hỗ trợ sẵn JSON log, cho phép các tính năng nâng cao như:

  • Lọc log (Log Filtering): Dễ dàng thu hẹp phạm vi log dựa trên các trường như log level, timestamp, hoặc metadata tùy chỉnh.
  • Tìm kiếm và phân tích (Search and Analysis): Dùng các công cụ truy vấn để phân tích và theo dõi xu hướng hành vi của ứng dụng.

Ngoài ra, nếu bạn đang dùng NestJS Mau, JSON logging giúp đơn giản hóa việc xem log theo định dạng có cấu trúc, gọn gàng, đặc biệt hữu ích cho việc debug và theo dõi hiệu năng.

Lưu ý Khi json được đặt thành true, ConsoleLogger sẽ tự động tắt việc tô màu text bằng cách đặt thuộc tính colors thành false. Điều này đảm bảo output vẫn là JSON hợp lệ, không lẫn các ký tự định dạng. Tuy nhiên, cho mục đích phát triển, bạn có thể ghi đè hành vi này bằng cách đặt tường minh colors thành true. Điều này sẽ thêm màu cho JSON log, giúp các entry dễ đọc hơn khi debug cục bộ.

Khi JSON logging được bật, log output sẽ trông như thế này (trên một dòng):

{
  "level": "log",
  "pid": 19096,
  "timestamp": 1607370779834,
  "message": "Starting Nest application...",
  "context": "NestFactory"
}

Bạn có thể xem các biến thể khác trong Pull Request này.

Structured logging params (tham số logging có cấu trúc)

Log message thường cần mang theo metadata - một user id, thời gian xử lý request, hoặc một correlation id. Kể từ NestJS v12, các plain object được truyền sau tham số message đầu tiên sẽ được coi là structured params và được gắn vào cùng một log entry, thay vì được phát ra thành các log record riêng biệt.

const logger = new Logger('UserService');
logger.log('User created', { userId: 1, email: '[email protected]' });

Ở chế độ text, các params được nối thêm inline vào cùng dòng đã được format:

[Nest] 3785  - 02/26/2026, 10:04:41 AM     LOG [UserService] User created { userId: 1, email: '[email protected]' }

Khi truyền nhiều plain object, chúng sẽ được gộp thành một tập params duy nhất:

logger.log('Request handled', { method: 'GET' }, { path: '/api', duration: 42 });
[Nest] 3785  - 02/26/2026, 10:04:41 AM     LOG [UserService] Request handled { method: 'GET', path: '/api', duration: 42 }

Ở chế độ JSON, mặc định các params được lồng dưới key params:

{
  "level": "log",
  "pid": 3785,
  "timestamp": 1772089691769,
  "message": "User created",
  "context": "UserService",
  "params": { "userId": 1 }
}

Nếu bạn muốn chúng được trải phẳng (spread) vào gốc của object JSON - điều mà một số log aggregator ưa thích hơn - hãy bật flattenParams:

new ConsoleLogger({ json: true, flattenParams: true });
{
  "level": "log",
  "pid": 3785,
  "timestamp": 1772089691769,
  "message": "User created",
  "context": "UserService",
  "userId": 1
}

Các tùy chọn ConsoleLogger liên quan là:

Tùy chọn Mô tả Mặc định
structuredParams Nếu bật, các plain object được log sau message sẽ được gắn vào cùng entry đó như params. true
flattenParams Nếu bật, params được trải phẳng vào gốc của JSON record thay vì lồng dưới params. Chỉ áp dụng cho chế độ JSON. false

Gợi ý Chỉ plain object mới được coi là params. Array, string, number, instance của class, và null vẫn được log như các message riêng biệt, và một plain object được truyền làm tham số đầu tiên vẫn được coi là chính message đó. Đặt structuredParams: false để khôi phục hành vi trước v12.

Ghi chú người dịch Cơ chế này khá giống cách các logger structured của Go (ví dụ zap, slog) hay pino trong Node xử lý: message chính tách biệt với các field key-value đi kèm, thay vì nhét hết vào một chuỗi string rồi tự parse lại bằng regex. Cái lợi thực tế là log aggregator (Datadog, ELK…) có thể query trực tiếp theo userId mà không cần parse text.

Sử dụng logger để log ở cấp ứng dụng (Using the logger for application logging)

Chúng ta có thể kết hợp nhiều kỹ thuật ở trên để có hành vi và định dạng nhất quán, cả cho việc logging hệ thống của Nest lẫn việc logging event/message ứng dụng của riêng chúng ta.

Một thực hành tốt là khởi tạo class Logger từ @nestjs/common trong mỗi service của chúng ta. Chúng ta có thể truyền tên service làm tham số context trong constructor của Logger, như sau:

import { Logger, Injectable } from '@nestjs/common';

@Injectable()
class MyService {
  private readonly logger = new Logger(MyService.name);

  doSomething() {
    this.logger.log('Doing something...');
  }
}

Trong implementation logger mặc định, context được in trong dấu ngoặc vuông, như NestFactory trong ví dụ bên dưới:

[Nest] 19096   - 12/08/2019, 7:12:59 AM   [NestFactory] Starting Nest application...

Nếu chúng ta cung cấp một logger tùy chỉnh thông qua app.useLogger(), nó sẽ thực sự được Nest sử dụng ở cấp nội bộ. Điều đó có nghĩa là code của chúng ta vẫn không phụ thuộc vào implementation cụ thể, trong khi ta có thể dễ dàng thay thế logger mặc định bằng logger tùy chỉnh của mình bằng cách gọi app.useLogger().

Theo cách đó, nếu ta làm theo các bước ở phần trước và gọi app.useLogger(app.get(MyLogger)), các lệnh gọi this.logger.log() sau đó từ MyService sẽ dẫn đến việc gọi phương thức log từ instance MyLogger.

Điều này phù hợp với hầu hết trường hợp. Nhưng nếu bạn cần tùy biến nhiều hơn (như thêm và gọi các phương thức tùy chỉnh), hãy chuyển sang phần tiếp theo.

Log kèm timestamp (Logs with timestamps)

Để bật logging kèm timestamp cho mọi message được log, bạn có thể dùng tùy chọn timestamp: true khi tạo instance logger.

import { Logger, Injectable } from '@nestjs/common';

@Injectable()
class MyService {
  private readonly logger = new Logger(MyService.name, { timestamp: true });

  doSomething() {
    this.logger.log('Doing something with timestamp here ->');
  }
}

Điều này sẽ tạo ra output theo định dạng sau:

[Nest] 19096   - 04/19/2024, 7:12:59 AM   [MyService] Doing something with timestamp here +5ms

Lưu ý +5ms ở cuối dòng. Với mỗi câu lệnh log, khoảng chênh lệch thời gian so với message trước đó được tính toán và hiển thị ở cuối dòng.

Implementation tùy chỉnh (Custom implementation)

Bạn có thể cung cấp một implementation logger tùy chỉnh để Nest sử dụng cho việc logging hệ thống, bằng cách đặt giá trị của thuộc tính logger thành một object thỏa mãn interface LoggerService. Ví dụ, bạn có thể bảo Nest dùng object console toàn cục có sẵn của JavaScript (vốn đã implement interface LoggerService), như sau:

const app = await NestFactory.create(AppModule, {
  logger: console,
});
await app.listen(process.env.PORT ?? 3000);

Việc tự implement logger của riêng bạn khá đơn giản. Chỉ cần implement từng phương thức của interface LoggerService như bên dưới.

import { LoggerService, Injectable } from '@nestjs/common';

@Injectable()
export class MyLogger implements LoggerService {
  /**
   * Write a 'log' level log.
   */
  log(message: any, ...optionalParams: any[]) {}

  /**
   * Write a 'fatal' level log.
   */
  fatal(message: any, ...optionalParams: any[]) {}

  /**
   * Write an 'error' level log.
   */
  error(message: any, ...optionalParams: any[]) {}

  /**
   * Write a 'warn' level log.
   */
  warn(message: any, ...optionalParams: any[]) {}

  /**
   * Write a 'debug' level log.
   */
  debug?(message: any, ...optionalParams: any[]) {}

  /**
   * Write a 'verbose' level log.
   */
  verbose?(message: any, ...optionalParams: any[]) {}
}

Sau đó bạn có thể cung cấp một instance của MyLogger thông qua thuộc tính logger trong object options của ứng dụng Nest.

const app = await NestFactory.create(AppModule, {
  logger: new MyLogger(),
});
await app.listen(process.env.PORT ?? 3000);

Kỹ thuật này, tuy đơn giản, nhưng không tận dụng dependency injection cho class MyLogger. Điều này có thể gây ra một số khó khăn, đặc biệt là cho việc testing, và hạn chế khả năng tái sử dụng của MyLogger. Để có giải pháp tốt hơn, xem phần Dependency Injection bên dưới.

Extend logger built-in (Extend built-in logger)

Thay vì viết một logger từ đầu, bạn có thể đáp ứng nhu cầu của mình bằng cách extend class ConsoleLogger built-in và ghi đè các hành vi được chọn của implementation mặc định.

import { ConsoleLogger } from '@nestjs/common';

export class MyLogger extends ConsoleLogger {
  error(message: any, stack?: string, context?: string) {
    // add your tailored logic here
    super.error(...arguments);
  }
}

Bạn có thể dùng logger đã extend như vậy trong các feature module của mình, như được mô tả trong phần Sử dụng logger để log ở cấp ứng dụng bên dưới.

Bạn có thể bảo Nest dùng logger đã extend của bạn cho việc logging hệ thống bằng cách truyền một instance của nó qua thuộc tính logger trong object options của ứng dụng (như đã trình bày trong phần Implementation tùy chỉnh ở trên), hoặc bằng kỹ thuật trong phần Dependency Injection bên dưới. Nếu làm vậy, bạn cần chú ý gọi super, như trong đoạn code mẫu ở trên, để chuyển tiếp lệnh gọi phương thức log cụ thể đến class cha (built-in), để Nest có thể dựa vào các tính năng built-in mà nó kỳ vọng.

Dependency injection

Để có chức năng logging nâng cao hơn, bạn sẽ muốn tận dụng dependency injection. Ví dụ, bạn có thể muốn inject một ConfigService vào logger của mình để tùy biến nó, rồi sau đó inject logger tùy chỉnh của bạn vào các controller và/hoặc provider khác. Để bật dependency injection cho logger tùy chỉnh của bạn, tạo một class implement LoggerService và đăng ký class đó như một provider trong một module nào đó. Ví dụ, bạn có thể

  1. Định nghĩa một class MyLogger extend logger ConsoleLogger built-in hoặc ghi đè hoàn toàn nó, như đã trình bày ở các phần trước. Nhớ implement interface LoggerService.
  2. Tạo một LoggerModule như bên dưới, và provide MyLogger từ module đó.
import { Module } from '@nestjs/common';
import { MyLogger } from './my-logger.service.js';

@Module({
  providers: [MyLogger],
  exports: [MyLogger],
})
export class LoggerModule {}

Với cấu trúc này, bạn đang cung cấp logger tùy chỉnh của mình để bất kỳ module nào khác cũng có thể sử dụng. Vì class MyLogger của bạn là một phần của module, nó có thể dùng dependency injection (ví dụ, để inject một ConfigService). Còn cần thêm một kỹ thuật nữa để cung cấp logger tùy chỉnh này cho Nest sử dụng trong việc logging hệ thống (ví dụ, cho bootstrapping và xử lý lỗi).

Vì việc khởi tạo ứng dụng (NestFactory.create()) diễn ra bên ngoài context của bất kỳ module nào, nó không tham gia vào giai đoạn Dependency Injection thông thường của quá trình khởi tạo. Vì vậy chúng ta phải đảm bảo có ít nhất một module ứng dụng import LoggerModule để kích hoạt Nest khởi tạo một instance singleton của class MyLogger của chúng ta.

Sau đó chúng ta có thể chỉ định Nest dùng cùng instance singleton đó của MyLogger bằng cấu trúc sau:

const app = await NestFactory.create(AppModule, {
  bufferLogs: true,
});
app.useLogger(app.get(MyLogger));
await app.listen(process.env.PORT ?? 3000);

Lưu ý Trong ví dụ trên, chúng ta đặt bufferLogs thành true để đảm bảo tất cả log sẽ được buffer (đệm lại) cho đến khi một logger tùy chỉnh được gắn vào (MyLogger trong trường hợp này) và quá trình khởi tạo ứng dụng hoàn tất hoặc thất bại. Nếu quá trình khởi tạo thất bại, Nest sẽ fallback về ConsoleLogger gốc để in ra các thông báo lỗi được báo cáo. Ngoài ra, bạn có thể đặt autoFlushLogs thành false (mặc định là true) để tự flush log theo cách thủ công (bằng phương thức Logger.flush()).

Ở đây chúng ta dùng phương thức get() trên instance NestApplication để lấy instance singleton của object MyLogger. Kỹ thuật này về cơ bản là một cách để “inject” một instance logger cho Nest sử dụng. Lệnh gọi app.get() lấy về instance singleton của MyLogger, và phụ thuộc vào việc instance đó đã được inject trước đó trong một module khác, như đã mô tả ở trên.

Bạn cũng có thể inject provider MyLogger này vào các class feature của mình, nhờ đó đảm bảo hành vi logging nhất quán giữa logging hệ thống của Nest và logging ứng dụng. Xem Sử dụng logger để log ở cấp ứng dụng và Inject một logger tùy chỉnh bên dưới để biết thêm thông tin.

Inject một logger tùy chỉnh (Injecting a custom logger)

Để bắt đầu, extend logger built-in với đoạn code như sau. Chúng ta cung cấp tùy chọn scope như metadata cấu hình cho class ConsoleLogger, chỉ định phạm vi transient để mỗi feature module có instance logger riêng của nó. Điều này quan trọng vì mỗi module đặt một context riêng cho logger của nó thông qua setContext() — nếu dùng singleton, lệnh gọi đó sẽ ghi đè context trên toàn bộ ứng dụng. Trong ví dụ này, chúng ta không extend từng phương thức riêng lẻ của ConsoleLogger (như log(), warn(), v.v.), dù bạn có thể chọn làm vậy.

import { Injectable, Scope, ConsoleLogger } from '@nestjs/common';

@Injectable({ scope: Scope.TRANSIENT })
export class MyLogger extends ConsoleLogger {
  customLog() {
    this.log('Please feed the cat!');
  }
}

Ghi chú người dịch Nếu bạn quen với Go, phạm vi Scope.TRANSIENT ở đây gần giống việc bạn tự new một struct logger mới mỗi lần cần, thay vì dùng chung một biến package-level — mỗi consumer nhận một instance độc lập, không lo bị ghi đè state (ở đây là context) bởi consumer khác. Đây là điểm khác biệt quan trọng so với provider mặc định của Nest, vốn là singleton dùng chung cho toàn app.

Tiếp theo, tạo một LoggerModule với cấu trúc như sau:

import { Module } from '@nestjs/common';
import { MyLogger } from './my-logger.service.js';

@Module({
  providers: [MyLogger],
  exports: [MyLogger],
})
export class LoggerModule {}

Tiếp theo, import LoggerModule vào feature module của bạn. Vì chúng ta đã extend Logger mặc định nên ta có sẵn tiện ích dùng phương thức setContext. Vì vậy ta có thể bắt đầu dùng logger tùy chỉnh biết về context, như sau:

import { Injectable } from '@nestjs/common';
import { MyLogger } from './my-logger.service.js';

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  constructor(private myLogger: MyLogger) {
    // Due to transient scope, CatsService has its own unique instance of MyLogger,
    // so setting context here will not affect other instances in other services
    this.myLogger.setContext('CatsService');
  }

  findAll(): Cat[] {
    // You can call all the default methods
    this.myLogger.warn('About to return cats!');
    // And your custom methods
    this.myLogger.customLog();
    return this.cats;
  }
}

Cuối cùng, chỉ định Nest dùng một instance của logger tùy chỉnh trong file main.ts của bạn như bên dưới. Tất nhiên trong ví dụ này, chúng ta chưa thực sự tùy biến hành vi của logger (bằng cách extend các phương thức của Logger như log(), warn(), v.v.), nên bước này thực ra không cần thiết. Nhưng nó sẽ cần thiết nếu bạn thêm logic tùy chỉnh vào các phương thức đó và muốn Nest dùng cùng implementation.

const app = await NestFactory.create(AppModule, {
  bufferLogs: true,
});
app.useLogger(new MyLogger());
await app.listen(process.env.PORT ?? 3000);

Gợi ý Ngoài việc đặt bufferLogs thành true, bạn cũng có thể tạm thời tắt logger bằng chỉ định logger: false. Lưu ý rằng nếu bạn cung cấp logger: false cho NestFactory.create, sẽ không có gì được log cho đến khi bạn gọi useLogger, nên bạn có thể bỏ lỡ một số lỗi khởi tạo quan trọng. Nếu bạn không ngại việc một số message ban đầu được log bằng logger mặc định, bạn có thể bỏ qua tùy chọn logger: false.

Liên kết log với request (Correlating logs with requests)

Tập trung log vào một chỗ giải quyết được vấn đề lưu trữ, chứ không giải quyết được vấn đề điều tra. Một khi mọi instance đều đẩy log về cùng một nơi, phần khó lại là tái dựng lại một request đơn lẻ giữa hàng ngàn dòng log đan xen nhau - đó là lý do vì sao phần lớn công việc debug production thực ra là việc nghĩ ra một correlation id, luồn nó qua mọi lệnh gọi log, rồi hy vọng không có chỗ nào trên đường đi quên truyền nó tiếp.

NestJS Observe loại bỏ phần việc sổ sách đó. Bật forwardLogs lên và mỗi dòng được ghi qua Logger của Nest sẽ được nắm bắt cùng với trace mà nó được ghi trong đó, đã gắn sẵn:

ObserveModule.forRoot({
  serviceId: 'orders-api',
  forwardLogs: true,
});

Bạn vẫn gọi this.logger.log() với một param orderId y hệt như trước - không cần sinh correlation id, không cần luồn một context object qua các tầng service. Trên một trang execution, log của request đó được đặt lên chính đồng hồ (clock) của trace, mỗi dòng nằm cạnh span đang chạy tại thời điểm nó được ghi, nên việc “cảnh báo retry nổ ra trước khi timeout, không phải sau” là thứ bạn có thể thấy trực tiếp thay vì phải suy luận từ timestamp giữa ba luồng log khác nhau.

Structured logging params cũng được giữ nguyên xuyên suốt, nên orderId vẫn là một trường có thể query được thay vì bị làm phẳng vào trong nội dung message. Các dòng log cũng có thể tự thiết lập cảnh báo (alert) - kiểu “báo cho tôi khi payment declined xuất hiện quá 10 lần trong 15 phút”.

Nếu bạn muốn giữ nội dung log trong aggregator của riêng mình, bạn không bắt buộc phải forward gì cả: khi tắt forwardLogs, SDK vẫn tăng cường ConsoleLogger để mỗi dòng mang theo trace id của nó, đủ để bạn nhảy từ một dòng trong stack hiện có sang trace đầy đủ trên dashboard. Xem tài liệu SDK để biết cả hai tùy chọn này và các thiết lập redaction (ẩn dữ liệu nhạy cảm) của chúng.

Ghi chú người dịch Đoạn này giới thiệu NestJS Observe — dịch vụ observability riêng của đội Nest (có gói Free và gói trả phí), không phải API core của framework - đọc kỹ sẽ thấy nó không thay thế phần “Custom implementation”/“Dependency injection” ở trên, mà chỉ là một lớp bổ sung để tự động gắn trace id vào log, tương tự cách bạn tích hợp OpenTelemetry hay Jaeger vào middleware Express thủ công.

Dùng logger bên ngoài (Use external logger)

Các ứng dụng production thường có yêu cầu logging đặc thù, bao gồm filtering nâng cao, định dạng, và logging tập trung. Logger built-in của Nest được dùng để theo dõi hành vi hệ thống của Nest, và cũng có thể hữu ích cho việc log text định dạng cơ bản trong các feature module của bạn khi đang phát triển, nhưng các ứng dụng production thường tận dụng các module logging chuyên dụng như Winston. Cũng như với bất kỳ ứng dụng Node.js tiêu chuẩn nào, bạn có thể tận dụng tối đa các module như vậy trong Nest.

Nội dung gốc: NestJS Documentation © Kamil Myśliwiec, giấy phép MIT. Bản dịch và ghi chú: Lê Văn Dũng.