异常日志脱敏关键在于拦截Throwable的toString()、日志参数序列化及message拼接三处,递归扫描cause链并正则匹配pwd=、password=、jdbc://等敏感字段,替换值为*,同时规范自定义异常toString()、禁用MDC直接输出原始连接串。

异常处理中日志脱敏的关键,不是删掉堆栈,而是不让数据库连接串、密码、token、绝对路径等敏感内容进入日志文本。重点在于拦截原始异常的 toString() 输出、日志参数序列化过程、以及日志框架最终拼接的 message 字符串——这三处最容易泄露连接字符串中的 pwd=、password=、jdbc:mysql://user:pass@host 等片段。
识别并拦截异常中的连接串敏感字段
数据库连接串常以字符串形式出现在异常 cause 的 message 或 stack trace 中(例如 SQLException 报错时直接打印完整 URL)。脱敏器需在日志写入前扫描 Throwable 对象:
- 递归遍历 getCause() 链,提取每个异常的 getMessage() 和 toString()
- 对提取出的字符串执行正则匹配:
pwd=([^;\s]+)|password=([^;\s]+)|jdbc:[^\s]*?://[^@]+@ - 匹配到后,用
***替换值部分,保留键名和结构(如pwd=***、jdbc:mysql://root:***@localhost:3306/db) - 注意避开误伤:不匹配含
password的变量名(如oldPassword),只匹配独立参数格式
定制日志事件处理器,统一过滤异常日志
不依赖业务代码手动调用工具类,而是从日志框架入口统一拦截。以 Logback 为例:
- 继承
ch.qos.logback.core.AppenderBase,重写append(ILoggingEvent event) - 检查
event.getThrowableProxy()是否非空 - 若存在,用脱敏逻辑清洗其
getMessage()和各StackTraceElement.toString()中的连接串 - 再调用父类
super.append(event)输出净化后的日志 - 该 Appender 可全局替换
ConsoleAppender或RollingFileAppender,零侵入
避免 toString() 泄露 —— 尤其针对自定义异常包装类
很多项目会用 BusinessException 包装原始异常,但若重写 toString() 时调用 super.toString(),就会把底层 SQLException 的完整连接 URL 带出来:
- 错误写法:
return super.toString() + ", code=" + errorCode; - 正确写法:
return getClass().getSimpleName() + "[code=" + errorCode + ", msg=" + message + "]"; - 禁止在
getMessage()中拼接getCause().getMessage(),除非已确认该 cause 已脱敏 - 若必须保留 cause 用于内部诊断,应在构造时传入,但对外输出完全隔离
配合 MDC 实现请求级上下文脱敏
某些异常日志会通过 MDC.put("dbUrl", url) 主动注入连接信息用于追踪——这属于高危行为,必须约束:
- 在日志 pattern 中禁用
%X{dbUrl}这类直接输出 MDC 值的写法 - 改用安全键名,如
mdc.dbId,再通过映射表查出脱敏后的标识(如mysql-prod-01) - 或在 MDC put 前强制脱敏:
MDC.put("dbUrl", Desensitizer.maskDbUrl(url)) - 确保所有 filter、interceptor、aop 在写入 MDC 前完成校验,阻断原始连接串入库









