Phong cách Thiết kế SDK JavaScript của Sentry 20.x: Kiến trúc API Thống nhất

SDK JavaScript của Sentry phiên bản 20.x áp dụng triết lý Unified API — một khuôn khổ thiết kế nhằm chuẩn hóa cách tương tác với hệ thống giám sát lỗi và hiệu năng trên mọi nền tảng. Mục tiêu cốt lõi là loại bỏ sự phân mảnh về thuật ngữ, hành vi và luồng dữ liệu giữa các SDK khác nhau, đồng thời mở rộng khả năng tích hợp mà không làm phức tạp giao diện người dùng.

Động lực đằng sau Unified API

Trước đây, mỗi SDK của Sentry (Python, Java, JavaScript, v.v.) phát triển độc lập theo nhu cầu cụ thể và bối cảnh kỹ thuật riêng. Hệ quả là:

  • Các khái niệm như "scope", "breadcrumb", hay "context" được triển khai với tên gọi, hành vi và phạm vi ảnh hưởng khác nhau.
  • Một số SDK chỉ tập trung vào báo cáo lỗi (explicit client-centric), khiến việc gắn kết các tính năng phụ trợ như theo dõi hành trình người dùng (breadcrumbs) hoặc gắn thẻ ngữ cảnh trở nên cồng kềnh hoặc không khả thi.
  • Tài liệu và hỗ trợ bị phân tán do thiếu sự nhất quán trong mô hình lập trình.

Nguyên tắc thiết kế then chốt

Unified API không chỉ là việc đổi tên hàm — đó là một hệ sinh thái được suy nghĩ lại từ gốc:

  • Tính nhất quán ngôn ngữ: Tất cả SDK đều sử dụng cùng bộ từ vựng (ví dụ: configureScope, addBreadcrumb, captureException) bất kể ngôn ngữ đích.
  • Tính mở rộng theo chiều dọc: Không chỉ báo lỗi — API hỗ trợ transaction monitoring, performance tracing, enrichment context, và event sampling ngay từ lớp trừu tượng cao nhất.
  • Quản lý ngữ cảnh thông minh: Mỗi luồng thực thi (hoặc execution context trong môi trường không đồng bộ như JavaScript) có một hub riêng, quản lý một ngăn xếp scope — đảm bảo dữ liệu ngữ cảnh không bị rò rỉ giữa các yêu cầu hoặc tác vụ.
  • Khả năng hoạt động khi chưa cấu hình: Các hàm như addBreadcrumb hoặc configureScope không gây lỗi nếu SDK chưa được khởi tạo (init()). Chúng trở thành no-op, cho phép thư viện thứ ba tích hợp an toàn mà không cần kiểm tra trạng thái SDK.
  • Tính đa nền tảng thực tế: API tránh phụ thuộc vào cơ chế đặc thù (ví dụ: thread-local storage trong JS), thay vào đó tận dụng các mô hình sẵn có (như AsyncLocalStorage trong Node.js hoặc Zone.js trong trình duyệt).

Các thành phần lõi

HUB — Trung tâm điều phối ngữ cảnh

Mỗi hub là một đơn vị quản lý độc lập gồm một client (đã cấu hình) và một ngăn xếp scope. Trong JavaScript, hub mặc định được gắn với AsyncLocalStorage để duy trì ngữ cảnh qua chuỗi lời gọi bất đồng bộ.

import { Hub } from '@sentry/core';

const customHub = new Hub(
  new Client({ dsn: 'https://xxx@sentry.io/123' }),
  new Scope()
);

// Chuyển hub hiện tại sang customHub tạm thời
Hub.run(customHub, () => {
  Sentry.captureMessage('This uses customHub');
});

SCOPE — Đóng gói dữ liệu đi kèm sự kiện

Scope chứa dữ liệu sẽ được tự động thêm vào mọi sự kiện gửi đi trong phạm vi đó: tags, contexts, user, level override, fingerprint, và cả các processor xử lý sự kiện.

Sentry.configureScope((scope) => {
  scope.setContext('game', {
    level: 42,
    mode: 'hardcore',
    lastCheckpoint: 'castle-dungeon'
  });

  scope.setTag('feature', 'multiplayer');
  scope.setUser({ id: 'player_789', email: 'hero@example.com' });
});

CLIENT — Đơn vị chịu trách nhiệm xây dựng và gửi sự kiện

Client là thành phần vô trạng thái, nhận đầu vào là sự kiện thô và scope, rồi áp dụng quy tắc chuyển đổi, lọc, và gửi qua transport. Nó không quản lý ngữ cảnh — đó là vai trò của hub.

class CustomTransport extends BaseTransport {
  async send(envelope) {
    const payload = serializeEnvelope(envelope);
    await fetch('https://custom-ingest.example.com/', {
      method: 'POST',
      body: payload,
      headers: { 'Content-Type': 'application/x-sentry-envelope' }
    });
  }
}

const client = new Client({
  dsn: 'https://xxx@sentry.io/123',
  transport: () => new CustomTransport()
});

Luồng xử lý sự kiện (Event Pipeline)

Khi gọi captureException hoặc captureEvent, sự kiện trải qua chuỗi xử lý tuần tự:

  1. Kiểm tra trạng thái SDK: Nếu không có client hoạt động → sự kiện bị loại bỏ ngay lập tức.
  2. Lấy mẫu (Sampling): Dựa trên tracesSampleRate hoặc sampleRate để quyết định giữ hay loại bỏ.
  3. Áp dụng scope: Gộp dữ liệu từ scope hiện tại (tags, contexts, user...) và chạy từng eventProcessor đã đăng ký.
  4. beforeSend hook: Cho phép sửa đổi hoặc hủy sự kiện trước khi gửi đi.
  5. Gửi qua transport: Đưa vào hàng đợi nội bộ; transport chịu trách nhiệm retry, rate limiting, và persistence nếu cần.

API tĩnh — Cửa ngõ dễ tiếp cận nhất

Các hàm cấp cao như Sentry.init(), Sentry.captureMessage(), hay Sentry.addBreadcrumb() đều là alias cho các phương thức tương ứng trên Hub::getCurrent(). Chúng che giấu độ phức tạp của hub và scope, nhưng vẫn đảm bảo tính đúng đắn trong môi trường bất đồng bộ.

// Tất cả đều tương đương với việc gọi trên hub hiện tại
Sentry.init({ dsn: 'https://...' });
Sentry.captureException(new Error('Boom!'));
Sentry.addBreadcrumb({ message: 'User clicked start', level: 'info' });

// Tương đương:
Hub.getCurrent().captureException(...);
Hub.getCurrent().addBreadcrumb(...);

Chế độ "minimal" — Tích hợp không ràng buộc

Gói @sentry/minimal cung cấp một lớp facade nhẹ, không phụ thuộc vào SDK đầy đủ. Khi không có SDK nào được cài đặt, mọi hàm đều trả về undefined hoặc không làm gì cả — giúp thư viện bên ngoài (ví dụ: một router hoặc logger) tích hợp breadcrumbs mà không lo crash hay phụ thuộc vòng.

// Trong thư viện thứ ba — không cần import SDK đầy đủ
import { addBreadcrumb } from '@sentry/minimal';

export function trackNavigation(to, from) {
  addBreadcrumb({
    category: 'navigation',
    message: `From ${from} → ${to}`,
    level: 'debug'
  });
}

Thẻ: sentry JavaScript unified-api hub scope

Đăng vào ngày 21 tháng 5 lúc 13:23