4. CloudWatch Logs采集与结构化处理

CloudWatch Logs 是 AWS 上最常用的日志汇聚与检索服务。它本身不负责“产生日志”,而是提供一个集中存放、检索、过滤和转发的容器。理解它的关键在于四个对象:日志组(Log Group)、日志流(Log Stream)、订阅过滤器(Subscription Filter)和指标过滤器(Metric Filter)。日志组是同一类日志的顶层容器,通常按应用或服务命名,例如 /aws/lambda/order-api 或 /app/order-service;日志流是日志组内按来源划分的序列,常见来源是单个 EC2 实例、单个容器任务或单个 Lambda 执行环境。日志事件(Log Event)则是真正写入的一条记录,包含时间戳和消息体。

采集日志的常见路径有三条:应用直接调用 CloudWatch Logs API 写入;通过 CloudWatch Agent 从 EC2 或本地服务器采集文件;通过 Lambda、ECS、EKS 等服务的原生集成自动写入。无论哪条路径,日志最终都以“日志组 → 日志流 → 日志事件”的层级落盘。这个层级决定了后续检索和过滤的作用范围:指标过滤器作用于日志组,订阅过滤器也作用于日志组,因此日志组的划分方式会直接影响后续能否高效提取指标。

日志组不是“文件夹”,而是过滤、保留策略、加密、订阅和指标提取的作用边界。划分日志组时,应让同一组日志具有相同的保留周期、访问权限和指标提取需求。

日志写入后,CloudWatch Logs 提供两种主要的“结构化处理”手段。第一种是指标过滤器,它把日志事件中的文本模式转换为 CloudWatch 指标。例如应用日志中有一行 ORDER_PAID amount=128.50 currency=CNY,我们希望统计支付金额和支付笔数。可以创建指标过滤器,匹配 ORDER_PAID,并用命名捕获组提取 amount 字段,将其发布为自定义指标 OrderPaidAmount,单位设为 None 或 Count。指标过滤器支持两种典型模式:计数型(匹配到一次就加 1)和数值型(把捕获到的数值作为指标值)。计数型适合统计错误次数、请求次数;数值型适合统计金额、耗时、队列深度。

第二种是订阅过滤器,它把日志事件实时转发到其他服务,例如 Lambda、Kinesis Data Streams、Firehose 或 OpenSearch。订阅过滤器同样基于过滤模式,只有匹配的事件才会被转发。它常用于把日志送到下游做二次加工、长期归档或全文检索。订阅过滤器和指标过滤器的区别在于:指标过滤器把日志“变成指标”,订阅过滤器把日志“送到别处”。两者可以同时存在于同一个日志组上,互不冲突。

CloudWatch Logs 采集与结构化处理链路 日志来源 EC2 / 容器 / Lambda 应用文件 / stdout CloudWatch Agent SDK PutLogEvents 日志组 Log Group /app/order-service 保留策略 / 加密 / 权限 过滤与订阅的作用边界 日志流 Log Stream i-0abc… / task-7f3… 按来源划分的事件序列 日志事件 Log Event 时间戳 + 消息体 ORDER_PAID amount=128.50 指标过滤器 Metric Filter 匹配模式 + 命名捕获组 计数型 / 数值型 发布自定义指标 订阅过滤器 Subscription Filter 实时转发匹配事件 下游目标 Lambda / Kinesis / Firehose OpenSearch / S3 CloudWatch 指标 OrderPaidCount OrderPaidAmount 过滤模式示例: [..., level, msg, amount, currency] "ORDER_PAID amount=$amount currency=$currency" 指标过滤器与订阅过滤器均作用于日志组,可同时启用;日志组划分决定过滤与订阅的作用范围。

下面用一个完整的示例说明如何把应用日志接入并提取业务指标。假设有一个订单服务运行在 EC2 上,应用把日志写入 /var/log/order-service/app.log,每行格式如下:

2025-03-11T10:23:41Z INFO ORDER_PAID orderId=O1001 amount=128.50 currency=CNY
2025-03-11T10:23:42Z INFO ORDER_PAID orderId=O1002 amount=64.00 currency=CNY
2025-03-11T10:23:43Z ERROR ORDER_FAILED orderId=O1003 reason=timeout
2025-03-11T10:23:44Z INFO ORDER_PAID orderId=O1004 amount=256.00 currency=CNY

第一步是采集。在 EC2 上安装 CloudWatch Agent,并在配置文件中声明日志文件路径、日志组名称和日志流名称。日志组命名为 /app/order-service,日志流使用实例 ID 或服务名,便于区分来源。配置片段如下:

{
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          {
            "file_path": "/var/log/order-service/app.log",
            "log_group_name": "/app/order-service",
            "log_stream_name": "{instance_id}",
            "timestamp_format": "%Y-%m-%dT%H:%M:%SZ"
          }
        ]
      }
    }
  }
}

第二步是创建指标过滤器。在日志组 /app/order-service 上创建两个过滤器:一个统计支付笔数,一个统计支付金额。支付笔数使用计数型,过滤模式为 ORDER_PAID,指标名 OrderPaidCount,命名空间 OrderService,单位 Count。支付金额使用数值型,过滤模式为 [..., amount, currency] "ORDER_PAID amount=$amount currency=$currency",指标名 OrderPaidAmount,单位 None。这里 $amount 是命名捕获组,CloudWatch 会把捕获到的数值作为指标值发布。注意过滤模式中的字段顺序必须与日志行中的实际顺序一致,否则匹配会失败。

第三步是验证。写入几条测试日志后,在 CloudWatch 控制台的“日志组 → 指标过滤器”中查看匹配情况,确认过滤器已生效。随后进入 CloudWatch 指标页面,选择命名空间 OrderService,查看 OrderPaidCount 和 OrderPaidAmount 是否有数据点。如果指标没有出现,常见原因有三类:过滤模式与日志格式不匹配、日志组名称写错、指标过滤器创建后尚未有新的日志事件写入。指标过滤器只处理创建之后写入的事件,历史日志不会被回溯统计。

指标过滤器不会回溯历史日志。创建过滤器后,只有新写入的日志事件才会被统计。如果需要统计历史数据,应先把历史日志导出到 S3,再用 Athena 或 Lambda 做离线分析。

第四步是配置订阅过滤器。如果希望把日志实时送到 Lambda 做结构化解析,或送到 Kinesis Firehose 归档到 S3,可以在同一日志组上创建订阅过滤器。订阅过滤器需要指定目标 ARN、过滤模式和角色权限。过滤模式可以留空表示转发全部事件,也可以只转发 ERROR 级别的事件以降低下游成本。订阅过滤器与指标过滤器可以同时存在,前者负责“转发”,后者负责“转指标”,两者互不影响。

在实际工程中,日志组划分和过滤模式设计是最容易出问题的两个环节。日志组划分过粗,会导致不同保留周期、不同权限、不同指标需求的日志混在一起;划分过细,又会增加管理成本。一个实用的原则是:同一日志组内的日志应具有相同的保留周期、相同的访问权限和相同的指标提取需求。过滤模式设计则要尽量使用命名捕获组,避免用宽泛的关键字匹配,否则容易把无关日志计入指标,导致指标失真。

最后需要理解指标过滤器的两个限制。第一,指标过滤器只支持文本匹配和命名捕获组,不支持复杂的 JSON 解析;如果日志是 JSON 格式,可以先用订阅过滤器转发到 Lambda 做解析,再把解析结果发布为自定义指标。第二,指标过滤器的数值型指标只支持单个数值捕获,不能在一次匹配中发布多个数值指标;如果需要多个数值,应创建多个过滤器。掌握这些边界,才能在日志接入和指标提取之间做出合理的设计选择。