fix(python): 补中间件服务端指标;中间件指标统一去掉 obs_ 前缀 - #12
Conversation
python/ 此前缺 HTTP 服务端指标:docs/微服务可观测性建设技术设计.md 与
spec/metrics-format.md 都声明中间件默认暴露 obs_http_server_*,而 python/ 下
grep http_server 零命中,能力矩阵里的「同上」是空头承诺。
指标(spec/metrics-format.md「默认暴露的中间件指标」)
- 新增 Metrics.http_server() → 懒注册的 _HttpServer 句柄,FastAPI / Flask / Django
三个适配器共用。缓存是必需的:prometheus_client 对同名指标重复注册直接抛 ValueError。
- path label 取**路由模板**:Starlette scope["route"].path / Flask url_rule.rule /
Django resolver_match.route,取不到(404)归 unmatched。实测 FastAPI 在
BaseHTTPMiddleware 的 call_next 之后 scope["route"] 可见,无需自行归一。
- 桶边界显式钉住 [0.005 … 10],与 Go / Node 对齐 —— prometheus_client 自带默认
多出 .075/.75/7.5,不钉就与另两个语言对不上。
- collect_server_metrics=False 可关闭(服务已有等价 instrumentation)。
- 视图抛异常时补记 500 再原样抛出,避免漏计。
其余四处
- Metrics.__init__ 构造时即做三级解析(显式 > OBS_* > 内置默认),同 Java 修复:
此前空值会被写进 series,与其它语言对不上、按 label 过滤静默漏数。
- metrics.init() 与 log.init() 统一为「可重复调用、最后一次生效」(重建实例与
注册表),此前是首次调用生效、后续参数被静默丢弃。
- log.init() 不再 root.setLevel(DEBUG):root 是宿主应用的全局开关,会连带让应用
自己挂在 root 上的 handler 开始收 DEBUG。级别改设在 get_logger() 交出的 logger
上;log.get_logger() 不带名字时返回 SDK 自己的 logger(obs_sdk)而不是 root。
- pyproject [test] extra 补 fastapi/flask/django:中间件单测用 importorskip,不装
会静默跳过 —— 本地/CI 全绿而中间件零覆盖。CI 随之并成 pip install -e 'python[test]'。
测试
- 新增 tests/test_env.py(6 例)覆盖 _env 三级解析;这层此前零覆盖,Java 的同类
缺陷正是从这里漏过去的。
- tests/test_middleware.py 重写:每个框架断言真实 text 输出里的
obs_http_server_requests_total,含路由模板(/items/{item_id} 而非 /items/12345)、
404 → unmatched、异常 → 500、直方图桶边界。
- test_metrics / test_log 补 init 重建、构造兜底、不动 root 级别三处。
回归验证:只 stash python/obs_sdk 回旧实现跑同一套测试 → 10 failed / 18 passed,
失败项即上述新断言;恢复后 28 passed、0 skipped(收集数 28 说明三个框架都在跑)。
Co-Authored-By: Claude Code <noreply@anthropic.com>
Welcome To opensourceways CommunityHey @TangJia025 , thanks for your contribution to the community. Bot Usage ManualI'm the Bot here serving you. You can find the instructions on how to interact with me at Here . That means you can comment below every pull request or issue to trigger Bot Commands. Contact GuideIf you have any questions, please contact the SIG: infratructure , |
CLA Signature PassTangJia025, thanks for your pull request. All authors of the commits have signed the CLA. 👍 |
Linking Issue Notice@TangJia025 , the pull request must be linked to at least one issue. |
命名口径统一。spec 原先要求中间件公共指标带 SDK 保留前缀 obs_,但四语言里**只有 Python 真的带**(本 PR 上一个 commit 刚加),Go / Node 注册的是无前缀名、注释却写着 obs_,Java 走 Actuator 更无从带。前缀的收益也不成立: - 同一条 series 已带 service label,名字里再编一遍前缀是重复信息; - 带前缀会让「同一指标名下的跨服务查询」失效,每个服务得各写一套名字; - 官方 instrumentation 的默认名不带这类前缀(Micrometer 的 http_server_requests_seconds),自造前缀要多做一层名字映射,与「薄封装、 不自研 instrumentation」冲突。 故 spec 改为**不加任何前缀**,四个实现与全部文档同步。 spec/metrics-format.md - 删掉 obs_ 保留前缀规则,补上「为什么中间件指标不加前缀」的三条理由。 - 指标名改 http_server_requests_total / http_server_request_duration_seconds / log_entries_total(末者四语言都还没实现,改名只为口径一致)。 - duration 的 label 补上 status_code:Go / Node / Python 三个实现本来就带, 契约是唯一掉队的一方(按状态码看时延是常见诉求)。 - 「服务要求」补一句同构边界:service/env/instance/community 是强约束,服务端指标的 名字与其余 label 由各语言官方库的既定形状决定,SDK 不改名。 实现 - Python:两个常量去前缀(本 PR 引入,尚未合入,一并纠正)。 - Go:middleware.go 文件头与行内注释、ginmw.go 文件头从「用 obs_ 前缀」改为无前缀 —— 此前这三处注释与代码注册的名字是矛盾的(代码一直是无前缀)。 - Node:middleware.js 文件头注释同上。 - Java:ObsSdkConfig / ObsMetrics 的 namespace javadoc 不再建议填 "obs_",改为 「用来拼业务指标的 <service>_ 前缀」;Java 无代码改动。 文档:README.md、AGENTS.md、go/README.md、node/README.md、python/README.md、 docs/微服务可观测性建设技术设计.md 的 obs_ 表述同步。 四语言验证:Python 28 passed / Go go vet + test ok / Node 10 pass / Java 38 passed。 Co-Authored-By: Claude Code <noreply@anthropic.com>
CLA Signature PassTangJia025, thanks for your pull request. All authors of the commits have signed the CLA. 👍 |
背景
spec/metrics-format.md与docs/微服务可观测性建设技术设计.md都声明 SDK 中间件默认暴露obs_http_server_*两个指标,但python/下grep http_server零命中 —— 文档、Go、Node 都有,Python 独缺,能力矩阵里的「同上」是空头承诺。
补实现时顺带发现:「
obs_前缀」这套约定四语言里只有本 PR 的 Python 真的照做,Go / Node 注册的是无前缀名、注释却写着
obs_,Java 走 Actuator 更无从带。因此本 PR 的后半段把命名口径反了过来,以「不加任何前缀」为准(详见下一节)。
改动一:补中间件 HTTP 服务端指标
http_server_requests_totalmethodpathstatus_codehttp_server_request_duration_secondsMetrics.http_server()→ 懒注册的_HttpServer句柄,三个适配器共用。缓存是必需的:prometheus_client对同名指标重复注册会直接抛ValueError,不缓存中间件多请求下必挂。path取路由模板:Starlettescope["route"].path/ Flaskurl_rule.rule/Django
resolver_match.route,取不到(404)归unmatched。实测 FastAPI 在BaseHTTPMiddleware的call_next之后scope["route"]可见,不需要自行归一化。[0.005 … 10]:prometheus_client自带默认多出.075/.75/7.5,不钉就会与 Go / Node 的桶对不上。
collect_server_metrics=False可整体关闭(服务已有等价 instrumentation 时)。改动二:命名口径反转 —— 中间件指标不加任何前缀
spec原先要求中间件公共指标带 SDK 保留前缀obs_。实际上:obs_?middleware/ginmwhttp_server_requests_total等obs_,与代码矛盾)lib/middlewareobs_)obs_即「带前缀」既没有实现基础,收益也不成立:同一条 series 已带
servicelabel,名字里再编一遍是重复信息;带前缀会让「同一指标名下的跨服务查询」失效;官方 instrumentation 的默认名(Micrometer 的
http_server_requests_seconds)本就不带这类前缀,自造前缀要多做一层名字映射,与「薄封装、不自研instrumentation」冲突。故 spec 改为不加任何前缀,四语言实现与全部文档同步:
spec/metrics-format.md:删掉obs_保留前缀规则并写明三条理由;指标名改为http_server_requests_total/http_server_request_duration_seconds/log_entries_total。duration的 label 补上status_code:Go / Node / Python 三个实现本来就带,契约是唯一掉队的一方(按状态码看时延,如只看 5xx 的 P99,是常见诉求)。
service/env/instance/community是强约束,服务端指标的名字与其余label 由各语言官方库的既定形状决定,SDK 只装配不改名 —— 所以 Java 的
http_server_requests_seconds/status/uri与另三语言的http_server_requests_total/status_code/path并不逐字相同,跨语言告警规则请对齐通用 label。obs_前缀」的注释;Python:两个常量去前缀。namespace的 javadoc 不再建议填"obs_",改为「用来拼业务指标的<service>_前缀」,无代码改动。README.md、AGENTS.md、go/README.md、node/README.md、python/README.md、docs/微服务可观测性建设技术设计.md的obs_表述全部同步。改动三:与 Java 对齐的五处
Metrics.__init__构造时即完成三级解析(显式参数 >
OBS_*> 内置默认)。此前空值会被写进 series,与其它语言对不上、按 label 过滤静默漏数。init语义:metrics.init()改为「可重复调用、最后一次生效」(重建实例与注册表),与
log.init()一致;此前是首次调用生效、后续参数被静默丢弃。log.init()移除root.setLevel(DEBUG)—— root 是宿主应用的全局开关,压到 DEBUG 会连带让应用自己挂在 root 上的 handler 开始收 DEBUG 记录。级别改设在
get_logger()交出的 logger 上;log.get_logger()不带名字时返回 SDK 自己的 logger(obs_sdk)而不是 root。[test]extra 补fastapi/flask/django:中间件单测用pytest.importorskip,不装会静默跳过—— 本地/CI 看着全绿而中间件实际零覆盖。CI 随之并成一条
pip install -e 'python[test]'。测试
tests/test_env.py(6 例)覆盖_env三级解析 —— 这层此前零覆盖,Java 的同类缺陷正是从这里漏过去的。tests/test_middleware.py重写:每个框架断言真实 text 输出里的http_server_requests_total,含路由模板(
/items/{item_id}而非/items/12345)、404 →unmatched、异常 → 500、直方图桶边界。test_metrics/test_log补init重建、构造兜底、不动 root 级别三处。验证
只
git stash回python/obs_sdk旧实现跑同一套测试:恢复后(四语言全跑):
遗留(未处理)
pathlabel 用原始 URL:middleware.go的r.URL.Path、ginmw 的c.Request.URL.Path(gin 明明有c.FullPath()可用)、Node 的req.originalUrl。Python 用的是路由模板,三语言
path语义不一致,且 Go / Node 有基数风险。属另一个改动,未动。docs/进度追踪.md:19(「存在无兜底缺陷」)与设计文档 §4.2.4 / R5 在 PR fix(java): 部署级字段兜底、community 跨请求串染 + ObsFilter 测试覆盖 #9 合入后已过时。Java 的偏差来自 PR fix(java): 部署级字段兜底、community 跨请求串染 + ObsFilter 测试覆盖 #9,早于本 PR。
顺带修掉的文档错误(非本次主题,单独说明)
python/README.md:flask_middleware(app, resolver=lambda: "openeuler")少一个参数,resolver 签名是
Callable[[request], Optional[str]],照抄会TypeError;同处
ObsMiddleware类名写错(实为DjangoMiddleware)。obs_sdk/metrics.py模块 docstring:metrics.counter("meeting_created_total", "…", "kind")把字符串当 labelnames 传,会被
list("kind")拆成['k','i','n','d']四个 label,改为["kind"]。🤖 Generated with Claude Code
AI 使用声明
当前 PR 是否有 AI 参与:
python/实现,用户逐条指定处理方式;随后按要求梳理
obs_前缀分布与status_codelabel 现状,据结论做命名口径反转