# 技术文档:Python 日志记录模块 ```markdown # MyLogger 模块技术文档 ## 概述 本模块提供一个轻量级、线程安全(基于单例模式)的日志记录工具,支持多级别日志输出,并可将日志写入文件或标准输出。该模块通过 `inspect` 模块自动获取调用上下文信息(如文件名、行号),并结合时间戳生成结构化日志。 --- ## 依赖项 - `sys`: 用于访问标准输出流。 - `codecs`: 提供带编码支持的文件读写功能(UTF-8)。 - `traceback.format_exc`: 用于捕获和格式化异常堆栈信息。 - `appPublic.timeUtils.timestampstr`: 返回当前时间的时间戳字符串(格式依赖具体实现)。 - `appPublic.Singleton.SingletonDecorator`: 单例装饰器,确保 `MyLogger` 类为单例模式。 - `inspect`: 获取运行时调用栈信息,用于提取调用者文件名和行号。 > ⚠️ 注意:`appPublic.*` 是自定义公共库,请确保其已正确安装并可导入。 --- ## 核心类:`MyLogger` ### 装饰器 ```python @SingletonDecorator class MyLogger: ``` 使用 `SingletonDecorator` 确保每个 `name` 对应唯一的 `MyLogger` 实例(注意:当前实现中未按 name 区分实例,可能存在设计缺陷 —— 见【注意事项】)。 --- ### 属性说明 | 属性 | 类型 | 描述 | |------|------|------| | `levels` | `dict` | 定义日志级别及其优先级数值(数字越大,优先级越高)。 | | `formater` | `str` | 日志输出格式模板,兼容 `%` 格式化语法。 | #### 日志级别说明(按优先级从高到低) | 级别名称 | 数值 | 用途 | |---------|-----|------| | `clientinfo` | 7 | 客户端相关信息,最高优先级 | | `info` | 6 | 常规信息提示 | | `debug` | 5 | 调试信息 | | `warning` | 4 | 警告信息 | | `error` | 3 | 错误信息 | | `exception` | 2 | 异常堆栈信息 | | `critical` | 1 | 致命错误,最低优先级 | > 输出规则:仅当日志条目的级别 ≤ 当前 logger 的 `level` 时才会被记录。 --- ### 构造函数:`__init__(self, name, levelname='debug', logfile=None)` #### 参数 | 参数 | 类型 | 默认值 | 描述 | |------|------|--------|------| | `name` | `str` | 必填 | 日志记录器名称,用于标识来源 | | `levelname` | `str` | `'debug'` | 初始日志级别,决定哪些消息会被输出 | | `logfile` | `str or None` | `None` | 日志文件路径;若为 `None`,则输出到 `sys.stdout` | #### 初始化行为 - 设置 `self.name`, `self.levelname`, `self.level`(对应数值) - 设置 `self.logfile` 和初始 `self.logger = None` --- ### 方法说明 #### `open_logger(self)` 打开日志目标: - 若指定了 `logfile`,以追加模式 (`a`) 打开文件,使用 UTF-8 编码。 - 否则,使用 `sys.stdout` 作为输出流。 > 使用 `codecs.open` 确保 Unicode 写入安全。 #### `close_logger(self)` 关闭已打开的日志文件句柄(如果存在),并将 `self.logger` 设为 `None`。 > ❗ 存在 bug:最后一行重复赋值 `self.logger = None` 多余且无意义。 #### `log(self, levelname, message, frame_info)` 核心日志写入方法。 ##### 参数 | 参数 | 类型 | 描述 | |------|------|------| | `levelname` | `str` | 要记录的日志级别 | | `message` | `str` | 日志内容 | | `frame_info` | `frame object` | 调用栈帧对象,来自 `inspect.currentframe()` | ##### 行为流程 1. 获取调用者的栈帧 → 提取 `filename` 和 `lineno` 2. 查询 `levelname` 对应的等级值 3. 如果该等级高于当前 logger 允许的最高等级(即 `level > self.level`),则跳过输出 4. 构造日志数据字典 `data` 5. 调用 `open_logger()` 打开输出 6. 使用 `formater % data` 格式化日志字符串 7. 写入并刷新缓冲区 8. 调用 `close_logger()` 关闭资源 > ✅ 特点:每次写日志都临时打开/关闭文件,适合低频日志场景,避免长期占用句柄。 --- ## 辅助函数(全局接口) 提供简洁的日志调用方式,封装了 `MyLogger` 的创建与调用过程。 | 函数 | 等效操作 | 示例 | |------|----------|-------| | `clientinfo(message)` | `logger.log('clientinfo', message, ...)` | `clientinfo("用户登录")` | | `info(message)` | `logger.log('info', message, ...)` | `info("任务开始执行")` | | `debug(message)` | `logger.log('debug', message, ...)` | `debug("变量x值为: " + str(x))` | | `warning(message)` | `logger.log('warning', message, ...)` | `warning("配置项缺失")` | | `error(message)` | `logger.log('error', message, ...)` | `error("数据库连接失败")` | | `critical(message)` | `logger.log('critical', message, ...)` | `critical("系统即将退出")` | | `exception(message)` | 记录异常堆栈 + 自定义消息 | `exception("处理请求出错")` | > 📌 `exception()` 特殊处理:自动附加 `traceback.format_exc()` 的完整堆栈跟踪。 --- ## 使用示例 ```python from mylogger import info, debug, error, exception def main(): info("程序启动") debug("正在加载配置...") try: 1 / 0 except Exception: exception("发生数学错误") if __name__ == '__main__': main() ``` 输出示例(假设输出到控制台): ``` 2025-04-05 10:20:30.123[Test][info][main.py:5]程序启动 2025-04-05 10:20:30.124[Test][debug][main.py:6]正在加载配置... 2025-04-05 10:20:30.125[exception][exception][main.py:8]发生数学错误 Traceback (most recent call last): File "main.py", line 7, in main 1 / 0 ZeroDivisionError: division by zero ``` --- ## 配置说明 ### 日志格式化字符串 ```python '%(timestamp)s[%(name)s][%(levelname)s][%(filename)s:%(lineno)s]%(message)s\n' ``` 字段解释: | 字段 | 含义 | |------|------| | `%(timestamp)s` | 时间戳(由 `timestampstr()` 提供) | | `%(name)s` | Logger 名称 | | `%(levelname)s` | 日志级别名称 | | `%(filename)s` | 发生日志的源文件名 | | `%(lineno)s` | 源代码行号 | | `%(message)s` | 用户提供的日志消息 | --- ## 注意事项与改进建议 ### ⚠️ 已知问题 1. **单例逻辑可能失效** - `SingletonDecorator` 可能对所有 `MyLogger` 创建返回同一实例,无法区分不同 `name`。 - 建议改为基于 `name` 的单例缓存机制。 2. **`close_logger()` 中冗余赋值** ```python self.logger.close() self.logger = None self.logger = None # 重复 ``` 第二个赋值无效,应删除。 3. **性能考量** - 每次 `log()` 都打开/关闭文件,在高频日志场景下效率低下。 - 建议引入持久化文件句柄管理(如首次打开后保持打开状态)。 4. **缺乏线程安全性** - 尽管是“单例”,但未加锁,多线程同时写日志可能导致混乱。 - 如需多线程支持,建议添加 `threading.Lock`。 5. **硬编码 logger 名称** - 所有辅助函数中 `logger = MyLogger('Test')` 固定为 `'Test'`,不合理。 - 应允许传参或动态设置默认名称。 --- ## 总结 本模块实现了基本的日志功能,具备以下优点: ✅ 结构清晰 ✅ 支持自动定位调用位置 ✅ 支持文件/控制台双输出 ✅ 提供丰富的日志级别 ✅ 易于使用的全局函数接口 适用于小型项目或嵌入式脚本环境。对于大型应用,建议升级至标准库 `logging` 模块或增强此模块的功能与健壮性。 ```