背景与挑战
在项目开发中,我们项目中使用了多个SDK。每个SDK都使用自己的日志格式和输出方式,导致日志分散、格式不一致,给问题排查和日志分析带来很大困难。 正是面对这样的痛点,我决定设计一个统一的日志SDK,让所有SDK都能以一致的格式和方式输出日志。
设计目标
- 保证所有 SDK 的日志格式、输出方式、等级划分一致。
- 日志模块与具体业务解耦,业务不应该关心日志具体输出到哪里(控制条/文件/网络),只关心"记录"这个行为。
- 每条日志都包含丰富的上下文,包括但不限于:SDK名称、时间戳、模块名等信息。
- 支持全局配置、命名空间级别配置,优先级为:命名空间 > 全局配置。
- 能够根据不同环境(开发/生产)动态调整日志级别。
实践方案
使用方式
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级别的日志将被忽略。
配置优先级
配置的优先级从高到低为:
- 命名空间级别配置(通过getLogger第三个参数或configure的namespaceLevels设置)
- 全局配置(通过configure的level设置)
这种设计既保证了灵活性,又提供了合理的默认值。
架构设计
采用分层架构设计,主要包含以下几个核心组件:
-
LogManager(日志管理器): 作为单例模式存在,负责全局配置;通过 getLogger 方法工厂化创建 Logger 实例,根据命名空间进行管理;控制日志级别检查和分发。
phpclass LogManager { // 配置日志管理器 configure(config: LogManagerConfig) {} // 获取或创建Logger实例 getLogger(namespace: string, context: LoggerContext = {}, level: keyof LevelNames = DEFAULT_CONFIG.level): Logger {} } // 创建单例 const instance = new LogManager(); export default instance;
-
Logger(日志记录器): 提供不同级别的日志记录方法(error、warn、info、debug、trace)。
phpexport class Logger { // 各级别日志方法 error(message: string, meta: LoggerContext = {}): void {} // ... 其他级别方法 }
-
Transport(传输器): 负责日志的实际输出目标;可扩展多种输出方式,如控制台、文件、网络等。
-
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日志管理问题,希望这个方案能给你带来一些启发。