用 Python logging.Filter 实现手机号、邮箱与身份证号日志脱敏

举报
yd_246123679 发表于 2026/11/01 15:45:00 2026/11/01
【摘要】 日志中常混入手机号、邮箱和身份证号。本文用 Python logging.Filter 编写正则替换过滤器并挂到 Handler 上,给出可配置字段、最小可运行示例、运行方法与性能注意事项,帮助在落盘前完成脱敏。

用 Python logging.Filter 在日志落盘前脱敏手机号、邮箱和身份证号

问题场景

应用日志经常直接把用户输入、接口参数或异常上下文写入文件、转发到远端。手机号、邮箱、身份证号一旦落盘,后续排查、归档、共享都会扩大暴露面。比较稳妥的做法不是在每个 logger.info 前手写替换,而是在日志输出链路上加一层过滤器。Python 的 logging.Filter 可以挂在 Handler 上,在 Handler 真正写出日志之前修改 LogRecord。

需要先明确:过滤器适合处理“消息正文”。异常堆栈、extra 字段、Formatter 自定义字段需要额外处理。

实现思路

  • 为三类敏感信息预编译正则,避免每条日志重复编译。
  • 编写 SensitiveDataFilter(logging.Filter),在 filter(record) 中调用 record.getMessage() 得到最终消息文本,替换后写回 record.msg,并清空 record.args,防止 Formatter 再次拼接原始参数。
  • 只把过滤器挂到需要落盘的 Handler 上,例如 FileHandler;如果多个 Handler 都要输出,应分别挂载,或统一在 Formatter 层处理。
  • 规则以 (名称, 编译后的正则, 替换串) 列表传入,便于增删字段。

最小可运行示例

import logging
import re

# 规则:名称、预编译正则、替换模板
DEFAULT_RULES = [
    (
        'mobile',
        re.compile(r'(?<!\d)(1[3-9]\d)(\d{4})(\d{4})(?!\d)'),
        r'\1****\3',
    ),
    (
        'email',
        re.compile(
            r'(?<![A-Za-z0-9._%+-])([A-Za-z0-9._%+-])[A-Za-z0-9._%+-]*'
            r'@([A-Za-z0-9.-]+\.[A-Za-z]{2,})(?![A-Za-z0-9.-])'
        ),
        r'\1***@\2',
    ),
    (
        'id_card',
        re.compile(r'(?<!\d)([1-9]\d{5})(\d{4})(\d{2})(\d{2})(\d{3}[\dXx])(?!\d)'),
        r'\1********\5',
    ),
]


class SensitiveDataFilter(logging.Filter):
    def __init__(self, rules=None, name=''):
        super().__init__(name)
        self.rules = rules if rules is not None else DEFAULT_RULES

    def _sanitize(self, text):
        if not isinstance(text, str):
            return text
        for _, pattern, repl in self.rules:
            text = pattern.sub(repl, text)
        return text

    def filter(self, record):
        try:
            message = record.getMessage()
        except Exception:
            # 格式化失败时退回原始 msg,避免过滤器改变日志行为
            message = str(record.msg)
        record.msg = self._sanitize(message)
        record.args = ()
        return True


def build_logger(log_path='app.log'):
    logger = logging.getLogger('safe_app')
    logger.setLevel(logging.INFO)
    logger.propagate = False
    if logger.handlers:
        return logger
    handler = logging.FileHandler(log_path, encoding='utf-8')
    handler.setFormatter(
        logging.Formatter('%(asctime)s %(levelname)s %(message)s')
    )
    handler.addFilter(SensitiveDataFilter())
    logger.addHandler(handler)
    return logger


if __name__ == '__main__':
    log = build_logger('app.log')
    log.info('用户手机号 13000000000,邮箱 alice@example.com')
    log.info('身份证号 110101199001011234 已提交')
    log.info('参数:%s,手机号:%s', {'email': 'bob@example.com'}, '13000000000')

运行方法

把代码保存为 demo.py,执行:

python demo.py

然后查看当前目录下的 app.log,应看到类似内容:

... INFO 用户手机号 130****0000,邮箱 a***@example.com
... INFO 身份证号 110101********1234 已提交
... INFO 参数:{'email': 'b***@example.com'},手机号:130****0000

示例中的手机号、邮箱和身份证号均为占位值,不对应真实用户。

可配置字段

SensitiveDataFilter 接受 rules 参数。新增规则时可以继续传入预编译正则,例如为银行卡号增加一条掩码规则:

rules = DEFAULT_RULES + [
    ('bank_card', re.compile(r'(?<!\d)(\d{4})\d{8}(\d{4})(?!\d)'), r'\1********\2'),
]
handler.addFilter(SensitiveDataFilter(rules=rules))

如果某类字段不需要脱敏,从规则列表中移除对应项即可;如果不同 Handler 需要不同规则,可以分别创建过滤器实例。

边界与限制

  1. 只处理消息正文。exc_info 的异常文本通常由 Formatter 追加,不会被这个过滤器捕获;如果异常消息中含敏感数据,需要自定义 Formatter 或在异常输出前处理。
  2. extra 字段和 record.__dict__ 中的自定义字段不会自动脱敏。Formatter 若引用这些字段,应在 Formatter 层处理。
  3. 正则覆盖有限:不匹配带空格或短横线的号码、15 位旧身份证、国际手机号、非 ASCII 邮箱等。应按业务补充规则,同时避免过度匹配。
  4. 过滤器修改的是共享的 LogRecord。挂到某个 Handler 后,后续 Handler 看到的是脱敏后的消息;如果其他 Handler 依赖原始参数,需要复制记录或改用 Formatter。
  5. 性能方面,正则已经预编译;只挂到必要 Handler;避免在高频 DEBUG 日志上全量运行多条复杂正则。日志量大时应对实际消息长度和规则数量做压测,不要只看单条耗时。
  6. 脱敏不是加密。掩码后仍可能通过组合信息推断,合规要求高时还需结合访问控制、日志留存周期和加密存储。
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。