日志监控与告警
1. 这是什么
日志、监控和告警是系统上线后观测运行状态的核心手段。
它们共同构成了系统可观测性的基础。
一句话理解:
- 日志负责记录发生了什么
- 监控负责观察趋势和状态
- 告警负责在超出预期时提醒人采取动作
2. 为什么重要
很多系统不是写挂的,而是:
- 出问题时没人知道
- 知道了也找不到原因
没有可观测性,再好的代码也很难长期稳定运行。
真正的工程能力不只是“系统能跑”,还包括:
- 系统出问题时能不能快速定位
3. 先建立直觉:日志、监控、告警不是同一件事
最常见的误区之一是:
- 以为打了很多日志就等于可观测性做好了
其实这三者解决的问题不同:
| 能力 | 主要回答什么问题 |
|---|---|
| 日志 | 当时具体发生了什么 |
| 监控 | 系统整体趋势怎么样 |
| 告警 | 是否已经超出预期,需要人介入 |
所以:
- 日志很多,不代表监控完整
- 监控很多,不代表告警有效
4. 核心内容
4.1 日志分级
日志分级的价值在于:
- 让不同重要性的事件被区别对待
常见级别例如:
DEBUGINFOWARNERROR
学习阶段更重要的是建立习惯:
- 不是所有日志都该打成错误
- 也不是所有日志都值得长期保留
4.2 结构化日志
结构化日志的核心价值是:
- 方便检索、聚合和分析
比起纯文本拼接,结构化日志更适合:
- 按字段查询
- 按 TraceId 聚合
- 做告警和统计
4.3 TraceId / 请求链路
TraceId 的价值特别大,因为:
- 一次请求可能会跨过很多层
- 如果没有统一标识,排障会非常困难
有了 TraceId,你就能把:
- 网关日志
- 应用日志
- 下游调用日志
串成一条请求链。
4.4 基础监控指标
监控不是只看机器 CPU 和内存。
至少要同时看两类指标:
系统指标
- CPU
- 内存
- 磁盘
- 网络
业务指标
- QPS
- RT
- 错误率
- 下单量
- 支付成功率
如果只看系统指标,很容易漏掉业务故障。
4.5 告警阈值设计
告警的目标不是“尽量多报”,而是:
- 报真正值得处理的问题
有效告警至少要考虑:
- 阈值是否合理
- 持续时间是否合理
- 是否有恢复通知
- 是否能定位到负责人
5. 学习重点
这一章最重要的是掌握:
- 日志是证据,不是输出堆积
- 监控和告警必须同时覆盖系统指标和业务指标
- TraceId 是排障的关键基础设施
- 告警质量比告警数量更重要
6. 常见问题
6.1 日志过多却没有重点
这样一出问题,检索成本会非常高。
6.2 监控只看机器指标不看业务指标
这会导致业务已经异常,但监控面板看起来仍然“机器正常”。
6.3 告警泛滥导致没人关注
告警太多、噪声太大,最后结果往往是:
- 所有人都麻木
7. 动手验证
这一节我用一个纯 Java demo,把 TraceId、结构化日志、基础指标和告警阈值串起来。
7.1 准备一个可运行示例
新建文件 ObservabilityDemo.java,内容如下:
java
import java.util.UUID;
public class ObservabilityDemo {
static class Metrics {
int totalRequests;
int totalErrors;
long totalCostMs;
void record(boolean success, long costMs) {
totalRequests++;
totalCostMs += costMs;
if (!success) {
totalErrors++;
}
}
double errorRate() {
return totalRequests == 0 ? 0 : (double) totalErrors / totalRequests;
}
long avgRt() {
return totalRequests == 0 ? 0 : totalCostMs / totalRequests;
}
}
public static void main(String[] args) {
Metrics metrics = new Metrics();
handleRequest(metrics, true, 80);
handleRequest(metrics, true, 120);
handleRequest(metrics, false, 300);
System.out.println("metric-totalRequests=" + metrics.totalRequests);
System.out.println("metric-totalErrors=" + metrics.totalErrors);
System.out.println("metric-avgRtMs=" + metrics.avgRt());
if (metrics.errorRate() > 0.2) {
System.out.println("alert-errorRateHigh=true");
}
if (metrics.avgRt() > 150) {
System.out.println("alert-rtHigh=true");
}
}
private static void handleRequest(Metrics metrics, boolean success, long costMs) {
String traceId = UUID.randomUUID().toString();
System.out.println("{\"level\":\"INFO\",\"traceId\":\"" + traceId
+ "\",\"event\":\"request_start\"}");
if (!success) {
System.out.println("{\"level\":\"ERROR\",\"traceId\":\"" + traceId
+ "\",\"event\":\"request_fail\",\"costMs\":" + costMs + "}");
} else {
System.out.println("{\"level\":\"INFO\",\"traceId\":\"" + traceId
+ "\",\"event\":\"request_ok\",\"costMs\":" + costMs + "}");
}
metrics.record(success, costMs);
}
}7.2 编译并运行
bash
javac ObservabilityDemo.java
java ObservabilityDemo7.3 你应该观察到什么
输出会有多行结构化日志和指标,但应重点包含这些关键信息:
text
{"level":"INFO","traceId":"...","event":"request_start"}
{"level":"ERROR","traceId":"...","event":"request_fail","costMs":300}
metric-totalRequests=3
metric-totalErrors=1
metric-avgRtMs=166
alert-errorRateHigh=true
alert-rtHigh=true7.4 每一行在验证什么
- 结构化日志里的
traceId:说明请求链路已具备可串联标识 request_start/request_ok/request_fail:说明日志事件应有明确语义metric-totalRequests、metric-totalErrors、metric-avgRtMs:说明监控指标关注整体趋势,而不是单条日志alert-errorRateHigh=true、alert-rtHigh=true:说明告警是基于指标阈值触发,而不是单纯“有日志就报警”
8. 练习建议
下面这些练习做完,这一章会更扎实:
- 给一个接口链路增加 TraceId
- 设计一套基础监控指标清单
- 复盘一次告警无效或过度告警的问题
- 总结哪些日志该打结构化字段,哪些不值得打印
9. 自测问题
- TraceId 为什么有助于排障?
- 为什么日志、监控、告警必须一起设计?
- 什么样的告警体系才算有效?
- 为什么只看机器指标不够?
10. 自测核对要点
如果你的回答能覆盖下面这些点,说明这一章基本掌握到位了:
- 日志、监控、告警分别解决不同层面的问题
- TraceId 是跨层排障的重要连接线
- 监控需要同时覆盖系统指标和业务指标
- 告警应尽量减少噪声并指向可行动问题