如何在Python日志框架中屏蔽请求URL中的敏感密码信息?
问题:日志中意外记录了URL里的敏感密码
先看示例代码:
try: r = requests.get('https://sensitive:passw0rd@what.ever/') r.raise_for_status() except requests.HTTPError: logging.exception("Failed to what.ever")
当端点返回非成功HTTP状态码时,日志会输出包含密码的内容:
Traceback (most recent call last): File "a.py", line 5, in <module> r.raise_for_status() File "venv/lib/python3.5/site-packages/requests/models.py", line 928, in raise_for_status raise HTTPError(http_error_msg, response=self) requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://sensitive:passw0rd@what.ever/
问题在于密码被直接记录到了日志中,虽然可以用logging filter过滤整行,但更理想的是直接屏蔽密码。由于logging.exception没有传入字符串,在应用侧过滤比较棘手,想知道在logging框架中何处可以转换日志记录?
解决方案
我来帮你梳理几个在logging框架里实现日志内容转换的实用方法,从源头到输出环节都有对应的方案:
1. 自定义LogRecord工厂:从创建日志记录时就处理
Python的logging允许替换默认的LogRecord工厂函数,在日志记录刚被创建的时候就修改敏感内容,这样后续所有处理环节拿到的都是已经脱敏的内容:
import logging import re def sanitized_record_factory(*args, **kwargs): # 创建原始的LogRecord record = logging.LogRecord(*args, **kwargs) # 定义匹配URL中密码部分的正则 url_pattern = re.compile(r'(https?://[^:]+):[^@]+@') # 处理日志消息 if record.msg: record.msg = url_pattern.sub(r'\1:***@', record.msg) # 处理异常栈文本 if record.exc_text: record.exc_text = url_pattern.sub(r'\1:***@', record.exc_text) return record # 替换默认的工厂函数 logging.setLogRecordFactory(sanitized_record_factory)
2. 自定义Filter:灵活修改LogRecord
Filter不仅能过滤日志,还可以直接修改可变的LogRecord对象,你可以针对特定日志器添加这个过滤,不会影响全局:
import logging import re class SanitizePasswordFilter(logging.Filter): def filter(self, record): url_pattern = re.compile(r'(https?://[^:]+):[^@]+@') # 处理日志消息 if hasattr(record, 'msg') and record.msg: record.msg = url_pattern.sub(r'\1:***@', record.msg) # 处理异常栈文本 if hasattr(record, 'exc_text') and record.exc_text: record.exc_text = url_pattern.sub(r'\1:***@', record.exc_text) # 返回True表示保留这条日志(如果返回False就会被过滤掉) return True # 给根日志器添加过滤器 root_logger = logging.getLogger() root_logger.addFilter(SanitizePasswordFilter())
3. 自定义Formatter:聚焦日志输出环节的处理
Formatter是负责把LogRecord转换成最终字符串的环节,你可以重写它的异常格式化方法,专门处理异常栈里的敏感URL:
import logging import re class SanitizedExceptionFormatter(logging.Formatter): def formatException(self, exc_info): # 获取原始的异常栈文本 original_exc_text = super().formatException(exc_info) # 替换密码部分 url_pattern = re.compile(r'https?://[^:]+:[^@]+@') return url_pattern.sub(r'https://***:***@', original_exc_text) # 给日志处理器设置这个Formatter console_handler = logging.StreamHandler() console_handler.setFormatter(SanitizedExceptionFormatter()) logging.getLogger().addHandler(console_handler)
额外建议:从源头避免密码出现在URL里
其实最彻底的办法是不要在URL里直接携带密码,requests支持通过auth参数传递认证信息,这样即使触发异常,日志里的URL也不会包含敏感内容:
# 用auth参数替代URL里的密码 r = requests.get('https://what.ever/', auth=('sensitive', 'passw0rd'))
内容的提问来源于stack exchange,提问作者vidstige
相关产品推荐
相关产品推荐

