Floci 的 CloudWatch Logs 指标过滤器契约验证:一份来自真实 AWS 观测的行为边界档案
2026/9/20 2:08:41 网站建设 项目流程

Floci 的 CloudWatch Logs 指标过滤器契约验证:一份来自真实 AWS 观测的行为边界档案

【免费下载链接】flociLight, fluffy, and always free - The AWS Local Emulator alternative项目地址: https://gitcode.com/gh_mirrors/fl/floci

本文围绕 docs/services/cloudwatch-metric-filters-verification.md 展开。该文档是 Floci(AWS 本地模拟器)中 CloudWatch Logs 指标过滤器(Metric Filter)模块的一份"契约验证记录":它把在真实 AWS 上实测到的行为模拟器测试期望明确分离开来,并给出可供 Floci 及其 SDK 兼容性测试直接消费的规范化证据文件。读完本文,你将理解 Floci 如何用"三档证据标签"约束模拟行为、148 例模式语料与有状态回放脚本如何工作,以及默认值累加、维度全有全无、系统维度、正则配额、CloudFormation 替换与回滚等关键行为在源码中的落地方式。

一、为什么需要一份"契约验证记录"

CloudWatch Logs 的指标过滤器(Metric Filter)位于日志与指标的交界处:PutMetricFilter定义过滤规则与指标变换,PutLogEvents写入日志时按规则匹配并产出指标数据点。这个行为链路复杂、边界众多(默认值、维度、时间戳、正则配额、CloudFormation 生命周期……),任何模拟器在复刻时都容易"想当然"。

Floci 的做法不是依赖文档猜测,而是用真实验证结果约束模拟实现。该文档开篇就划清了界限:

  • 它是"把 AWS 观测结果与模拟器测试、文档推断需求分开"的记录;
  • 不是"每个 CloudWatch Logs 行为都已测试"的声明。

所有观测数据以 JSON fixture 形式固化在仓库中,作为测试的输入数据而非模拟器输出,从而保证"模拟器测试期望"永远来自 AWS 真值。

二、三档证据标签:区分"实测"与"推断"

文档定义了证据可信度分级,这也是阅读本文所有结论时必须先理解的规则:

标签含义
Verified on Live AWS该场景已对官方 AWS 端点实际执行过;不暗示该场景之外的变体也成立
SDK-tested against FlociAWS SDK 客户端实际驱动过 Floci,证明的是客户端互操作性,而非独立的 AWS 行为真值
Documentation-backed有 AWS API 参考、指南或公开 schema 支撑该需求;不代表真实执行过

这套标签体系在 fixture 中也有一致体现:例如 metric-filter-publishing-aws.json 的顶层字段就是"verification": "Verified on Live AWS",并声明"值是独立的 AWS 结果,而非模拟器生成的期望"。

三、观测环境与方法

文档记录了 2026-09-16 在eu-west-1的真实探测条件,全部使用隔离的合成日志组、指标过滤器和单个 CloudFormation 栈:

  • AWS CLI 版本2.36.45
  • STS 身份私有校验,服务调用使用显式官方端点;
  • 指标查询统一用GetMetricStatistics,周期 60 秒,统计量覆盖SumSampleCountMinimumMaximum
  • 主观测结束于 08:40:45 UTC,混合事件跟进收敛于 08:56:48 UTC,最终系统维度探测与清理完成于 09:06:51 UTC;
  • 收敛判定:早期出现的空结果或部分结果不被当作最终契约,必须反复读取直至结果稳定。

这一点在回放脚本中体现为await_stable机制(见 metric_filter_replay.py):在超时上限内持续轮询,直到"完整结果"连续稳定达到--stable-seconds(AWS 模式至少 30 秒)才算收敛,否则报ReplayError("observations did not converge within the bounded window")

四、模式语料库:148 例合成用例的捕获脚本

文档指出模式语料由 tools/aws-capture/cloudwatchlogs/capture_filter_patterns.py 单独捕获,共148 个合成用例,覆盖三类套件:

套件内容示例
basecases()术语匹配(ERROR-timeout否定、?ERROR可选、短语引号、通配ERR*)、正则(%ERROR|WARN%、锚点、量词、\d+、字符类)、JSON 数值/布尔/IS NULL/NOT EXISTS/通配*/指数/精度、选择器(数组索引、$.a[*]$['a.b']、负索引)、空格分隔字段([a, b, c]、省略号、约束、跨字段 OR)
edgesedge_cases()数值边界(1e309溢出、1e-999下溢、9007199254740993精度)、结构化消息的=/!=/通配、关键字大小写、正则计数(一个模式内 2 个与 3 个正则的差异)
boundariesboundary_cases()数组内IS TRUE/FALSE/NULL、嵌套通配、a/ba@b、unicodecafé" "空白短语、超大指数

该脚本的关键安全设计(值得复刻):

  1. 强制官方端点--endpoint-url必须等于https://logs.{region}.amazonaws.com,否则直接parser.error拒绝执行(capture_filter_patterns.py);
  2. 只读合成请求:仅调用TestMetricFilter,不写任何资源、不使用真实用户日志;
  3. 限流重试:遇到Throttling按 2 的指数退避重试最多 4 次(L206-L214);
  4. 异常白名单:只有InvalidParameterException才会被记为error字段,其他任何异常都会中止捕获(raise RuntimeError("AWS capture blocked: ..."));
  5. provenance 元数据:每个 fixture 都记录sourcecapturedAtregionendpointeventNumberBase等溯源信息,并声明"仅合成数据"。

运行方式是显式 opt-in--profile--region--endpoint-url--output均必填。正常测试只消费它产出的filter-pattern-aws*.json记录文件,无需凭证、无网络访问。

五、有状态回放:metric_filter_replay.py 的双模式设计

模式语料(TestMetricFilter)只验证"匹配判定",而指标发布(默认值、维度、系统维度)需要有状态回放。文档所述的有状态观测记录在metric-filter-publishing-aws.json中,而执行回放的脚本是 compatibility-tests/sdk-test-python/tests/metric_filter_replay.py:

双模式互斥parse_args):

  • --aws:写真实商业分区 AWS,必须同时提供--profile--region--ack-live-writes I_ACCEPT_AWS_WRITES三重确认,region 必须属于botocore已知的商业分区,且--stable-seconds >= 30--timeout >= 120
  • --endpoint:指向localhost/127.0.0.1/::1的回环地址,仅允许 http/https、无凭据,自动使用 dummy 凭证test/test与默认区域eu-west-1

防误删所有权机制owned_group,L80-L95):创建日志组时打上floci-metric-replay: <token>标签;删除前再次验证所有权标签,若标签不匹配或已被占用,拒绝清理;创建失败时绝不删除既有资源。

离线测试守护(test_metric_filter_replay.py):所有 pytest 用例通过 monkeypatch 禁掉socket.connect,确保"导入与正常 pytest 执行绝不联网";并验证参数校验拒绝远程/无防护写入、客户端固定官方端点(即使环境变量被污染)、错误输出做脱敏(不泄露 profile、账号等私有值)。

观测矩阵scenarios())对应 fixture 中的四组数据:

  • metricDefaults:混合批次、先命中后未命中、先未命中后命中、纯未命中(quiet)四类场景;
  • extraction:缺失值/显式 null/非数值字符串/数值控制组;
  • ordinaryDimensions:完整、缺 A、缺 B、全缺四种事件;
  • systemDimensions@aws.account@aws.region、两者组合。

每个场景占据独立的事件时间分钟,且每个场景的期望值直接从 fixture 读取(不可变),这保证了可复现性。

六、观测行为全集(Verified on Live AWS)

下表是文档的核心——每一行都在其声明范围内于真实 AWS 上验证过。这是 Floci 实现指标过滤器时必须满足的行为契约:

领域场景观测结果
组级正则配额五个各含两个正则表达式的指标过滤器接受;第六个含正则的过滤器被拒,返回LimitExceededException。配额按"含正则的过滤器"计数
正则替换把含正则的模式换成纯文本,之后在配额满时再从纯文本改回正则第一次修改释放了槽位;被拒的更新保留了原纯文本定义;删除另一个含正则的过滤器后更新成功
模式内正则配额一个模式里放三个表达式拒绝,InvalidParameterException;两个表达式可接受
默认值贡献一次请求三个未命中事件,默认值 7,随后无更多摄入Sum 21、SampleCount 3、Minimum 7、Maximum 7;后续空闲分钟不再出现额外数据点
混合批次模式ERROR、指标值 3、默认 7;一个批次含ERRORINFOERRORSum 13、SampleCount 3、Minimum 3、Maximum 7。匹配事件不会抑制未命中事件的默认值
分离批次同一过滤器在同一事件时间分钟内先命中后未命中、或先未命中后命中两种顺序都收敛到 Sum 10、SampleCount 2
迟到追加先观测到默认值 11,之后同一历史分钟内追加带时间戳的匹配值 3Sum 14、SampleCount 2。先前的默认值不被撤回
时间戳落点新摄入的倒签、当代、未来时间戳事件指标出现在事件时间戳所在分钟;创建过滤器之前时间戳的事件,只要摄入发生在创建之后也可工作
提取值用独立判别字段匹配;指标值引用$.value;默认 7缺失或显式 null 值产生 7;数值 3 产生 3;非数值字符串在完整观测窗口内不产生数据点
变换引用校验JSON 模式中未提及的标量指标值与维度选择器;然后通配指标值$.values[*]未提及的标量选择器被接受;通配指标值被拒,InvalidParameterException。这四个规范化校验观测见metric-filter-reference-validation-aws.json
无默认值同样的提取场景,不设默认值只有合法数值产生数据点
普通维度$.a配维度 A、$.b配维度 B;发送完整、缺 A、缺 B、全缺事件完整集产生带维度的序列;三个不完整集产生无维度样本,而非部分维度序列或丢弃事件
默认值 JSON 类型原始签名请求中数值defaultValue: 7,然后字符串defaultValue: "7"数字:HTTP 200 空 body;字符串:HTTP 400SerializationExceptionSTRING_VALUE cannot be converted to Double
默认值与维度只有系统维度时的默认值;然后带普通变换维度的默认值仅系统维度被接受;普通维度 + 默认值被拒绝
系统维度发布常规非中心化日志组;模式ERROR、值 3、默认 7;一个ERROR一个INFO;依次请求 account、region、两者匹配值获得请求的维度;模式未命中的默认值无维度。每种情况恰好两个样本、总计 10
维度数量两个普通维度 + 一个系统维度,然后 + 两个系统维度总计三个接受;总计四个拒绝,InvalidParameterException
选择操作符fieldSelectionCriteria中的词形ANDOR接受
变换日志标志GetTransformer返回空配置的新建组上设置truefalse均接受并可往返(round-trip);随后的账号策略读取返回零个 transformer 策略。不测试 transformer 执行
CloudFormation 身份显式命名的指标过滤器;栈输出RefDescribeStackResource两者都返回过滤器名称本身,不是LogGroupName|FilterName
CloudFormation 替换重命名显式命名的过滤器旧过滤器的DELETE_IN_PROGRESS/DELETE_COMPLETE先于新过滤器的CREATE_IN_PROGRESS/CREATE_COMPLETE;栈完成更新
CloudFormation 回滚在合法的WARN/值 2 定义之后,用非法模式{重命名旧过滤器被删除、替换创建失败、回滚重建先前的名称与定义;日志读取、变化的 creationTime、Ref 与还原后的模板共同确认;被尝试的过滤器没有存活
CloudFormation 替换策略安装UpdateReplacePolicy: Retain后重命名该类型仍然先删除旧过滤器再创建替换;无DELETE_SKIPPED事件,仅替换件存活。不适用于其他资源类型或栈 DeletionPolicy
TestMetricFilter 编号记录的模式语料请求(含匹配与不匹配消息)匹配事件编号从 1 开始
TestMetricFilter JSON 提取记录的 JSON 模式匹配公开的extractedValues是空对象;内部字段提取仍需要以发布指标值

七、关键行为解读:从观测到实现

7.1 默认值按事件累加,不是"每分钟一次"

文档特别强调:默认值观测推翻了"每分钟默认值封顶一次"的实现猜想。CloudWatch 按分钟聚合事件贡献;一个抑制同分钟内不同未命中事件的"分钟累加器"不是等价实现。

Floci 源码严格按"每个事件一个贡献"实现:在 CloudWatchLogsMetricFilterService.java 的evaluate方法中,对每个日志事件独立执行pattern.match,未命中时value = t.getDefaultValue(),每个匹配/未命中事件都生成一个独立的Publication。对应的 Floci 测试在 CloudWatchLogsMetricFilterPublishingTest.java 中验证:三个INFO未命中立即产生Sum 21、SampleCount 3,而下一个空闲分钟为 0。

7.2 迟到追加不撤回

"先默认值 11,后匹配值 3" 收敛到 Sum 14,说明已发布的数据点不会被重算。Floci 测试 lateMatchAppendsWithoutRetractingAlreadyPublishedDefault 精确复刻:先摄入INFO得到 11,再摄入同一分钟内的ERROR追加到 14。

7.3 时间戳按事件时间落点

事件时间戳(而非摄入时间)决定指标落在哪个分钟,且支持未来时间戳与"创建前时间戳 + 创建后摄入"。源码中Publication记录的是logEvent.getTimestamp() / 1000(毫秒转秒),测试 timestampsComeFromEventsIncludingBeforeCreationAndFutureNotIngestionTime 直接验证未来分钟与创建前分钟两种情况。文档同时澄清:这不意味着"过滤器创建前已摄入的日志会被追溯处理"。

7.4 提取值的三态语义

$.value缺失或显式 null → 使用默认值 7;数值 3 → 3;非数值字符串 → 不产生数据点。源码注释精确描述了这一规则:"Preserve raw text: only missing/null falls back. A present nonnumeric value skips."(只对缺失/null 回退到默认值,存在的非数值跳过)。

7.5 普通维度 all-or-none

维度要么全有、要么全无:不完整的维度集产生无维度样本,绝不产生部分维度序列。fixture 中ordinaryDimensionsabsentSeries明确列出{"A": "alpha"}{"B": "beta"}两个"不存在的序列"。源码在evaluate中遇到任何维度提取为 null 就dimensions.clear()并中断。

7.6 系统维度只随匹配事件发布

@aws.account/@aws.region系统维度作用于匹配事件,而模式未命中的默认值无维度。系统字段校验与维度上限在 MetricFilterRules.java 中:emitSystemFieldDimensions只允许这两个字段,且与普通维度合计不得超过MAX_DIMENSIONS = 3

7.7 默认值的 wire 级类型校验

原始签名请求级别的观测(metric-filter-default-value-wire-aws.json)证明:defaultValue传 JSON 字符串"7"会在协议反序列化层被拒(HTTP 400、SerializationExceptionSTRING_VALUE cannot be converted to Double),而数值7返回 200。这说明该约束发生在 AWS 服务端序列化层,模拟器在更外层校验同样合法——关键是必须拒绝。Floci 在 LogsMetricFilterCfnProvisioner.java 与CloudWatchLogsMetricFilterService.requireTransformation中均强制defaultValue为有限数值。

7.8 通配指标值被拒绝

metricValue: "$.values[*]"这类通配引用在 AWS 端返回InvalidParameterException: Invalid metric transformation: metric value $.values[*] must be a valid number。该观测在 metric-filter-reference-validation-aws.json 的四个规范化校验用例中记录(含"未提及的标量选择器被接受"作为正面对照)。源码的requireTransformation同样要求:metricValue要么是合法数字,要么是模式能提供的单个值字段pattern.declaresSingleValueField(value)),通配引用不满足此条件。

7.9 正则配额:共享的组级 5 个 + 模式内 2 个

  • 组级:每个日志组最多 5 个含正则的过滤器,且该配额由指标过滤器与订阅过滤器共享validateFilterRegexQuota同时扫描 metricFilterStore 与 subscriptionFilterStore,见 CloudWatchLogsService.java,常量MAX_REGEX_FILTERS_PER_LOG_GROUP = 5);
  • 模式内:单个模式最多 2 个正则表达式(FilterPattern.MAX_REGEXES = 2,见 FilterPattern.java)。

文档明确标注:该共享配额是Documentation-backed(Put 操作参考 + 正则语法指南),因为有状态探测未创建订阅目标,不构成对实时混合写入配额行为的实证。

八、CloudFormation 生命周期:替换顺序与回滚

8.1 身份:Ref与物理 ID 都只是过滤器名称

与直觉(LogGroupName|FilterName)不同,AWS::Logs::MetricFilterRefDescribeStackResource返回的都是过滤器名称本身。源码中setIdentity将 physicalId 设为identity.name(),日志组作为私有元数据(__FlociMetricFilterLogGroupName)存储(LogsMetricFilterCfnProvisioner.java)。

8.2 替换 = 先删后建

当前AWS::Logs::MetricFilterregistry schema 指定delete_then_createmetric-filter-cfn-aws.json记录了完整事件序列:UPDATE_IN_PROGRESS → DELETE_IN_PROGRESS → DELETE_COMPLETE → CREATE_IN_PROGRESS → CREATE_COMPLETE → UPDATE_COMPLETE,删除理由是"AWS 官方要求的替换:先删除现有资源,再创建新资源"。源码provision()replacement分支完全按此顺序执行并上报事件。

8.3 回滚恢复完整旧定义

非法模式{的重命名探测产生了完整的回滚事件链:尝试创建失败 → 回滚删除尝试件 → 重建先前的名称与定义。fixture 记录priorInstanceWasRecreated: trueattemptedFilterSurvived: falsestoredTemplateRestored: true。源码通过快照机制(SNAPSHOT属性保存完整旧定义)在rollbackUpdate中恢复;文档也确认了creationTime变化证明是"重建"而非"复用"。

8.4UpdateReplacePolicy: Retain对 MetricFilter 无效

一个反直觉的实测结论:即使安装UpdateReplacePolicy: Retain,重命名时该类型仍然先删后建,没有DELETE_SKIPPED事件,最终只存活替换件。源码注释明确:"AWS replaces this type by deleting first, including with UpdateReplacePolicy Retain."(AWS 通过先删除来替换该类型,包括带 Retain 策略时)。文档同时谨慎限定:这不描述其他资源类型或栈级 DeletionPolicy。

九、Floci 源码中的契约落地与工程化

9.1 服务实现与发布队列

CloudWatchLogsMetricFilterService.java 是核心实现,其常量直接对应文档观测到的 AWS 行为:

常量对应行为
MAX_FILTERS_PER_LOG_GROUP100组级过滤器上限
MAX_DIMENSIONS(MetricFilterRules)3普通 + 系统维度合计上限
MAX_REGEX_FILTERS_PER_LOG_GROUP5组级正则过滤器共享配额
MAX_REGEXES(FilterPattern)2单模式正则数上限
MAX_QUEUED_SAMPLES100_000发布队列容量
MAX_PENDING_SAMPLES10_000失败重试保留样本数

发布链路采用异步队列PutLogEvents触发onLogEventsIngested事件,每个过滤器的贡献被评估成不可变Batch入队,由单线程发布者(logs-metric-publisher)写出到 CloudWatch Metrics 服务。失败样本进入pending重试表,每秒重试一次,且带 60 tick 的聚合告警;队列放不下时在请求线程内联写入而非丢弃;重置/停止时通过resetGeneration代际计数丢弃属于旧状态的样本。这套设计保证"模拟器测试期望来自 AWS 真值"的 fixture 可以被确定性地复现。

9.2 测试如何消费 fixture

  • Java 端:CloudWatchLogsMetricFilterPublishingTest.java 直接读取打包的metric-filter-publishing-aws.json,把每一节(metricDefaultsextractionordinaryDimensionssystemDimensions)作为输入数据逐条断言 Floci 的发布结果。类注释强调:"AWS observations are input data, never regenerated from emulator output."(AWS 观测是输入数据,绝不由模拟器输出重新生成);
  • Python 端:fixture 打包在 compatibility-tests/sdk-test-python/tests/fixtures/metric-filter-publishing-aws.json,与回放模块同目录,test_fixture_is_packaged_beside_the_module_not_in_a_repository_ancestor甚至校验它不落在仓库祖先目录;
  • Java SDK 兼容测试:MetricFilterFixture.java 从classpath:/cloudwatchlogs/metric-filter-publishing-aws.json加载同一份 oracle。

9.3 未测量边界:诚实声明

文档明确列出不在观测范围内的边界,Floci 因此不宣称支持:

  • null 或重复的系统维度条目、所有非法维度值变体;
  • 完整 transformer 执行、中心化源元数据、崩溃恢复、发布失败注入;
  • 组级 100 过滤器上限的替换、与其他栈资源的冲突;
  • 维度通配引用、带字面星号的属性名(metric-filter-reference-validation-aws.jsonunmeasured字段)。

十、证据文件索引

文件内容
docs/services/cloudwatch-metric-filters-verification.md契约验证记录本体:三档标签、观测环境、行为全集、未测量边界
tools/aws-capture/cloudwatchlogs/capture_filter_patterns.py148 例合成模式语料的 opt-in 捕获脚本(base/edges/boundaries 三套件)
src/test/resources/cloudwatchlogs/metric-filter-publishing-aws.json有状态发布观测的规范化记录(默认值、提取、普通维度、系统维度)
src/test/resources/cloudwatchlogs/metric-filter-default-value-wire-aws.json原始签名请求级别:字符串"7"→ 400SerializationException;数值 7 → 200
src/test/resources/cloudwatchlogs/metric-filter-reference-validation-aws.json四项变换引用校验观测(未提及标量接受、通配拒绝)
src/test/resources/cloudwatchlogs/metric-filter-cfn-aws.jsonCloudFormation 身份、替换顺序、回滚、Retain 策略观测
compatibility-tests/sdk-test-python/tests/metric_filter_replay.py有状态回放脚本(--aws/--endpoint双模式、所有权保护、收敛轮询)
compatibility-tests/sdk-test-python/tests/test_metric_filter_replay.py离线校验(禁网、参数守卫、输出脱敏、场景矩阵断言)
src/test/java/io/github/hectorvent/floci/services/cloudwatch/logs/CloudWatchLogsMetricFilterPublishingTest.javaFloci 端消费 fixture 的发布行为测试
src/main/java/io/github/hectorvent/floci/services/cloudwatch/logs/CloudWatchLogsMetricFilterService.java指标过滤器服务:Put/Describe/Delete/Test 与异步发布队列
src/main/java/io/github/hectorvent/floci/services/cloudwatch/logs/MetricFilterRules.java系统字段、选择标准、维度上限校验
src/main/java/io/github/hectorvent/floci/services/cloudformation/provisioners/LogsMetricFilterCfnProvisioner.javaCFN 身份、替换、回滚与所有权快照

十一、参考来源

文档所列依据(均为 AWS 官方资料,此处按名称列出,便于对照):

  • PutMetricFilter API 参考;
  • PutSubscriptionFilter API 参考(用于共享正则配额);
  • TestMetricFilter API 参考;
  • Filter and pattern syntax(过滤与模式语法指南);
  • Metric-filter values and dimensions(指标过滤器值与维度指南);
  • Metric-filter concepts(指标过滤器概念);
  • CloudFormation MetricFilter 参考;
  • CloudFormation registry schemas(delete_then_create替换策略的来源)。

结语

这份契约验证文档的价值在于边界清晰:哪些行为有 AWS 实测背书、哪些只是文档支撑、哪些完全未测量,都被显式记录并固化为可消费的 JSON fixture。Floci 的实现层(服务常量、配额校验、发布队列、CFN provisioner)与测试层(Java/Python 双端 fixture 消费)都围绕这份契约展开,形成"真实 AWS 观测 → 规范化证据 → 模拟实现 → 兼容性验证"的闭环。对于希望在模拟器上获得高保真 CloudWatch Logs 指标行为的开发者,这份文档与其配套脚本、fixture 是理解行为边界与复现验证流程的最佳起点。

【免费下载链接】flociLight, fluffy, and always free - The AWS Local Emulator alternative项目地址: https://gitcode.com/gh_mirrors/fl/floci

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询