第4章:日志级别设计——DEBUG、INFO、WARN、ERROR在加密业务中的合理使用

日志级别这玩意儿,说白了就是给日志贴个“严重程度”标签。很多团队的日志写得跟流水账一样,出了事根本捞不到关键信息。我这些年看过太多案例了——要么全打INFO,要么DEBUG日志上了生产,磁盘被撑爆。

加密业务的日志跟普通业务还不一样。你想想看,密钥、证书、签名、验签,每一步都涉及敏感资产。日志级别设错了,轻则排查困难,重则影响合规。

我个人习惯把日志级别看作一套“信号系统”:

  • DEBUG:开发者的显微镜,看细节的
  • INFO:值班人员的仪表盘,看状态的
  • WARN:运维的预警铃,看风险的
  • ERROR:故障的紧急灯,看断了没

先看一张图,把这四个级别在加密链路中的定位看清楚。

加密SDK日志级别决策图谱 加密SDK日志级别决策图谱 DEBUG 开发阶段使用 INFO 正常业务轨迹 WARN 潜在风险预警 ERROR 故障立即响应 各层级典型场景对照 密钥生成参数 密钥算法、位数、格式详情 → DEBUG 加密/解密操作完成 操作成功、耗时、数据量大小 → INFO 证书即将过期 距过期不足30天,但尚可继续使用 → WARN

4.1 DEBUG:开发阶段的“无损探测器”

DEBUG日志只服务于一件事——让开发者看清加密链路的每一个中间状态。

我记得有一次排查签名验签不匹配的bug,问题出在ECDSA签名时用错了一个曲线参数。如果没打DEBUG日志,我可能要花两天才能定位到那个地方。DEBUG日志把签名原始数据、曲线类型、密钥指纹全吐出来了,一看就知道了。

经验之谈:DEBUG日志里可以记录密钥指纹的SHA-256摘要,但不能打印完整明文密钥。这是红线,踩了就违规。

哪些场景该用DEBUG:

  • 每次Hash计算的输入输出
  • 签名/验签的原始数据与签名结果
  • 密钥协商过程中的中间参数
  • 证书链解析的每一步状态
  • SM2/SM4等国密算法的中间操作标记

一个实战案例:我之前在一个支付SDK里,每次SM4加密前都会在DEBUG级别打印明文数据长度和IV向量。为什么?因为有一次生产环境出了个诡异的问题:不同语言实现的加密结果不一致。最后通过DEBUG日志对比IV,发现是Java端误用了随机IV,而C#端用了固定IV。DEBUG日志直接定位了根因。

注意:DEBUG日志在生产环境必须默认关闭,通过动态配置开关按需开启。另外,日志里不要打印手机号、身份证号等PII字段,即便明文也不行。

4.2 INFO:业务轨迹的“行车记录仪”

INFO记录的是“这件事做完了,结果是好的”。它是给值班人员和监控系统看的,不是给开发者看细节用的。

我建议每个加解密操作只打一到两行INFO日志。太多就成噪音了。

INFO应该记录什么:

  • 加密/解密操作的成功执行(带操作标识符和耗时)
  • 签名/验签的成功结果
  • 密钥加载/生成完成(不带密钥内容)
  • 证书链验证通过
  • TLS握手完成(附带协议版本和加密套件)

我一贯的原则:INFO日志要能让值班人员只看日志就能判断“业务运行是否正常”。如果一个新人刚接手这个SDK,看INFO日志能知道加密链路走到哪一步了,那就是合格的INFO日志。

一句话总结:INFO日志不要写过程,只写结果。

4.3 WARN:潜在风险的“预警雷达”

WARN的意思是:事情还能跑,但已经不正常了。加密业务里,WARN比ERROR更值得重视,因为它往往是故障的前兆。

我曾经接过一个紧急工单,客户说加密SDK突然报错。我一查历史WARN日志,发现早在两周前就一直在打印“证书有效期不足15天”的警告。客户没当回事,结果证书过期当天所有加密连接全部断开。

哪些场景该用WARN:

  • 证书有效期剩余不足30天(可配置阈值)
  • 加密操作耗时超过基线值2倍以上
  • 密钥轮换失败,但现有密钥仍可用
  • 硬件安全模块连接超时,走软件降级
  • 签名验签失败但业务允许重试

关于WARN的一个特别提醒:我见过一些团队把WARN当成“更轻的ERROR”来用,只要失败了就打WARN。这不对。WARN的核心语义是“有风险,但业务还没断”。如果业务已经断了,那就是ERROR。

4.4 ERROR:故障现场的“事故记录仪”

ERROR日志代表一件事:这条加密链路断了,业务受到了直接影响。

ERROR日志最重要的是三点:

  1. 谁出错了(是加密操作、密钥加载还是验证环节)
  2. 什么出错了(具体的异常类型和错误码)
  3. 跟什么有关(关联的请求ID、密钥ID、会话ID)

我见过太多ERROR日志就写了一句“加密失败”,然后什么上下文都没有。排查的时候完全抓瞎。

避坑指南:我曾经遇到过有人在ERROR日志里打印了完整的加密密钥(因为觉得“出错了就要保留现场”)。这是灾难性的。ERROR日志依然要遵守安全规范——不能打印明文密钥、私钥、用户口令。出错的上下文应该是错误码、异常堆栈和业务标识,不是敏感数据。

哪些场景该用ERROR:

  • 密钥加载失败,且无法恢复
  • 证书链验证彻底失败
  • 哈希严重不一致(数据可能被篡改)
  • HSM连接断开且无法重连
  • 签名验签失败且不再重试

4.5 四个级别的使用速查表

级别 使用场景 敏感字段 生产环境默认 一句话判断
DEBUG 开发调试、追踪中间状态 可打印密钥摘要,禁止打印明文密钥 关闭 我看清了每一步
INFO 正常业务操作完成 只打操作标识,不打内容 开启 事情做完了,结果OK
WARN 潜在风险、降级、超时 可打印密钥过期时间,不打密钥值 开启 有隐患,但还能忍
ERROR 业务中断、彻底失败 只打错误码、请求ID,不打密钥 开启 断了,必须马上看

4.6 代码示例:一个加密操作的日志编排

来看一段真实的加密操作日志是怎么组织的。嗯,这里用了“熔断+灰度”的小技巧。

// 伪代码示例:对称加密操作日志编排
public byte[] encryptData(byte[] plainData, String keyId) {
    // DEBUG:记录加密前状态
    logger.debug("开始加密,数据长度={}字节,密钥ID={}", plainData.length, keyId);

    try {
        Key key = loadKey(keyId);
        // DEBUG:记录密钥加载状态,但只打指纹
        logger.debug("密钥加载完成,指纹={}", DigestUtils.sha256Hex(key.getEncoded()));

        byte[] cipherData = doEncrypt(plainData, key);

        // INFO:记录加密成功结果
        logger.info("加密操作成功,密钥ID={},数据长度={}→{}字节,耗时={}ms",
                keyId, plainData.length, cipherData.length, elapsed);

        return cipherData;
    } catch (KeyExpiredException e) {
        // WARN:密钥过期但可能有降级预案
        logger.warn("密钥已过期,密钥ID={},过期时间={},尝试降级密钥", keyId, key.expireTime);
        // ... 降级逻辑
        return fallbackEncrypt(plainData, keyId);
    } catch (EncryptException e) {
        // ERROR:加密彻底失败
        logger.error("加密操作失败,密钥ID={},错误码={},请求ID={}",
                keyId, e.getCode(), requestId, e);
        throw e;
    }
}

这个代码块的关键点在哪?

  • 同一函数内,三个级别都出现了,但各自负责各自的语义
  • DEBUG只打印中间状态,不打敏感内容
  • INFO记录了关键指标:数据量变化和耗时
  • WARN描述了风险+降级动作
  • ERROR带上了错误码和请求ID,方便关联排查

4.7 关于加密SDK的一个特别提醒

加密SDK的日志还有一条红线:合规敏感度。

有些行业(比如金融、政务)对密码产品的日志有强制要求。我记得有个项目,客户要求所有涉及密钥操作的日志必须保留至少180天,且不能明文记录任何密钥材料。这意味着即便在ERROR级别,你也不能把私钥的内容写进日志。

我的建议是:在设计日志级别时,提前跟安全合规团队对一遍日志样例。让他们看一眼你准备在ERROR日志里打印哪些字段。不要等到上线了再改。

4.8 小结一下

  • DEBUG:只给开发看,记录链路中间态;生产默认关,动态开
  • INFO:给监控看,记录业务成功结果;一行搞定,不打过程
  • WARN:给运维看,预警潜在风险;别等它变成ERROR再后悔
  • ERROR:给应急看,记录故障现场;带上上下文,但不碰敏感数据

这四级的划分,说白了就是让你在不同场景下,都能最快找到最该看的那条日志。我自己做加密SDK快十年了,每次出问题复盘,十有八九都跟日志级别混用有关。把级别定清楚,排查效率能提升一大截。