@nestjs/observe là gì? Cách NestJS giúp bạn nhìn thấy chuyện gì đang xảy ra trong production


@nestjs/observe là gì? Cách NestJS giúp bạn nhìn thấy chuyện gì đang xảy ra trong production

Khi làm backend, có một tình huống gần như developer nào cũng từng gặp:

Local chạy bình thường.

Staging chạy bình thường.

Deploy production.

User báo API chậm.

Bạn mở log lên.

Không thấy error.

Bạn kiểm tra database.

Database vẫn chạy.

CPU server cũng chưa quá cao.

Và câu hỏi lúc này là:

Vậy cuối cùng nó chậm ở đâu?

Đây chính là bài toán mà Observability muốn giải quyết.

Và NestJS hiện đã có một giải pháp chính thức cho việc này:

npm install @nestjs/observe

Tên của package đó là:

@nestjs/observe

NestJS mô tả đây là SDK observability chính thức, có khả năng tự động theo dõi HTTP request, GraphQL, microservices, BullMQ jobs, cron jobs, traces, metrics và nhiều thông tin runtime khác.


Trước tiên: Observability là gì?

Có thể hiểu rất đơn giản:

Observability là khả năng nhìn vào bên trong hệ thống để biết nó đang làm gì.

Ví dụ bạn có API:

POST /registrations

User báo:

Đăng ký mất 4 giây.

Nếu chỉ dùng log thông thường, bạn có thể thấy:

POST /registrations
Registration completed

Nhưng bạn vẫn chưa biết:

4 giây đó nằm ở đâu?

Có thể là:

Controller
   ↓
Service
   ↓
Check player
   ↓
Query database
   ↓
Create registration
   ↓
Send email
   ↓
Push BullMQ job

Một bước nào đó có thể mất rất nhiều thời gian.

Observability giúp bạn nhìn thấy cả luồng đó.

Ví dụ:

POST /registrations                 4.2s

RegistrationController.create       4.1s
   │
   ├── PlayerService.find            40ms
   │
   ├── RegistrationRepository.save  120ms
   │
   ├── InvoiceService.create        180ms
   │
   └── EmailService.send            3.7s

Nhìn vào đây, bạn gần như biết ngay vấn đề:

EmailService.send = 3.7 giây

Không cần đoán.


Vậy @nestjs/observe làm gì?

Điểm đặc biệt của @nestjs/observe là nó không chỉ nhìn Node.js ở mức thấp.

Nó hiểu cấu trúc của NestJS.

Ví dụ một hệ thống monitoring thông thường có thể chỉ cho bạn biết:

POST /registrations
SQL query
HTTP request

Trong khi NestJS Observe có thể hiểu những thành phần như:

RegistrationController

RegistrationService

Guards

Interceptors

Resolvers

Queue consumers

Cron jobs

NestJS cho biết SDK này hook trực tiếp vào request lifecycle của framework, vì vậy telemetry có thể được biểu diễn theo chính các controller, provider và queue handler bạn viết.

Đây là khác biệt khá quan trọng.

Ví dụ thay vì nhìn thấy:

POST /api/registration

bạn có thể nhìn hệ thống theo logic của application:

RegistrationController.create()

RegistrationService.registerPlayer()

PaymentService.createInvoice()

Đối với developer, kiểu thông tin này hữu ích hơn rất nhiều.


Ví dụ thực tế

Giả sử bạn có:

@Post()
async register(@Body() dto: CreateRegistrationDto) {
  return this.registrationService.create(dto);
}

Service:

async create(dto: CreateRegistrationDto) {
  const player = await this.playerService.find(dto.playerId);

  const registration = await this.registrationRepo.save({
    playerId: dto.playerId,
    eventId: dto.eventId,
  });

  await this.emailService.sendConfirmation(player);

  return registration;
}

Một ngày user báo:

Registration API hôm nay rất chậm.

Trước đây bạn có thể bắt đầu thêm log:

console.time('find-player');

const player = await this.playerService.find(dto.playerId);

console.timeEnd('find-player');

Sau đó:

console.time('save-registration');

const registration =
  await this.registrationRepo.save(...);

console.timeEnd('save-registration');

Rồi tiếp tục:

console.time('send-email');

await this.emailService.sendConfirmation(player);

console.timeEnd('send-email');

Code bắt đầu đầy những đoạn kiểu:

console.time()
console.log()
performance.now()

Và khi debug xong...

có khi bạn lại phải xóa chúng.


Với NestJS Observe

Ý tưởng là bạn instrument application một lần.

Ví dụ:

import { createObserveModule } from '@nestjs/observe';

export const {
  ObserveModule,
  ObserveInstrument,
} = createObserveModule();

Sau đó khai báo module:

@Module({
  imports: [
    ObserveModule.forRoot({
      appKey: process.env.OBSERVE_APP_KEY,
      appSecret: process.env.OBSERVE_APP_SECRET,
      serviceId: 'backend-api',
    }),
  ],
})
export class AppModule {}

Và ở bootstrap:

const app = await NestFactory.create(AppModule, {
  instrument: ObserveInstrument,
});

SDK yêu cầu NestJS core từ 11.1.4 trở lên, nên project NestJS 11 tương đối mới đã có thể tích hợp, không nhất thiết phải nâng lên NestJS 12 trước.


Sau đó Nest có thể tự theo dõi gì?

Một trong những điểm hấp dẫn nhất là auto instrumentation.

Nó có thể theo dõi các loại workload như:

HTTP request
GraphQL
Microservices
BullMQ
Cron
Interval
Timeout

Ngoài ra SDK còn thu thập:

traces
runtime metrics
custom metrics
CPU profiles
errors

Nếu project của bạn có:

@Cron(...)

hoặc:

@Processor('email')

thì đây là thứ khá hữu ích.


Trace là gì?

Nếu chỉ nhớ một khái niệm về observability, hãy nhớ trace.

Trace có thể hiểu là:

Nhật ký hành trình của một request.

Ví dụ:

User
 │
 │ POST /registrations
 ▼
RegistrationController
 │
 ▼
RegistrationService
 │
 ├── MySQL
 │
 ├── PaymentService
 │
 └── BullMQ
       │
       ▼
     EmailWorker

Toàn bộ hành trình đó có thể được coi là:

1 Trace

Bên trong trace có nhiều:

Span

Ví dụ:

Trace: POST /registrations

├── Controller                    10ms
├── RegistrationService          380ms
├── MySQL INSERT                  20ms
├── PaymentService              300ms
└── BullMQ enqueue               15ms

Mỗi đoạn là một span.


Nó giúp tìm API chậm như thế nào?

Giả sử API bình thường:

POST /registration

p50 = 120ms
p95 = 300ms
p99 = 600ms

Sau một release:

p50 = 150ms
p95 = 2.4s
p99 = 6.1s

Điều đó có nghĩa là phần lớn request vẫn tương đối ổn, nhưng một nhóm request bị chậm rất mạnh.

Bạn có thể mở một request chậm và nhìn waterfall:

RegistrationController.create
        │
        ├── PlayerService.find
        │      35ms
        │
        ├── RegistrationRepo.save
        │      40ms
        │
        └── PaymentGateway.create
               4.8s

NestJS docs mô tả chính workflow này: route được gom theo route pattern, sau đó có thể kiểm tra percentile như p95 và mở từng slow execution để xem waterfall.

Đây chính là điểm observability mạnh hơn việc chỉ đọc log.


Không chỉ HTTP API

Đây có lẽ là phần mình thấy hữu ích nhất với backend NestJS thực tế.

Giả sử bạn có BullMQ:

@Processor('email')
export class EmailProcessor {

  @Process()
  async send(job: Job) {
    // send email
  }
}

Một job đôi lúc chạy:

30 giây

Bạn có thể muốn biết:

job nào chậm?
service nào chậm?
external API nào chậm?

Observe có instrumentation cho BullMQ.


Tương tự với cron job:

@Cron('0 11 * * *')
async sendBusBookingDigest() {
  ...
}

Bạn có thể theo dõi:

Cron started

↓ 120ms

Query bookings

↓ 840ms

Generate report

↓ 2.4s

Send email

Cron completed

Thay vì chỉ biết:

Cron ran successfully.

Metrics là gì?

Trace giúp xem một request cụ thể.

Metrics giúp nhìn toàn bộ hệ thống.

Ví dụ:

Request per second

CPU usage

Memory usage

Event loop delay

Error rate

Response time

Bạn có thể có dashboard kiểu:

Requests

12:00    150 req/min
13:00    210 req/min
14:00    900 req/min

và:

Error rate

12:00    0.1%
13:00    0.2%
14:00    8.4%

Ngay lập tức bạn biết:

14:00 có chuyện gì đó xảy ra.

Logs, Metrics và Traces khác nhau thế nào?

Có thể nhớ như sau:

Logs
→ Chuyện gì đã xảy ra?

Metrics
→ Hệ thống đang khỏe hay không?

Traces
→ Request đã đi qua những đâu?

Ví dụ:

LOG

Payment failed

cho biết có lỗi.


METRIC

payment_error_rate = 12%

cho biết lỗi đang xảy ra nhiều.


TRACE

Registration
   ↓
PaymentService
   ↓
PayPal API
      timeout 5s

cho biết lỗi nằm ở đâu.

Ba thứ kết hợp lại mới tạo thành observability tốt.


Thế nó có giống Sentry không?

Có phần giống, nhưng trọng tâm khác nhau.

Sentry rất mạnh ở:

Exceptions

Stack traces

Error grouping

Release regression

Frontend errors

Còn observability/APM tập trung nhiều hơn vào:

Performance

Request tracing

Latency

Metrics

Infrastructure behavior

Distributed flows

Ví dụ:

API bị 500

Sentry rất hữu ích.

Nhưng:

API không lỗi,
chỉ từ 200ms tăng lên 4 giây.

Observability thường hữu ích hơn.

Trong thực tế hai thứ có thể tồn tại song song.


Điểm thú vị: không chạy collector trong request thread

Theo repository chính thức, telemetry được gửi thông qua một detached worker thread nhằm tránh làm request path chính phải chịu việc xử lý telemetry trực tiếp.

Có thể hình dung:

Request

Controller
↓
Service
↓
Database
↓
Response

không phải đợi:

send monitoring data

mỗi lần request hoàn thành.

Thay vào đó:

Application
     │
     └── telemetry
             ↓
         Worker Thread
             ↓
        Observe collector

Cách này giúp giảm ảnh hưởng của monitoring tới request chính.


Một điểm nữa khá hay: AsyncLocalStorage

Một vấn đề thường gặp trong NestJS là muốn theo dõi:

requestId

userId

traceId

tenantId

xuyên suốt nhiều service.

Ví dụ:

Controller
    ↓
Service A
    ↓
Service B
    ↓
Repository

NestJS Observe đã duy trì AsyncLocalStorage cho request, jobs và messages được instrument, đồng thời expose context đó thông qua TracerService.

Nghĩa là bạn có thể dùng trace context xuyên suốt call stack mà không cần tự viết toàn bộ middleware/context infrastructure.


Ví dụ một bug thực tế

Giả sử hệ thống có:

Parent registration

↓

Create registration

↓

Generate invoice

↓

Send email

User báo:

Một vài lần đăng ký mất gần 10 giây.

Log:

Registration created successfully.

Không có lỗi.

Database monitoring:

normal

CPU:

normal

Nhưng trace:

POST /registration           9.3s

├── ValidatePlayer           30ms
├── CreateRegistration       45ms
├── GenerateInvoice          80ms
└── SendConfirmationEmail    9.1s

Bây giờ vấn đề rất rõ:

Email provider chậm.

Bạn có thể quyết định đổi architecture:

Registration

↓

Save DB

↓

Push BullMQ job

↓

Return response

Sau đó:

BullMQ

↓

Send email

Response có thể giảm từ:

9.3s

xuống:

150ms

Đây là một ví dụ điển hình về giá trị của observability:

Không chỉ phát hiện lỗi, mà giúp developer hiểu hệ thống đang hoạt động như thế nào.


NestJS 11 có dùng được không?

Có.

Đây là điểm rất dễ hiểu nhầm vì native observability được nhấn mạnh như một tính năng của NestJS 12.

Nhưng SDK chính thức hiện yêu cầu:

@nestjs/core >= 11.1.4

Nếu sử dụng GraphQL thì yêu cầu phiên bản @nestjs/graphql phù hợp nữa.

Vì vậy nếu bạn đang chạy:

NestJS 11.2.x

thì hoàn toàn có thể thử.


Cài đặt

Cơ bản chỉ cần:

npm install @nestjs/observe

Tạo credentials từ NestJS Observe và cấu hình:

OBSERVE_APP_KEY=...
OBSERVE_APP_SECRET=...

Sau đó:

import { createObserveModule } from '@nestjs/observe';

export const {
  ObserveModule,
  ObserveInstrument,
} = createObserveModule();

Import:

@Module({
  imports: [
    ObserveModule.forRoot({
      appKey: process.env.OBSERVE_APP_KEY,
      appSecret: process.env.OBSERVE_APP_SECRET,
      serviceId: 'my-api',
    }),
  ],
})
export class AppModule {}

Bootstrap:

const app = await NestFactory.create(AppModule, {
  instrument: ObserveInstrument,
});

await app.listen(3000);

Sau đó application bắt đầu gửi telemetry.


Có miễn phí không?

Theo README hiện tại của dự án, NestJS Observe có free tier lên tới:

300,000 events / tháng

Đối với project cá nhân hoặc môi trường staging, con số này có thể đủ để thử nghiệm.

Tuy nhiên với production traffic lớn, nên kiểm tra pricing hiện tại trước khi triển khai rộng.


Khi nào nên dùng?

Nếu hệ thống chỉ có:

10 API
1 database
ít traffic

thì có thể:

logs + Sentry

đã đủ.

Nhưng khi architecture bắt đầu thành:

HTTP API
   │
   ├── MySQL
   ├── Redis
   ├── BullMQ
   ├── cron jobs
   ├── external APIs
   ├── email service
   └── WebSocket

thì việc debug bằng log bắt đầu rất khó.

Lúc này observability trở nên cực kỳ giá trị.


Kết luận

@nestjs/observe không phải một logger mới.

Nó cũng không đơn giản là:

console.log phiên bản xịn.

Có thể hiểu nó như:

Một camera quan sát bên trong ứng dụng NestJS.

Thay vì chỉ biết:

API chậm.

bạn có thể biết:

API chậm
→ ở RegistrationService
→ cụ thể EmailService
→ request tới email provider
→ mất 4.7 giây.

Thay vì:

Cron đôi khi lỗi.

bạn có thể nhìn:

Cron
→ query DB
→ generate report
→ send email
→ exception.

Và đó chính là giá trị lớn nhất của observability:

giảm thời gian developer phải đoán.

Khi hệ thống production ngày càng phức tạp, việc biết hệ thống lỗi chưa đủ.

Quan trọng hơn là biết:

Nó lỗi ở đâu?

Chậm ở đâu?

Bắt đầu từ khi nào?

Request nào bị ảnh hưởng?

Service nào gây ra vấn đề?

@nestjs/observe là cách NestJS đang đưa khả năng đó trở thành một phần native hơn của framework.

Đăng nhận xét

Mới hơn Cũ hơn