Python测试开发工具库:日志脱敏工具(敏感信息自动屏蔽)

目录

一、先搞懂:日志脱敏的核心需求与实现原理

[1.1 核心需求拆解](#1.1 核心需求拆解)

[1.2 实现原理](#1.2 实现原理)

二、工具设计思路:从易用性和扩展性出发

三、代码实现:可直接复用的完整脚本

[3.1 代码目录结构](#3.1 代码目录结构)

[3.2 配置文件:desensitize_config.json](#3.2 配置文件:desensitize_config.json)

[3.3 核心脱敏模块:desensitizer.py](#3.3 核心脱敏模块:desensitizer.py)

[3.4 日志集成模块:desensitize_handler.py](#3.4 日志集成模块:desensitize_handler.py)

[3.5 演示示例:demo.py](#3.5 演示示例:demo.py)

[3.6 运行效果](#3.6 运行效果)

四、实际应用场景演示

[4.1 接口测试日志脱敏](#4.1 接口测试日志脱敏)

[4.2 APP测试日志脱敏(Airtest集成)](#4.2 APP测试日志脱敏(Airtest集成))

五、使用注意事项

最后:工具的扩展方向


在测试开发日常工作中,日志是排查问题、跟踪流程的核心依据------接口请求参数、用户操作轨迹、数据库交互记录,都需要通过日志留存。但随之而来的问题是,这些日志里很容易夹杂敏感信息:用户的手机号、身份证号、银行卡号,甚至是数据库密码、接口Token。一旦这些日志被不当泄露,不仅可能违反数据安全相关规定,还会给用户和项目带来潜在风险。

所以,实现一套能自动识别并屏蔽敏感信息的日志脱敏工具,成了测试开发工作中的刚需。今天就带大家从零构建这个工具,涵盖从原理理解到实际落地的完整流程,最终产出可直接复用的Python 3.8.6脚本。

一、先搞懂:日志脱敏的核心需求与实现原理

1.1 核心需求拆解

在动手写代码前,先明确工具要解决的核心问题:

  • 能精准识别常见敏感信息:比如手机号(11位数字)、身份证号(18位,含X)、银行卡号(16-19位)、密码/Token(通常是长字符串或特定格式);

  • 脱敏规则可配置:不同项目的敏感信息类型可能不同,支持自定义添加/修改脱敏规则,不用改核心代码;

  • 不破坏原有日志结构:只替换敏感信息部分,保留日志的时间、级别、模块等原有字段;

  • 低侵入性:能无缝集成到Python logging模块,不用大幅修改现有日志打印逻辑。

1.2 实现原理

日志脱敏的核心逻辑很简单:**"匹配-替换"**两步走:

  1. 匹配:通过正则表达式,精准匹配日志文本中的敏感信息;

  2. 替换:将匹配到的敏感信息,用指定字符(比如*)进行部分或全部屏蔽,同时保留部分非敏感字符用于定位(比如手机号保留前3后4位)。

整个流程可以理解为:日志文本 → 正则匹配敏感信息 → 脱敏替换 → 输出脱敏后日志。

二、工具设计思路:从易用性和扩展性出发

结合实际工作场景,工具采用"配置化+模块化"的设计思路,拆解为3个核心模块:

  1. 配置模块:用配置文件存储脱敏规则(比如正则表达式、替换格式),支持动态修改,不用重启服务;

  2. 核心脱敏模块:封装脱敏逻辑,接收原始日志文本,应用配置的脱敏规则,返回脱敏后的文本;

  3. 日志集成模块:自定义Python logging的Handler或Filter,将脱敏逻辑嵌入日志输出流程,实现"打印即脱敏"。

这样设计的好处是:后续要新增敏感信息类型,只需修改配置文件;要适配不同的日志框架,只需调整集成模块,核心脱敏逻辑无需改动,扩展性更强。

三、代码实现:可直接复用的完整脚本

本次实现采用多文件结构,先给出整体目录,再逐一讲解各文件的功能和代码:

3.1 代码目录结构

python 复制代码
log_desensitizer/
├── config/
│   └── desensitize_config.json  # 脱敏规则配置文件
├── core/
│   └── desensitizer.py          # 核心脱敏逻辑
├── log_handler/
│   └── desensitize_handler.py   # 日志集成Handler
└── demo.py                      # 演示示例

3.2 配置文件:desensitize_config.json

用JSON文件存储脱敏规则,每个规则包含3个核心字段:

  • rule_name:规则名称(用于标识);

  • regex:匹配敏感信息的正则表达式;

  • replace:替换格式(用group分组保留部分字符,其余用*替换)。

python 复制代码
{
  "rules": [
    {
      "rule_name": "phone",
      "regex": "1[3-9]\\d{9}",
      "replace": "$1****$2"
    },
    {
      "rule_name": "id_card",
      "regex": "(\\d{6})(\\d{8})(\\d{4})",
      "replace": "$1********$3"
    },
    {
      "rule_name": "bank_card",
      "regex": "(\\d{4})(\\d{12,15})",
      "replace": "$1***********$2"
    },
    {
      "rule_name": "password",
      "regex": "password[:=]\\s*['\"]?([^'\"]+)['\"]?",
      "replace": "password=******"
    },
    {
      "rule_name": "token",
      "regex": "token[:=]\\s*['\"]?([a-zA-Z0-9]{32,})['\"]?",
      "replace": "token=******"
    }
  ]
}

说明:

  • 手机号正则:匹配11位手机号,替换后保留前3位和后4位(比如13800138000 → 138****8000);

  • 身份证号正则:匹配18位身份证号(含X),替换后保留前6位和后4位(比如110101199001011234 → 110101********1234);

  • 密码/Token正则:匹配"password=xxx""token=xxx"格式的字符串,直接全部替换为******。

3.3 核心脱敏模块:desensitizer.py

该模块负责加载配置文件、实现脱敏逻辑,提供一个对外的desensitize方法:

复制代码
python 复制代码
import json
import re
import os

class LogDesensitizer:
    def __init__(self):
        # 加载配置文件
        self.config_path = os.path.join(
            os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
            "config",
            "desensitize_config.json"
        )
        self.rules = self._load_config()
    
    def _load_config(self):
        """加载脱敏规则配置"""
        try:
            with open(self.config_path, "r", encoding="utf-8") as f:
                config = json.load(f)
                return config.get("rules", [])
        except Exception as e:
            print("加载脱敏配置文件失败:", str(e))
            return []
    
    def desensitize(self, text):
        """
        核心脱敏方法:对输入文本应用所有脱敏规则
        :param text: 原始文本(日志内容)
        :return: 脱敏后的文本
        """
        if not text or not self.rules:
            return text
        
        desensitized_text = text
        for rule in self.rules:
            regex = rule.get("regex")
            replace = rule.get("replace")
            if not regex or not replace:
                continue
            # 应用正则替换
            desensitized_text = re.sub(regex, replace, desensitized_text)
        return desensitized_text

# 单例模式,避免重复加载配置
desensitizer = LogDesensitizer()

说明:

  • 采用单例模式创建desensitizer实例,避免每次脱敏都重新加载配置文件,提升性能;

  • _load_config方法负责读取配置文件,若读取失败则返回空列表,不影响原有日志输出;

  • desensitize方法接收原始日志文本,遍历所有脱敏规则,依次应用正则替换,返回脱敏后的文本。

3.4 日志集成模块:desensitize_handler.py

自定义logging的Handler,将脱敏逻辑嵌入日志输出流程。这里继承StreamHandler(控制台输出Handler),重写emit方法:

复制代码
python 复制代码
import logging
from core.desensitizer import desensitizer

class DesensitizeStreamHandler(logging.StreamHandler):
    def emit(self, record):
        """
        重写emit方法:输出日志前先进行脱敏处理
        :param record: 日志记录对象
        """
        # 获取原始日志消息
        msg = self.format(record)
        # 脱敏处理
        desensitized_msg = desensitizer.desensitize(msg)
        # 替换record的msg为脱敏后的消息
        record.msg = desensitized_msg
        # 调用父类的emit方法输出日志
        super().emit(record)

说明:emit方法是logging输出日志的核心方法,我们在输出前对日志消息进行脱敏处理,再调用父类方法完成输出,实现"打印即脱敏"的效果。如果需要输出到文件,可类似继承FileHandler重写emit方法。

3.5 演示示例:demo.py

编写测试代码,验证工具的脱敏效果:

复制代码
python 复制代码
import logging
from log_handler.desensitize_handler import DesensitizeStreamHandler

def setup_logger():
    """配置日志器,集成脱敏Handler"""
    logger = logging.getLogger("test_logger")
    logger.setLevel(logging.DEBUG)
    
    # 移除默认Handler,避免重复输出
    logger.handlers.clear()
    
    # 添加脱敏Handler
    handler = DesensitizeStreamHandler()
    # 配置日志格式:时间-级别-模块-消息
    formatter = logging.Formatter(
        "%(asctime)s - %(levelname)s - %(module)s - %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S"
    )
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    
    return logger

if __name__ == "__main__":
    logger = setup_logger()
    
    # 模拟不同场景的日志输出,包含各类敏感信息
    logger.debug("用户登录:手机号13800138000,密码password=12345678")
    logger.info("用户实名认证:身份证号110101199001011234")
    logger.warning("接口请求:token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...,银行卡号6222021234567890123")
    logger.error("数据库连接失败:url=mysql://user:password=root123@localhost:3306/test")

3.6 运行效果

执行demo.py后,控制台输出的脱敏日志如下:

python 复制代码
2024-05-20 15:30:00 - DEBUG - demo - 用户登录:手机号138****8000,密码password=******
2024-05-20 15:30:00 - INFO - demo - 用户实名认证:身份证号110101********1234
2024-05-20 15:30:00 - WARNING - demo - 接口请求:token=******,银行卡号6222***********90123
2024-05-20 15:30:00 - ERROR - demo - 数据库连接失败:url=mysql://user:password=******@localhost:3306/test

可以看到,所有敏感信息都被成功屏蔽,同时保留了部分特征用于问题定位,符合预期效果。

四、实际应用场景演示

除了上面的基础演示,工具还能适配测试开发工作中的多种实际场景,这里举2个常见例子:

4.1 接口测试日志脱敏

在接口测试脚本中,打印请求参数和响应数据时,自动脱敏敏感信息:

复制代码
python 复制代码
import requests
from log_handler.desensitize_handler import DesensitizeStreamHandler
import logging

# 配置日志(集成脱敏Handler)
logger = logging.getLogger("api_test")
logger.setLevel(logging.INFO)
handler = DesensitizeStreamHandler()
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)

def test_user_info():
    url = "https://api.example.com/user/info"
    headers = {"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}
    data = {
        "phone": "13912345678",
        "id_card": "310101198001011234",
        "bank_card": "6217001234567890123"
    }
    
    logger.info("请求用户信息接口,请求参数:%s", data)
    response = requests.post(url, headers=headers, json=data)
    logger.info("接口响应数据:%s", response.json())

if __name__ == "__main__":
    test_user_info()

输出日志中,手机号、身份证号、Token等敏感信息会被自动脱敏,避免泄露。

4.2 APP测试日志脱敏(Airtest集成)

在Airtest的APP自动化测试中,集成脱敏工具,屏蔽日志中的用户敏感信息:

python 复制代码
from airtest.core.api import *
import logging
from log_handler.desensitize_handler import DesensitizeStreamHandler

# 配置Airtest日志,集成脱敏Handler
logger = logging.getLogger("airtest")
logger.setLevel(logging.INFO)
logger.handlers.clear()
handler = DesensitizeStreamHandler()
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)

def test_app_login():
    # 连接设备
    connect_device("Android:///")
    # 启动APP
    start_app("com.example.app")
    # 输入手机号和密码
    touch(Template("phone_input.png"))
    text("13700137000")
    touch(Template("password_input.png"))
    text("abc123456")
    logger.info("用户输入:手机号13700137000,密码abc123456")
    # 点击登录
    touch(Template("login_btn.png"))
    sleep(2)
    logger.info("登录操作完成")

if __name__ == "__main__":
    test_app_login()

Airtest运行日志中,输入的手机号和密码会被自动脱敏,保障数据安全。

五、使用注意事项

  • 正则表达式精准性:配置文件中的正则表达式需要精准匹配敏感信息,避免误脱敏。比如手机号正则要排除11位非手机号的数字串,可根据实际场景优化正则;

  • 配置文件更新:修改配置文件后,需要重启应用才能生效(若需要热更新,可在_desensitize方法中添加定时重新加载配置的逻辑);

  • 性能影响:对于日志量极大的场景,正则替换可能会影响性能。可通过限制脱敏规则数量、优化正则表达式(避免贪婪匹配)来提升性能;

  • 特殊格式敏感信息:若项目中有特殊格式的敏感信息(比如企业内部的员工编号),可在配置文件中新增对应的正则和替换规则;

  • 日志存储安全:脱敏后的日志仍需妥善存储,避免被未授权人员访问,同时保留脱敏记录,便于后续审计。

最后:工具的扩展方向

这个日志脱敏工具是基础版本,后续可根据实际需求扩展:

  • 支持更多配置格式:比如YAML配置文件,比JSON更易读;

  • 动态加载配置:通过接口或定时任务,实现配置的热更新,不用重启应用;

  • 脱敏等级配置:支持不同脱敏等级(比如部分屏蔽、全部屏蔽),根据日志用途动态切换;

  • 敏感信息统计:记录脱敏次数、敏感信息类型分布,生成统计报表,便于掌握日志安全状况。

相关推荐
唐叔在学习2 小时前
Python自动化指令进阶:UAC提权
后端·python
旺仔小拳头..2 小时前
Java ---变量、常量、类型转换、默认值、重载、标识符、输入输出、访问修饰符、泛型、迭代器
java·开发语言·python
wujj_whut2 小时前
【Conda实战】从0到1:虚拟环境创建、多Python版本管理与环境切换全指南
开发语言·python·conda
geoqiye3 小时前
2026官方认证:贵阳宠物行业短视频运营TOP5评测
大数据·python·宠物
龙腾AI白云3 小时前
AI智能体搭建(3)深度搜索智能体如何搭建与设计 Agent#智能体搭建#多智能体#VLA#大模型
python·django·virtualenv·scikit-learn·tornado
海棠AI实验室3 小时前
第十一章 错误处理体系:异常分层与可恢复策略
python·异常处理
love530love3 小时前
EPGF 新手教程 22教学模板不是压缩包:EPGF 如何设计“可复制、可检查、可回收”的课程模板?
ide·人工智能·windows·python·架构·pycharm·epgf
ai_top_trends4 小时前
不同 AI 生成 2026 年工作计划 PPT 的使用门槛对比
人工智能·python·powerpoint
老顾聊技术4 小时前
“Anthropic 最新发布的 AI Skills:赋能任务自动化与跨领域应用“
运维·人工智能·自动化