7.4 KiB
7.4 KiB
技术文档:Python 日志记录模块
# 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() |
行为流程
- 获取调用者的栈帧 → 提取
filename和lineno - 查询
levelname对应的等级值 - 如果该等级高于当前 logger 允许的最高等级(即
level > self.level),则跳过输出 - 构造日志数据字典
data - 调用
open_logger()打开输出 - 使用
formater % data格式化日志字符串 - 写入并刷新缓冲区
- 调用
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()的完整堆栈跟踪。
使用示例
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
配置说明
日志格式化字符串
'%(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 |
用户提供的日志消息 |
注意事项与改进建议
⚠️ 已知问题
-
单例逻辑可能失效
SingletonDecorator可能对所有MyLogger创建返回同一实例,无法区分不同name。- 建议改为基于
name的单例缓存机制。
-
close_logger()中冗余赋值self.logger.close() self.logger = None self.logger = None # 重复第二个赋值无效,应删除。
-
性能考量
- 每次
log()都打开/关闭文件,在高频日志场景下效率低下。 - 建议引入持久化文件句柄管理(如首次打开后保持打开状态)。
- 每次
-
缺乏线程安全性
- 尽管是“单例”,但未加锁,多线程同时写日志可能导致混乱。
- 如需多线程支持,建议添加
threading.Lock。
-
硬编码 logger 名称
- 所有辅助函数中
logger = MyLogger('Test')固定为'Test',不合理。 - 应允许传参或动态设置默认名称。
- 所有辅助函数中
总结
本模块实现了基本的日志功能,具备以下优点:
✅ 结构清晰
✅ 支持自动定位调用位置
✅ 支持文件/控制台双输出
✅ 提供丰富的日志级别
✅ 易于使用的全局函数接口
适用于小型项目或嵌入式脚本环境。对于大型应用,建议升级至标准库 logging 模块或增强此模块的功能与健壮性。