4. 日志基础:CloudWatch Logs 结构与查询归纳总结
聊到日志自动化巡检,你首先得搞清楚你的“原材料”长什么样。CloudWatch Logs 的结构,说白了就三件事:日志组、日志流、日志事件。搞懂这三者的关系,你的巡检脚本才能写得顺手。
一句话总结:日志组是项目容器,日志流是实例来源,日志事件是单条记录。咱们的巡检,就是在这三层结构上做文章。
4.1 日志组与日志流:分清楚“文件夹”和“文件”
我刚接触 CloudWatch Logs 那会儿,也迷糊过。后来自己想了个比喻:日志组就像一个大文件夹,日志流就是里面的单个文件。
具体来说:
- 日志组(Log Group):一组相关日志的集合。比如你的 Lambda 函数叫
my-log-checker,系统会自动创建一个同名的日志组。它主要管理日志的保留策略、权限和指标过滤。 - 日志流(Log Stream):日志组里的最小写入单元。每个执行环境(比如每个 Lambda 并发实例)都有自己的日志流。我排查高并发问题时就发现,不同实例的日志是分散在不同流里的。
实用技巧:查询时如果发现“少了日志”,先看日志流列表。很可能数据都在,只是分散在多个流里。用 filterLogEvents API 会自动跨流检索,但用 CLI 直接查就得指定流名。
4.2 日志事件:别再当字符串看了
日志事件是 CloudWatch Logs 里的最小数据单元。它自带三个核心字段,很多人只关注消息体,忽略了另外两个宝贝:
| 字段 | 说明 | 巡检价值 |
|---|---|---|
| timestamp | 事件产生时间(毫秒级 Unix 时间戳) | 判断日志是否延迟上报、统计时间序列 |
| message | 日志原文(字符串) | 关键词匹配、JSON 解析、异常识别 |
| ingestionTime | CloudWatch 服务端接收时间 | 排查日志写入延迟——写入量过大时这个值会明显滞后 |
我记得有个项目,业务方说 Lambda 日志“丢了”。查了半天,其实日志都在,只是 ingestionTime 比 timestamp 晚了快 10 分钟。所以你在写巡检时,不建议盯着 ingestionTime 做实时判别,它是有延迟的。
4.3 查询语法:从 CLI 到 SDK 到 Logs Insights
CloudWatch Logs 给咱们提供了三套查询方式,各有各的用法。
4.3.1 AWS CLI:快速验证的好帮手
开发阶段我经常用 CLI 先测试查询条件。比如查某个日志组最近 5 分钟的错误:
aws logs filter-log-events \
--log-group-name "/aws/lambda/my-log-checker" \
--start-time $(date -d '5 minutes ago' +%s)000 \
--filter-pattern "ERROR" \
--output json
--filter-pattern 支持简单的关键词过滤。注意:它只查前 10,000 条匹配结果,量大的时候建议加时间范围缩小查询。
4.3.2 Logs Insights:分析利器
这是我后来用得最多的方式。它用类似 SQL 的语法,能直接做聚合、统计、排序。
经典查询模板(统计过去 1 小时各状态码分布):
fields @timestamp, @message
| filter @message like /(?i)error|exception|fail/
| parse @message /\[?status\]?\s*[:=]?\s*(?<status_code>\d{3})/
| stats count() by status_code
| sort status_code asc
| limit 10
这里有个坑——parse 语句的正则写不好就容易匹配不到数据。我建议先用 display 看看提取结果对不对,再跑 stats。
4.3.3 SDK:程序化集成的核心
咱们做自动化巡检,最终还是要落到 SDK 调用。以 Python 的 boto3 为例:
import boto3
client = boto3.client('logs')
response = client.filter_log_events(
logGroupName='/aws/lambda/my-log-checker',
startTime=int((datetime.now() - timedelta(hours=1)).timestamp() * 1000),
endTime=int(datetime.now().timestamp() * 1000),
filterPattern='ERROR'
)
for event in response['events']:
print(event['timestamp'], event['message'][:200])
注意分页:filter_log_events 单次最多返回 10,000 条事件或 1 MB 数据。如果日志量巨大,必须处理 nextToken 做分页拉取。我曾经就因为这,巡检报告里漏了一小时的错误日志。
4.4 日志归总:给巡检数据“瘦身”
原始日志通常很“嘈杂”——有调试信息、有健康检查、有重复堆栈。直接拿来巡检,效率低,还容易误报。我的处理套路是这样的:
- 粗筛:按日志组+时间范围,只拉取最近 N 分钟的日志。
- 精筛:用
filterPattern或代码内正则,锁定 ERROR、CRITICAL、Exception 等关键字。 - 结构化:如果是 JSON 日志,直接解析成字典;如果不是,用正则提取关键字段成表格。
- 聚合:按错误类型、模块、时间窗口做计数归总。
说白了,你的 Lambda 巡检函数每次跑,应该只做“高价值数据提取”——把海量日志压缩成几十行关键摘要。这样 SNS 通知也不会刷屏,S3 存储也省空间。
4.5 知识结构总览
下面这张图梳理了本章的核心脉络。我自己做课程的时候习惯画这么一张,方便回顾。
嗯,有了这个结构,你写 Lambda 巡检函数时脑子里会清晰很多——先确定日志组,再针对日志事件用合适的查询方式,最后把结果归总。别小看这些基础,跑通一遍,后面的 S3 归档、SNS 告警、定时调度才能稳稳立住。