JVM 存活监控别盯 logback 滚动文件,要盯 stdout
给某后台 Java 服务写健康检查,规格是"日志文件超过 5 分钟没有新行就告警"。于是我自然地把告警目标指向 logback 输出的那个滚动文件——logs/app.log.2026-05-15-0.log,看起来最新、最顺手。结果业务低峰期持续告警,端口通、PID 在、服务健康,就是日志文件一动不动。
现象
凌晨流量低谷:
nc -z 127.0.0.1 8080返回正常ps aux | grep app.jar进程存在/proc/<PID>/net/tcp连接活跃- 但
stat logs/app.log.2026-05-15-0.log显示 mtime 已经 8 小时前
监控脚本判定"超过阈值无写入",连续触发告警。
根因
logback 的滚动文件记录的是业务流量,不是进程活跃度。
几个具体机制:
- INFO 级别在零请求时完全沉默——没有请求就没有 access log,INFO 级业务日志同样安静
- ERROR 级别沉默才是好事——没有错误不写,mtime 不更新
-0.log可能是最旧的分片——logback 按大小滚动时,-0.log是最早的一段,-N.log才是当前写入点- 跨日滚动——每天 0 点文件名换一个,旧文件名的 mtime 从此永远不变
所以,logback 文件的 mtime = 上一次业务事件的时间。业务空闲 8 小时 = mtime 静止 8 小时,但进程完全正常。
把 app.log.YYYY-MM-DD-0.log 的 mtime 当做 liveness 指标,会在低流量窗口(夜间、周末、冷备服务)持续误报。你盯的是"最后一次有用户访问的时间",不是"进程还在不在"。
正解
用 /proc/<PID>/fd/1 对应的 stdout 文件做活跃度探针。
用 nohup java -jar app.jar > app.log 2>&1 & 启动的 JVM,fd 1(stdout)和 fd 2(stderr)都重定向到同一个文件。即便零业务流量,这个文件也会被 JVM 本身持续写入:
- Spring Boot 周期任务心跳
- 连接池保活日志
- Tomcat NIO connector 内部事件
- JVM GC 输出(如果开了 verbose gc)
- 框架的 @Scheduled 任务
实测 Spring Boot Web 服务在纯空载时,stdout 每 30 秒左右仍有写入。
先找准 stdout 对应的实际文件路径,不要凭文件名猜:
ls -la /proc/$(pgrep -f "app.jar")/fd/ | grep -E 'log|out|err'
# 输出示例:
# 1 -> /data/jar/svc/app.log # stdout — 这才是 liveness 目标
# 2 -> /data/jar/svc/app.log # stderr — 同一文件
# 8 -> /data/jar/svc/logs/app.log.2026-05-15-0.log # logback — 业务日志推荐的三层健康检查(按信号强度排序):
# 第一层:端口存活(最强信号,挂了必告警)
nc -z -w 2 127.0.0.1 8080 || alert "port down"
# 第二层:进程存在(兜底)
pgrep -f "app.jar" >/dev/null || alert "process dead"
# 第三层:stdout 活跃度(弱信号,阈值放宽)
LOG=/data/jar/svc/app.log
AGE=$(( $(date +%s) - $(stat -c %Y "$LOG") ))
[ $AGE -gt 900 ] && alert "stdout silent for ${AGE}s"阈值不要拍脑袋定。先采样一周低谷时段的实际静默时长:
# 每 2 分钟记一次 stdout 的 mtime
*/2 * * * * stat -c '%Y' /data/jar/svc/app.log >> /tmp/mtime-samples.log取 p99 再乘以 1.5 作为告警阈值。多数 Spring Boot Web 服务落在 10–15 分钟;纯批处理 worker 可能需要 30 分钟以上。
通过 /proc/<PID>/fd/ 找到真正的 stdout 文件路径,以其 mtime 做弱存活信号。阈值采样定,不要默认 5 分钟。纯低频服务(如内部管理后台)直接跳过 stdout 检查,只做端口 nc。
几个特殊场景
纯低频内部后台(管理端、运营工具):确实可能 8 小时以上没有任何写入,stdout 检查没有意义。直接只做端口 nc,在监控配置里标注原因和采样依据。
有定时重启的服务(如 0 3 * * * restart.sh):凌晨 3 点 stdout 文件会被截断,mtime 重置,可能产生瞬间的"文件缩小"误判。检查脚本要忽略重启窗口(如 03:00–03:10)。
挂了 Arthas 的 JVM:不要在存活监控脚本里用 jstack、jcmd 等 attach 类命令——attach 会短暂加锁 JVM,生产环境可能卡住请求。监控永远只做 nc、ps、stat 这类外部只读操作。
一句话外卖
logback 滚动文件的 mtime 是业务流量计,不是进程心跳;JVM liveness 探针应该指向 stdout(
/proc/<PID>/fd/1),它在零业务时依然有框架级写入。