Python 日志脱敏的工程实现方式

8次阅读

必须脱敏密码、手机号、身份证号、银行卡号、邮箱地址、Token、密钥等字段;优先通过重写logrecordfactory及getmessage()实现结构化解析脱敏,handler级仅作临时补救,第三方库日志需单独配置。

Python 日志脱敏的工程实现方式

日志里哪些字段必须脱敏

密码、手机号、身份证号、银行卡号、邮箱地址、Token、密钥这些字段,只要出现在日志中,就属于高危明文,必须处理。不是“建议脱敏”,是“不脱敏就违规”。尤其在金融、政务、医疗类系统中,Logging.info() 里直接拼接 user.phonerequest.headers['Authorization'] 是典型雷区。

常见错误现象:WARNING:root:login failed for user 138****1234, pwd=123456 —— 这条日志同时泄露了脱敏不全的手机号和明文密码,审计一查一个准。

  • 优先识别业务实体中的敏感字段,比如 User 模型里的 id_cardbank_account
  • http 请求/响应体、Headers、Query Params 中的敏感键名(如 passwordaccess_token)要统一拦截
  • 避免只对固定字段名脱敏(比如只拦 password),有些接口pwdauth_keysecret 也能传密钥

用 LogRecordFactory 替换默认日志工厂

python 默认的 LogRecord 不会碰你传进去的 msg 字符串,它原样记录。想在写入前清洗内容,就得接管日志记录对象的生成过程——最稳妥的方式是重写 LogRecordFactory

为什么这样做:比在每个 logger.info() 前手动调用脱敏函数更可靠,也比用 Handler 的 emit() 做字符串替换更早、更彻底(避免敏感内容已进缓冲区或被其他 Handler 拦截)。

立即学习Python免费学习笔记(深入)”;

容易踩的坑:Logger.makeRecord() 是私有方法,不要重写它;也不要试图 monkey patch LogRecord.__init__,Python 3.12+ 已限制此类操作。

  • 继承 logging.LogRecord,重写 getMessage() 方法,在其中对 self.msgself.args 做结构化解析与脱敏
  • 注册新工厂:调用 logging.setLogRecordFactory(YourSanitizedRecord),且必须在所有 logger 初始化前执行
  • 注意 self.args 可能是 dict、list 或嵌套结构,不能只做 str.replace();推荐用 json.dumps() + 正则或递归遍历清洗

Handler 级脱敏只适合简单场景

如果项目已经上线、无法改日志工厂,或者只需要对特定输出渠道(比如只对文件日志脱敏,控制台保留原始)做处理,可以用自定义 Handleremit() 阶段清洗。

性能影响明显:每条日志都要做一次字符串解析 + 正则匹配,高频日志服务下 CPU 开销会上升 10%~30%;而且若日志已被格式化为字符串(比如用了 %(message)s),再做关键词替换极易误伤(如把 “password_reset” 里的 “password” 也替了)。

  • 仅适用于低频、非核心服务,或临时补救措施
  • 务必在 formatter.format() 后、stream.write() 前做脱敏,否则可能重复处理或漏处理
  • 正则模式至少加词边界:r'bpasswords*[:=]s*["']([^"']*)["']',避免匹配到单词片段

第三方库日志怎么管

requestssqlalchemydjango.db 这些库自己打的日志,不会走你的 LogRecordFactory,除非它们显式调用你配置的 logger 实例。默认它们用的是各自模块名下的 logger(如 requests.packages.urllib3),你得单独配置。

最容易被忽略的一点:SQLAlchemy 的 echo=True 打印的 SQL 日志,如果带参数(WHERE phone = '13812345678'),根本不在 Python logging 体系内,而是直接 print 到 stderr —— 这类必须关掉或用 engine.echo = False + 改用 before_cursor_execute 事件钩子捕获并脱敏。

  • logging.getLogger('requests') 单独获取并设置 handler / Filter
  • urllib3boto3 等底层库,优先设 level=logging.WARNING,避免 INFO 级别泄露请求头
  • Django 用户注意:LOGGING 配置里要显式包含 'django.db.backends' 并绑定脱敏 handler

事情说清了就结束。真正难的不是写脱敏逻辑,是穷举所有日志出口——包括你没意识到它在打日志的那些地方。

text=ZqhQzanResources