日志级别设计:TRACE、DEBUG、INFO、WARN、ERROR、FATAL 在期权场景下的定义与使用规范
日志级别这东西,说白了就是给系统装个「分级报警器」。
我见过不少团队,上来就 INFO 打天下,结果线上出问题,几千行日志翻得人想吐。也见过有人 DEBUG 开满,一天写几个 G 的日志,磁盘直接爆掉。
期权做市系统尤其敏感。你想想看,一个延迟几毫秒的报价,可能就错过了一笔几十万的套利。日志级别设计不好,要么淹死你,要么坑死你。
一、六个级别的核心定义
先给个总览。这六个级别,从低到高,严重程度递增:
| 级别 | 含义 | 期权场景典型用途 |
|---|---|---|
| TRACE | 最细粒度的追踪信息 | 单笔订单的完整生命周期、每个计算步骤的中间值 |
| DEBUG | 调试信息,开发阶段使用 | 策略参数变化、行情快照、内部状态切换 |
| INFO | 正常运行的提示信息 | 系统启动、连接建立、每日结算完成、订单成交 |
| WARN | 潜在问题,不影响当前运行 | 行情延迟超过阈值、风控接近限额、网络重连 |
| ERROR | 错误事件,功能受影响 | 订单拒单、行情源断开、计算异常、数据库写入失败 |
| FATAL | 致命错误,系统无法继续 | 内存溢出、核心进程崩溃、策略引擎死锁 |
我的个人习惯:生产环境默认只开 INFO 及以上。DEBUG 和 TRACE 只在排查特定问题时动态开启,用完立刻关掉。
二、每个级别的使用规范
1. TRACE —— 显微镜级别的细节
TRACE 是给「最后一公里」用的。我在项目中遇到过几次诡异的 Greeks 计算偏差,就是靠 TRACE 日志定位到的。
什么时候用?
- 每个订单从「收到请求」到「交易所确认」的完整链路
- 定价模型里每个中间变量的值(比如隐含波动率迭代的每一步)
- 套利策略里每个价差计算的原始数据
// 示例:TRACE 日志记录一次定价计算
logger.trace("定价计算开始 | 标的价={} | 剩余时间={} | 行权价={}",
underlyingPrice, timeToExpiry, strikePrice);
// ... 中间计算 ...
logger.trace("BSM中间值 | d1={} | d2={} | N(d1)={} | N(d2)={}",
d1, d2, normCdf(d1), normCdf(d2));
logger.trace("定价完成 | 理论价={} | 隐含波动率={}",
theoreticalPrice, impliedVol);
避坑指南:我曾经在 TRACE 里打印了完整的订单簿快照,每次 200 行。结果一天下来日志文件 50G。后来改成只打印「变化部分」,数据量降了 90%。
2. DEBUG —— 开发者的眼睛
DEBUG 是开发阶段的主力。说白了,就是让你知道「系统在想什么」。
我建议这样用:
- 策略参数的动态调整记录
- 风控规则的触发条件与计算结果
- 网络连接的建立与断开细节
- 定时任务的执行状态
// 示例:DEBUG 记录风控检查
logger.debug("风控检查 | 当前持仓={} | 风险限额={} | 检查结果={}",
currentPosition, riskLimit, checkResult);
if (checkResult == RiskCheckResult.EXCEEDED) {
logger.warn("风控触发 | 持仓超限 | 当前={} | 限额={}",
currentPosition, riskLimit);
}
3. INFO —— 系统的「心跳」
INFO 是给运维人员看的。要保证一个不熟悉系统的人,只看 INFO 日志就能知道系统在干什么。
我的经验:
- 系统启动与关闭(带版本号)
- 与交易所的连接状态变化
- 每日结算完成(带关键指标)
- 订单成交(带合约、价格、数量)
- 策略切换或参数重载
// 示例:INFO 记录系统关键事件
logger.info("系统启动完成 | 版本={} | 策略={} | 连接交易所={}",
VERSION, strategyName, exchangeList);
logger.info("订单成交 | 合约={} | 方向={} | 价格={} | 数量={} | 耗时={}ms",
contract, side, price, qty, elapsedMs);
4. WARN —— 提前预警
WARN 不是错误,但值得关注。我常说:「WARN 是系统在喊救命,但还没断气。」
典型场景:
- 行情延迟超过 50ms(正常是 5ms)
- 风控指标接近限额(比如用了 80%)
- 网络重连成功(但已经断开了 3 秒)
- 数据库连接池使用率超过 70%
注意:WARN 不能太多。如果每分钟出现几十条 WARN,运维人员就会麻木,真正的问题反而被忽略了。我一般要求 WARN 的日产量不超过 100 条。
5. ERROR —— 必须处理的问题
ERROR 意味着某个功能不可用了。但系统还能继续跑。
我遇到过的典型 ERROR:
- 订单被交易所拒单(带拒单原因码)
- 行情源断开且重连失败
- 定价计算抛出异常(比如除零、空指针)
- 数据库写入超时或失败
// 示例:ERROR 记录异常
try {
// 发送订单
exchange.sendOrder(order);
} catch (OrderRejectedException e) {
logger.error("订单被拒 | 合约={} | 原因={} | 错误码={}",
order.contract, e.getReason(), e.getErrorCode());
// 触发降级逻辑
fallbackHandler.handle(order);
}
我的习惯:每个 ERROR 日志必须附带上下文信息(合约、价格、数量等),方便快速定位。光打印「订单被拒」四个字,等于没打。
6. FATAL —— 最后的遗言
FATAL 是系统崩溃前的最后一条日志。用完这条,系统就挂了。
什么时候用?
- 内存溢出(OOM)
- 核心线程死锁
- 策略引擎状态不一致(比如 Greeks 计算出现 NaN)
- 配置文件加载失败且无默认值
// 示例:FATAL 记录致命错误
if (Double.isNaN(greeks.delta) || Double.isNaN(greeks.gamma)) {
logger.fatal("Greeks 计算异常 | 合约={} | delta={} | gamma={} | 终止策略引擎",
contract, greeks.delta, greeks.gamma);
System.exit(1); // 或者触发看门狗重启
}
重要:FATAL 日志必须包含足够的信息让运维人员知道「为什么挂了」。我见过有人只写了个「FATAL: system crash」,然后就没有然后了。这等于白写。
三、日志级别使用流程图
下面这张图,是我自己总结的「日志级别决策流程」。每次写日志前,问自己三个问题:
嗯,这个流程很简单:先问系统还能不能跑,再问功能有没有受影响,然后看是不是潜在风险,最后看是不是调试需要。一路问下来,级别自然就定了。
四、生产环境的最佳实践
最后分享几个我在实战中踩过的坑:
- 动态级别调整:生产环境默认 INFO,但必须支持通过配置中心动态调整某个模块的日志级别。我遇到过行情模块出问题,远程把级别降到 DEBUG,定位完再调回去。
- 日志采样:对于高频事件(比如每秒 1000 次的行情更新),不要每条都打。我一般用「每 100 条打 1 条」或者「每 10 秒打 1 条」的策略。
- 上下文传递:每个日志最好带上「请求 ID」或「订单 ID」,方便串联。我见过最坑的日志就是「计算异常」四个字,完全不知道是哪个订单。
- 不要打敏感信息:账户密码、API Key 这些绝对不能出现在日志里。我曾经见过有人把交易所的密钥直接打到了 INFO 日志里,还好发现得早。
总结一句话:TRACE 给机器看,DEBUG 给开发者看,INFO 给运维看,WARN 给值班的人看,ERROR 给开发组长看,FATAL 给老板看(因为系统挂了)。