【实录】多 SDK 日志乱象的解决方案:统一日志 SDK 设计分享

背景与挑战

在项目开发中,我们项目中使用了多个SDK。每个SDK都使用自己的日志格式和输出方式,导致日志分散、格式不一致,给问题排查和日志分析带来很大困难。 正是面对这样的痛点,我决定设计一个统一的日志SDK,让所有SDK都能以一致的格式和方式输出日志。


设计目标

  1. 保证所有 SDK 的日志格式、输出方式、等级划分一致。
  2. 日志模块与具体业务解耦,业务不应该关心日志具体输出到哪里(控制条/文件/网络),只关心"记录"这个行为。
  3. 每条日志都包含丰富的上下文,包括但不限于:SDK名称、时间戳、模块名等信息。
  4. 支持全局配置、命名空间级别配置,优先级为:命名空间 > 全局配置。
  5. 能够根据不同环境(开发/生产)动态调整日志级别。

实践方案

使用方式

javascript 复制代码
// 在应用层(业务代码)使用
import { configure, ConsoleTransport, RemoteTransport, JsonFormatter, LEVELS } from 'common-logger';
​
// 全局配置
configure({
  level: LEVELS.INFO, // 全局日志级别
  namespaceLevels: {
    'IM': LEVELS.INFO, // 命名空间日志级别,可以在这里设置,也可以实例化时设置;优先级高于全局
  },
  transports: [ // 日志输出目标
     new ConsoleTransport(new JsonFormatter()) // 输出目标到控制台,格式化为 JSON
  ]
});
​
// 在 SDK 中使用
import { getLogger } from 'common-logger';
​
class IMService {
  constructor() {
    // 创建日志记录器,并设置日志级别为 DEBUG
    this.logger = getLogger('IM', { version: '2.0.0' }, LEVELS.DEBUG);
  }
​
  connect() {
    this.logger.info('Connecting to server', { server: 'im.example.com' });
    try {
      // 连接逻辑
      this.logger.debug('Connection established');
    } catch (error) {
      this.logger.error('Connection failed', { error: error.message });
    }
  }
}

核心设计理念

日志级别管理

我们定义了五级日志级别(从高到低):ERROR、WARN、INFO、DEBUG、TRACE,默认级别为INFO。日志系统只会记录大于等于配置级别的日志,例如配置为INFO级别时,DEBUG和TRACE级别的日志将被忽略。


配置优先级

配置的优先级从高到低为:

  1. 命名空间级别配置(通过getLogger第三个参数或configure的namespaceLevels设置)
  2. 全局配置(通过configure的level设置)

这种设计既保证了灵活性,又提供了合理的默认值。


架构设计

采用分层架构设计,主要包含以下几个核心组件:

  1. LogManager(日志管理器): 作为单例模式存在,负责全局配置;通过 getLogger 方法工厂化创建 Logger 实例,根据命名空间进行管理;控制日志级别检查和分发。

    php 复制代码
    class LogManager {
       // 配置日志管理器
       configure(config: LogManagerConfig) {}
       
       // 获取或创建Logger实例
       getLogger(namespace: string, context: LoggerContext = {}, level: keyof LevelNames = DEFAULT_CONFIG.level): Logger {}
    }
    ​
    // 创建单例
    const instance = new LogManager();
    export default instance;
  2. Logger(日志记录器): 提供不同级别的日志记录方法(error、warn、info、debug、trace)。

    php 复制代码
    export class Logger {
       // 各级别日志方法
       error(message: string, meta: LoggerContext = {}): void {}
       // ... 其他级别方法
    }
  3. Transport(传输器): 负责日志的实际输出目标;可扩展多种输出方式,如控制台、文件、网络等。

  4. Formatter(格式化器): 负责日志的格式化,将日志数据转换为字符串;可以根据不同需求定制日志格式。


目录结构

perl 复制代码
common-logger/
├── src/
│   ├── index.ts                 # 入口文件,暴露 API
│   ├── logger.ts                # Logger 类,提供日志记录接口
│   ├── log-manager.ts           # 核心管理类,单例,负责配置和分发
│   ├── constants.ts             # 常量定义(日志级别等)
│   ├── formatters/              # 各种格式化器实现
│   │   ├── formatter.ts
│   │   ├── json-formatter.ts
│   ├── transports/              # 各种输出目标实现
│   │   ├── transport.ts
│   │   ├── console-transport.ts
│   │   ├── remote-transport.ts
│   ├── utils/                   # 工具函数
│   │   └── index.ts
├── package.json
└── README.md

总结与思考

通过这个统一的日志SDK,我们成功解决了多SDK日志管理的问题。关键在于将日志的记录与具体实现解耦,提供了灵活的配置方式和丰富的上下文信息。

在实际开发过程中,我发现良好的抽象和合理的默认值配置非常重要。既不能让使用者感到过于复杂,又要提供足够的灵活性来满足各种使用场景。

这个设计不仅适用于当前项目,也可以作为独立的npm包发布,供其他项目使用。如果你也面临类似的多SDK日志管理问题,希望这个方案能给你带来一些启发。

相关推荐
白袜队今年挖矿机2 小时前
Spring事务基础概念
前端
Samsong2 小时前
JavaScript逆向之对称加密算法
javascript·逆向
一枚前端小能手2 小时前
🛡️ Token莫名其妙就泄露了?JWT安全陷阱防不胜防
前端·javascript·安全
杰哥有只羊2 小时前
微信小程序-名片生成
前端
薛定谔的算法2 小时前
Vue.js 条件渲染与列表渲染详解:原理、用法与最佳实践
前端·vue.js·前端框架
_前端小李_2 小时前
关于预检请求
前端
李游Leo2 小时前
JavaScript事件机制与性能优化:防抖 / 节流 / 事件委托 / Passive Event Listeners 全解析
开发语言·javascript·性能优化
复苏季风2 小时前
Vue3 小白的疑惑:为什么用 const 定义的变量还能改?
前端·javascript·vue.js
扉川川2 小时前
File和Blob对象的区别
javascript