Python 结构化日志脱敏:递归处理敏感键,而不是盲目替换字符串
先沿数据结构处理,再考虑字符串
日志里混入访问令牌、密码或 Cookie,既可能造成泄露,也会增加排查问题的风险。将对象全部转成字符串后,再用正则盲目替换,容易误伤普通业务内容,也可能漏掉嵌套字段。对于已经结构化的数据,按数据结构和约定键名处理,是一个范围更清楚的起点。
下面的实现使用 Python 内置容器与标准库测试辅助:遇到 dict,检查每个键;字符串键转为小写后与敏感键集合比较;命中时直接写入 [REDACTED],不再递归原值。未命中的值继续处理。list 和 tuple 逐项递归,其他类型原样返回。
可运行实现与内存测试
from copy import deepcopy
SENSITIVE_KEYS = {"token", "password", "authorization", "cookie"}
def redact_log_value(value):
"""构造脱敏容器,不修改传入的普通容器。"""
if isinstance(value, dict):
return {
key: "[REDACTED]"
if isinstance(key, str) and key.lower() in SENSITIVE_KEYS
else redact_log_value(item)
for key, item in value.items()
}
if isinstance(value, list):
return [redact_log_value(item) for item in value]
if isinstance(value, tuple):
return tuple(redact_log_value(item) for item in value)
return value
# 全部示例值均为明显虚构值。
source = {
"request": {
"Token": "FAKE-TOKEN-001",
"profile": {"PASSWORD": "FAKE-PASSWORD-002", "name": "demo-user"},
"items": [
{"authorization": "FAKE-AUTH-003"},
("keep-this", {"CoOkIe": "FAKE-COOKIE-004"}),
],
"message": "自由文本 FAKE-SECRET-005 不会被本函数扫描",
}
}
original = deepcopy(source)
redacted = redact_log_value(source)
assert redacted["request"]["Token"] == "[REDACTED]"
assert redacted["request"]["profile"]["PASSWORD"] == "[REDACTED]"
assert redacted["request"]["items"][0]["authorization"] == "[REDACTED]"
assert redacted["request"]["items"][1][1]["CoOkIe"] == "[REDACTED]"
assert redacted["request"]["items"][1][0] == "keep-this"
assert redacted["request"]["message"] == "自由文本 FAKE-SECRET-005 不会被本函数扫描"
assert source == original
assert source["request"]["Token"] == "FAKE-TOKEN-001"
print("全部断言通过")
保留键名,不改写业务对象
大小写归一只用于比较,不会改变输出键名。Token、PASSWORD、CoOkIe 都可以命中规则,输出仍保留原来的键名。敏感字段的原值无论是字符串还是容器,都会整体替换为标记,避免将原值继续送入日志。
dict、list、tuple 会构造新容器,因此不必为了脱敏而改写正在使用的业务对象。测试验证了多层字典、大小写键、列表中的字典、元组中的字典以及源对象不变。上述内存样例的全部断言已通过,未读取真实日志或使用真实凭据。
但这不是通用深拷贝:其他类型会原样返回,自定义可变对象仍可能共享引用。本例只面向约定范围内的普通容器;生产系统需要先确定日志数据模型及序列化策略。
敏感键集合需要适配实际系统
这四个键不是通用的秘密识别器。有的系统使用 access_token、secret、session_id 或自定义字段,应依据日志 schema 扩充规则,评估命名空间和嵌套约定。更稳妥的方案通常是日志白名单:只记录明确允许输出的字段,而不是事后猜测哪些字段敏感。
脱敏可作为防线之一,但不能替代日志字段设计、访问权限和存储周期管理。规则变更还应配套固定输入输出测试,防止新增字段悄悄越过原有检查。
自由文本、循环引用与大对象的边界
函数不会扫描普通字符串。示例 message 中的虚构秘密片段会保持原样,这是有意展示的边界,不代表它适合直接记录。自由文本应单独制定规则;没有可靠规则时,宁可不记录敏感上下文,也不要把一次替换当作安全保证。
示例适用于有限、无循环引用的普通结构。循环引用可能导致递归持续进行直至触发异常,生产实现应维护访问记录并明确循环引用的输出策略。深层嵌套和超大对象也需要大小、深度及耗时限制,避免日志处理本身拖慢业务。
实际使用时应先构造脱敏结果,再交给日志记录器,避免意外记录 source;同时确认记录器、异常处理分支和调试输出不会绕过该边界。结构化日志脱敏的价值不是声称覆盖所有秘密,而是让数据模型、白名单、有限规则与测试协同工作,并对遗漏范围保持诚实。
- 点赞
- 收藏
- 关注作者
评论(0)