0
0
Lập trình
Admin Team
Admin Teamtechmely

Một API Để Quản Lý Tất Cả Logs Trong Dự Án

Đăng vào 7 tháng trước

• 5 phút đọc

Một API Để Quản Lý Tất Cả Logs Trong Dự Án Lớn

Giới Thiệu: Sự Khó Khăn Của Các API Phân Tán

Trong các dự án phần mềm có nhiều đội ngũ và chu kỳ phát triển dài, vấn đề phân tán của các API thường xuyên xảy ra. Mỗi đội, hoặc thậm chí mỗi lập trình viên, có thể chọn các thư viện hoặc cách tiếp cận khác nhau cho những nhiệm vụ chung như logging, xử lý lỗi, yêu cầu HTTP, v.v. Điều này không chỉ dẫn đến sự không đồng nhất, mà còn làm tăng độ phức tạp trong việc bảo trì, gây khó khăn trong việc đào tạo thành viên mới và cuối cùng, làm chậm tiến độ phát triển.

Hãy tưởng tượng một kịch bản trong đó việc ghi log ở frontend sử dụng console.log hoặc một thư viện cụ thể, trong khi backend lại sử dụng một thư viện khác, có thể với các cấp độ và định dạng khác nhau. Nếu bạn cần tập trung log, hoặc thay đổi cách triển khai bên dưới, bạn sẽ phải đối mặt với một nhiệm vụ khổng lồ.

Giải pháp nằm ở sự thống nhất. Tạo ra một "Một API Để Quản Lý Tất Cả" cho một số chức năng quan trọng có thể biến đổi cách mà các đội tương tác với hệ thống.


Trường Hợp Nghiên Cứu: Thống Nhất Hệ Thống Logs

Hệ thống logs là một ứng cử viên hoàn hảo cho sự thống nhất. Nó xuyên suốt mọi phần của ứng dụng của chúng ta, cho dù là ở phía khách (trình duyệt, React Native) hay ở phía server (Node.js).

Vấn Đề Khi Không Có Sự Thống Nhất

  • Không Đồng Nhất: console.log, console.warn, debug, pino, winston, log4js. Mỗi loại có cú pháp và khả năng riêng.
  • Bảo Trì: Nếu chúng ta thay đổi hệ thống logs (ví dụ, từ file system sang cloud service), chúng ta phải sửa đổi mã ở nhiều nơi và nhiều nền tảng.
  • Đào Tạo: Các lập trình viên mới phải học nhiều công cụ ghi log khác nhau.
  • Tập Trung: Khó khăn trong việc hợp nhất và phân tích logs từ nhiều nguồn với định dạng khác nhau.

Giải Pháp: Một Giao Diện Thống Nhất

Chúng ta có thể tạo ra một API ghi log đơn giản và độc lập với cách triển khai. Mục tiêu là các lập trình viên chỉ cần học một giao diện duy nhất.

typescript Copy
// src/infrastructure/log/index.ts

export type LogLevel = "info" | "warn" | "error";

export interface LoggerInterface
  extends Record<LogLevel, (message: string) => void> {
  info(message: string, context?: Record<string, unknown>): void;
  warn(message: string, context?: Record<string, unknown>): void;
  error(
    message: string,
    error?: Error,
    context?: Record<string, unknown>
  ): void;
}

export const log: LoggerInterface =
  typeof document !== "undefined"
    ? (await import("./client")).BrowserLogger
    : (await import("./server")).ServerLogger;

Lưu ý: Các môi trường như Bun, Cloudflare, Deno định nghĩa một đối tượng window tương tự như trong trình duyệt, mặc dù không chạy trong trình duyệt web. Điều này giúp duy trì tính tương thích với mã JavaScript.

Với giao diện này, bất kỳ phần nào của mã chúng ta, cho dù ở phía khách hay phía server, chỉ cần nhập log và sử dụng các phương thức của nó.


Sức Mạnh Của Các Bundler: Các Triển Khai Cụ Thể Cho Mỗi Môi Trường

Sự đẹp đẽ của kiến trúc này được tối đa hóa với các bundler hiện đại như TurbopackVite. Những công cụ này cho phép chúng ta có nhiều triển khai cho cùng một giao diện, và bundler sẽ đóng gói chỉ mã liên quan cho môi trường cuối cùng (khách hay server).

Điều này đạt được nhờ các kỹ thuật như Tree Shaking hay loại bỏ mã chết. Khi bundler xử lý mã cho khách, nó nhận ra rằng triển khai của logger cho server không bao giờ được sử dụng và do đó, loại bỏ hoàn toàn khỏi bundle cuối cùng, dẫn đến một gói nhỏ hơn và tối ưu hơn. Điều tương tự cũng xảy ra ở phía server, nơi mà triển khai của khách bị loại bỏ.

Triển Khai Cho Trình Duyệt (Khách):

typescript Copy
// src/infrastructure/log/browserLogger.ts
"client only";

import type { LoggerInterface, LogLevel } from "./";

const formatMessage = (
  level: LogLevel,
  message: string,
  context?: Record<string, unknown>
) =>
  `[${level.toUpperCase()}] ${message} ${context ? JSON.stringify(context) : ""}`;

export const BrowserLogger: LoggerInterface = {
  info(message: string, context?: Record<string, unknown>): void {
    console.info(formatMessage("info", message, context));
    // Triển khai Sentry/Datadog/Bugsnag
  },
  warn(message: string, context?: Record<string, unknown>): void {
    console.warn(formatMessage("warn", message, context));
    // Triển khai Sentry/Datadog/Bugsnag
  },
  error(
    message: string,
    error?: Error,
    context?: Record<string, unknown>
  ): void {
    console.error(formatMessage("error", message, context), error);
    // Triển khai Sentry/Datadog/Bugsnag
  },
};

Triển Khai Cho Server (Node.js):

Tại đây, chúng ta có thể sử dụng các thư viện mạnh mẽ như pino hoặc winston.

typescript Copy
// src/infrastructure/log/server.ts
"server only";

import type { LoggerInterface } from "./log";
import pino from "pino";

const pinoLogger = pino({
  level: process.env.LOG_LEVEL || "info",
  transport: {
    target: "pino-pretty",
    options: {
      colorize: true,
    },
  },
});

export const ServerLogger: LoggerInterface = {
  info(message: string, context?: Record<string, unknown>): void {
    pinoLogger.info(context, message);
  },
  warn(message: string, context?: Record<string, unknown>): void {
    pinoLogger.warn(context, message);
  },
  error(
    message: string,
    error?: Error,
    context?: Record<string, unknown>
  ): void {
    pinoLogger.error({ err: error, ...context }, message);
  },
};

Lợi Ích Rõ Ràng Của Sự Thống Nhất

  1. Tính Nhất Quán: Mã nguồn có hình thức và hành vi tương tự trên toàn bộ dự án.
  2. Bảo Trì Đơn Giản Hơn: Các thay đổi trong việc triển khai một API được thực hiện ở một nơi duy nhất.
  3. Dễ Dàng Kiểm Tra: Các giao diện rõ ràng giúp việc tạo mocks và kiểm tra đơn vị dễ dàng hơn.
  4. Giảm Đường Cong Học Tập: Các lập trình viên mới chỉ cần học API thống nhất, không phải nhiều công cụ khác nhau.
  5. Khả Năng Tái Sử Dụng Mã: Logic nghiệp vụ có thể độc lập với nền tảng.
  6. Tách Biệt: Mã sử dụng API được tách biệt khỏi việc triển khai cụ thể của nó.

Kết Luận

Áp dụng mô hình "Một API Để Quản Lý Tất Cả" cho các chức năng xuyên suốt là một khoản đầu tư mang lại lợi ích lâu dài trong các dự án phức tạp. Nó giảm ma sát, cải thiện chất lượng mã và, nhờ vào khả năng tối ưu của các bundler như ViteWebpack/Turbopack, cho phép chúng ta duy trì mã sạch và thống nhất mà không làm giảm hiệu suất. Bắt đầu với hệ thống logs là một bước đầu tiên tuyệt vời để chứng minh giá trị của chiến lược này.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào