用 Python 给 JSONL 日志脱敏:保持 JSON 结构,并让坏行可见
日志排查常常需要把 JSONL 片段交给同事、工单系统或实验环境,但原始行里可能混有手机号、邮箱、身份证样式字符串和 IPv4 地址。直接删除整条记录会丢上下文;直接用正则替换整行又可能破坏 JSON,导致后续工具无法解析。
本文验证一个零依赖的 Python 小工具:只处理 JSON 中的字符串值,递归保留对象和数组结构,并把无法解析的输入行原样保留、单独报告。
设计边界
工具处理四类常见标识:
- 邮箱地址;
- 中国大陆手机号样式;
- 18 位身份证样式字符串;
- 通过逐段 0–255 校验的 IPv4 地址。
它不是完整的匿名化系统,不能替代字段分级、访问控制或合规审查。对于业务标识、姓名、地址、设备序列号等自定义字段,仍需按各自数据字典补充规则。
为什么不能直接替换整行
假设输入是:
{"actor":{"email":"alice@example.com","phone":"13800138000"},"source_ip":"10.20.30.40"}
如果先对整行字符串做替换,替换文本中的引号或固定标记可能破坏 JSON 语法。更稳妥的方法是先用 json.loads 解析,再递归遍历字符串值,最后用 json.dumps 输出紧凑 JSONL。
核心遍历逻辑如下:
def redact_value(value, counts):
if isinstance(value, str):
return redact_text(value, counts)
if isinstance(value, list):
return [redact_value(item, counts) for item in value]
if isinstance(value, dict):
return {key: redact_value(item, counts) for key, item in value.items()}
return value
这样只替换值,不修改 JSON 键名;对象、数组和数值类型也保持不变。
IPv4 不能只靠正则形状判断
999.1.2.3 看起来像 IPv4,但不是合法地址。工具先用表达式找到候选,再逐段转换为整数并检查是否处于 0–255。这样能避免把版本号等普通文本误标为 IP。
错误行必须可见
批量处理日志时,最危险的做法之一是悄悄跳过坏行。该工具采用两种处理:
- 无法解析的行原样输出;
- stderr 输出包含行号和错误列号的摘要,进程以状态码 2 结束。
这样既不会破坏原始证据,也能让批处理脚本立即发现输入质量问题。
实际验证
本次项目包含 4 个标准库 unittest 测试,覆盖嵌套结构、非法 IPv4、坏行保留和输出仍是有效 JSONL。运行结果:
Ran 4 tests in 0.000s
OK
对两行合成样例运行后,stderr 摘要为:
{"records_processed": 2, "redactions": {"email": 2, "id_card": 1, "ipv4": 1, "phone": 1}, "parse_errors": []}
脱敏后的第一行仍可被普通 JSON 解析器读取:
{"ts":"2026-11-03T00:00:00Z","actor":{"email":"[REDACTED_EMAIL]","phone":"[REDACTED_PHONE]"},"source_ip":"[REDACTED_IPV4]","message":"login ok"}
使用方式
python3 jsonl_privacy_redactor.py input.jsonl -o output.jsonl
python3 -m unittest discover -s tests -v
如果要接入管道,可以省略输入文件名并从 stdin 读取。生产使用前,应根据自己的日志字段和合规要求扩展标识规则,并保留原文件访问控制,不要因为“已脱敏”就放宽权限。
小结
对 JSONL 做脱敏时,优先在解析后的数据结构中处理字符串值;对无法解析的行保留证据并显式报错;对 IP 这类有明确数值范围的格式增加语义校验。这个零依赖实现适合作为日志导出前的一道轻量检查,但不应被当作完整的隐私治理方案。
- 点赞
- 收藏
- 关注作者
评论(0)