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 日志归总:给巡检数据“瘦身”

原始日志通常很“嘈杂”——有调试信息、有健康检查、有重复堆栈。直接拿来巡检,效率低,还容易误报。我的处理套路是这样的:

  1. 粗筛:按日志组+时间范围,只拉取最近 N 分钟的日志。
  2. 精筛:用 filterPattern 或代码内正则,锁定 ERROR、CRITICAL、Exception 等关键字。
  3. 结构化:如果是 JSON 日志,直接解析成字典;如果不是,用正则提取关键字段成表格。
  4. 聚合:按错误类型、模块、时间窗口做计数归总。

说白了,你的 Lambda 巡检函数每次跑,应该只做“高价值数据提取”——把海量日志压缩成几十行关键摘要。这样 SNS 通知也不会刷屏,S3 存储也省空间。

4.5 知识结构总览

下面这张图梳理了本章的核心脉络。我自己做课程的时候习惯画这么一张,方便回顾。

CloudWatch Logs 结构与查询 日志组 (Log Group) 日志流 (Log Stream) 日志事件 (Log Event) 三种查询方式 AWS CLI 快速验证查询条件 Logs Insights SQL 风格统计分析 SDK (boto3) 自动化巡检核心 巡检数据归总流程 粗筛 → 精筛 → 结构化 → 聚合 关键输出 关键错误摘要 + 时间序列统计

嗯,有了这个结构,你写 Lambda 巡检函数时脑子里会清晰很多——先确定日志组,再针对日志事件用合适的查询方式,最后把结果归总。别小看这些基础,跑通一遍,后面的 S3 归档、SNS 告警、定时调度才能稳稳立住。