可通过统计单位时间内ERROR等异常日志频次,结合滑动窗口或Redis计数器实现高频异常告警,需异步处理、动态调阈值、分级抑制并关联链路追踪闭环。

可以通过统计单位时间内日志中特定错误级别(如 ERROR)或异常关键词(如 “Exception”、“timeout”、“500”)的出现频次,结合滑动窗口或计数器机制触发告警,核心在于“高频异常即异常”——不是等服务彻底挂掉,而是捕获异常行为的突增信号。
选择支持动态采样的日志框架组件
Logback 和 Log4j2 都支持通过 AsyncAppender + Filter 实现轻量级频次拦截。推荐使用 Logback 的 ThresholdFilter 配合自定义 Evaluator,或直接集成 Micrometer + Dropwizard Metrics 做日志事件计数。关键点是避免在日志输出主路径做复杂计算,应把频次统计下沉到异步队列消费侧。
- Logback 中可扩展
JaninoEventEvaluator,用脚本判断日志消息是否含 “java.lang.NullPointerException” 且过去 60 秒内已出现 ≥5 次 - Log4j2 可用
ScriptFilter+ Groovy 脚本调用外部 Redis 计数器(key 格式:log:err:{exceptionType}:20240515:14),实现跨实例聚合 - 避免在
append()方法里实时查数据库或远程调用,否则会拖慢日志写入,引发线程堆积
构建带时间衰减的滑动计数器
固定窗口(如每分钟清零)容易漏判跨窗口爆发;滑动窗口更准但实现稍重。实际可用“分段环形数组 + 时间戳标记”模拟:例如维护最近 10 个 6 秒桶,每次记录异常时更新对应桶计数,并累加有效桶值得到当前 60 秒总量。
- 用
ConcurrentHashMap<string atomiclong></string>存 {errorKey} → 当前计数,配合 ScheduledExecutorService 每 6 秒清理过期 key(key 带时间戳后缀) - 更稳妥的做法是接入 Redis 的
INCR + EXPIRE组合,例如INCR log:alert:db-connection-timeout后立即EXPIRE … 60,天然支持分布式场景 - 阈值不宜设死,建议按历史基线动态调整:比如取过去 24 小时同时间段 P95 值 ×1.8 作为当前阈值
区分告警等级与抑制误报
高频 WARN 不等于故障,高频 ERROR 也不一定需立刻电话告警。要基于日志上下文做分级决策:
立即学习“Java免费学习笔记(深入)”;
- 同一堆栈 trace 在 1 分钟内重复出现 ≥10 次 → 触发 P3 邮件告警(提示“疑似某接口下游稳定超时”)
- 不同异常类型(NPE、SQLTimeout、RedisConnectionFailure)在 30 秒内各自突破阈值 → 触发 P2 企业微信+短信(提示“多模块并发异常,可能存在基础设施问题”)
- 自动抑制连续 3 次相同告警(ID 基于 exception hash + traceId 前 8 位),防止告警风暴;恢复后发送“已恢复”摘要
对接可观测链路完成闭环
告警本身不是终点。触发后应自动 enrich 日志上下文:提取该时间段内相关 traceId、SpanId,拉取对应链路追踪数据(如 SkyWalking 或 Zipkin),并关联 JVM 监控(GC 次数、线程数、内存使用率)生成简报。
- 通过 Logback 的
MDC注入 traceId,确保日志与链路可对齐 - 告警消息体中附带直连 Grafana 看板链接(带时间范围参数),点击即可查看该时段错误率、RT 分布、服务依赖拓扑
- 支持一键执行预案脚本:如检测到 DB 连接池耗尽,自动触发扩容连接数或熔断非核心查询
不复杂但容易忽略的是日志格式标准化——如果 ERROR 日志里没有统一的异常类名、没有 traceId 字段、堆栈被截断,再好的频次模型也找不到真实根因。先管好日志源头,再谈智能告警。


















