BiSheng v2.6.0 指标日志可观测性(F042)实战:结构化日志埋点与监控层解析全解析
【免费下载链接】bishengBISHENG is an open LLM devops platform for next generation Enterprise AI applications. Powerful and comprehensive features include: GenAI workflow, RAG, Agent, Unified model management, Evaluation, SFT, Dataset Management, Enterprise-level System Management, Observability and more.项目地址: https://gitcode.com/GitHub_Trending/bi/bisheng
导读
本文围绕 BiSheng(开源 LLM DevOps 平台)v2.6.0 的042-metric-log-observability(F042)Feature,完整讲解"结构化日志埋点 + 监控层解析"这一可观测性方案的落地全貌:后端如何以零侵入、零阻塞的方式,把数据库、对象存储、模型调用、E+ 站内信四类子系统的原始测量打成契约化的BS_METRIC日志行,再由外部日志管线(ELK / Loki / ES)聚合成 P95、QPS、成功率等指标。读完本文,你将掌握MetricLogConf配置、emit_metric埋点工具、四类埋点接入位置、日志契约字段字典、监控层查询示例,以及本方案背后 12 个"反直觉事实"与设计决策,可直接用于部署观测或复刻同类埋点体系。
1. Feature 定位:做什么、不做什么
F042 的目标非常聚焦:为四类子系统埋结构化指标日志,由外部监控层采集并解析出指标。
| 指标域 | 要覆盖的指标 |
|---|---|
| 数据库 | 查询耗时 P95、QPS、慢查询明细、连接池等待数 |
| 对象存储 | 上传/下载请求成功率(排除 401/403 签证过期,只计超时/5xx/连接失败)、上传/下载耗时 |
| 模型调用 | TTFT(首 Token 延迟)、调用成功/失败 |
| E+ 站内信 | 接口调用事件(成功/失败/被短路、耗时) |
同时明确列出非目标(design.md §1):
- 不引入
prometheus_client、不建/metrics端点、不搭 Grafana——指标的聚合、存储、看板、告警全部是监控层的职责,后端只负责"把每条原始测量打成契约化日志"; - 不做进程内百分位计算(预算好的 P95 跨进程不可合并,详见 §3 决策 2);
- 不改动模型调用现有的 ES telemetry 链路(仅并行补一行日志)。
该 Feature 的交付状态:design.md 与 tasks.md 均已过审,实现 12/12 完成(Wave 1 + Wave 2 + Wave 3 全部 ✅),各任务说明见 tasks.md。
2. 方案选型:为什么是"写日志"而不是 Prometheus
design.md §3 对三种交付方式做了完整对比(决策 1),这是理解整个 Feature 的前提:
| 方案 | 优点 | 缺点 |
|---|---|---|
| A. 结构化日志 + 监控层解析 | 零新依赖;对多进程天然透明;复用已有HTTP_ACCESS_METRIC先例 | 依赖外部日志管线的解析/聚合能力;指标有采集延迟 |
B. Prometheus/metrics进程内注册表 | 业界标准,histogram_quantile算 P95 | 多进程需 multiproc 共享目录;Celery/Linsight worker 非 HTTP 服务,暴露/metrics需额外端口或 Pushgateway;引入新依赖 |
| C. 复用现有 ES telemetry 事件流 | 模型调用已在用,跨进程天然 | DB 超高频不能逐条写 ES;需进程内预聚合反而更重 |
选定 A。核心原因是 BiSheng 采用多进程部署(FastAPI web 进程 + Celery workers + Linsight worker),大量 DB 查询、模型调用、文件存储发生在 worker 内,而日志方案各进程各写各的、采集端集中汇,一次性绕开了多进程/metrics聚合的全部复杂度。
三个派生决策同样关键:
- 决策 2(打原始测量值):进程内算好的 P95 跨进程不可合并——把各 worker 的 P95 再平均/取 max 都是错的;只有原始样本(或直方图桶计数)能跨进程汇总成一个池子算真实全局 P95。这正是"写日志、监控层聚合"分工的技术前提。
- 决策 3(DB 指标拆两种日志行):高 QPS 下每条 SQL 一行日志量不可接受;纯"仅慢查询"又算不出 QPS 和完整 P95。最终采用"慢查询明细行 + 周期汇总行":慢查询逐条打(含
op/elapsed_ms供排查),另每进程每 N 秒打一行汇总,携带count(→QPS)+ 延迟直方图桶计数(→P95)。 - 决策 4(对象存储成功率口径):401/403 是签证/权限过期的预期场景,不代表存储服务不可用,单列
result=excluded不进失败分母,只有超时/5xx/连接失败算result=error。
另外还有决策 5(埋点收敛点)与决策 6(连接池等待数用饱和度反推),分别见 §5 与 §6.2。
3. 配置:MetricLogConf参数详解与运维开关
F042 的所有行为都由MetricLogConf配置类驱动(T001),定义于 src/backend/bisheng/core/config/settings.py#L665-L689,作为metric_log字段挂到全局Settings(settings.py#L756)。
| 配置键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | bool | true | 总开关,关闭后不打任何BS_METRIC行 |
db | bool | true | 域开关:控制db_query/db_query_agg/db_pool三类 DB 指标 |
obj_storage | bool | true | 域开关:控制对象存储 put/get 指标 |
model_invoke | bool | true | 域开关:控制模型调用 TTFT/status 指标 |
eplus | bool | true | 域开关:控制 E+ 站内信调用事件指标 |
db_slow_query_ms | int | 200 | 慢查询明细阈值(毫秒),查询耗时 ≥ 该值才打db_query明细行 |
db_agg_window_s | int | 10 | db_query_agg汇总行 flush 与db_pool池采样窗口(秒) |
在config.yaml中对应写入(docker/bisheng/config/config.yaml 的部署形态参考):
metric_log: enabled: true db: true obj_storage: true model_invoke: true eplus: true db_slow_query_ms: 200 db_agg_window_s: 10从源码实现看,配置读取是懒加载 + fail-open:_get_conf()在启动极早期配置未就绪时返回None,此时_domain_enabled默认放行(conf is None → True),避免启动阶段埋点静默丢失;未知/新增 domain 也默认放行(见 metric_log.py#L44-L62)。record_db_query在读开关时同样做了双重判定——enabled与db任一关闭即整条链路零开销返回(metric_log.py#L267-L269)。
4. 核心工具:metric_log.py的格式化、直方图与池采样
T002/T003 交付了埋点核心工具模块 src/backend/bisheng/common/services/metric_log.py,职责边界极其清晰:只负责格式化并输出一行日志,所有聚合(P95/QPS/成功率)都留给监控层。
4.1emit_metric:统一入口与 logfmt 转义
def emit_metric(domain: str, **fields) -> None: """Emit one ``BS_METRIC`` line for ``domain``. Best-effort: never raises."""格式化规则(与测试 test_metric_log.py 一一对应):
- 固定 marker
BS_METRIC+domain=<域>开头; None字段省略不打(解析时按缺省处理);- bool 渲染为
1/0; - float 四舍五入到 3 位小数(
5200.456789 → 5200.457),且不出现科学计数法; - 含空格、
"、=、换行、制表符等特殊字符的字符串值加双引号并转义(\→\\、"→\"、换行/制表符→空格),保证整行 logfmt 可解析。
最关键的设计约束——零阻塞与异常隔离(design §5 坑 4):emit_metric整体包裹 try/except,埋点任何失败只打一条 debug 日志、绝不向调用方抛异常,绝不改变任何原有控制流/返回值/异常语义。测试test_emit_metric_never_raises_on_bad_value专门验证了这一点(传入__str__会抛异常的畸形对象也不会导致崩溃)。这是因为 DM 的dmAsync是"假异步"(内部同步阻塞事件循环),埋点里任何同步 IO / 锁竞争都会放大成 worker 冻结——所以埋点只做内存计数 + 非阻塞日志,周期 flush 不加全局锁。
4.2DbQueryHistogram:用日志搬运 Prometheus histogram
DB 查询延迟直方图是"DB 双行"方案(决策 3)的核心数据结构(metric_log.py#L111-L163):
- 桶上界(毫秒):
5, 10, 25, 50, 100, 250, 500, 1000++inf; record(elapsed_ms, now):O(log 桶数) 的bisect_left定位,边界值归属当前桶(elapsed == bound落在该桶,Prometheus<=语义),全程加锁线程安全(cursor 事件在多线程触发);maybe_flush(now, window_s):窗口期满返回window_s / count / sum_ms / le_5 ... le_inf聚合字典并清零,否则返回None。le_*是累积桶(le_10含le_5,le_inf == count),累积计数跨进程、跨窗口可直接相加——这正是"监控层 sum 后histogram_quantile(0.95)得真实全局 P95、count/window得 QPS"的技术前提;- 计数器挂在模块级单例
db_histogram(design §5 坑 7:SQLAlchemysessionmaker每次调用新建、只有 engine 是单例,计数器若挂 session 会丢数据)。
时间注入约定:now与window_s均由调用方传入,本模块不读时钟——保证窗口可确定性测试(测试中window_s=0可强制每次查询立即 flush)。
4.3maybe_emit_pool_gauge:连接池饱和度采样
连接池"等待数"用饱和度反推而非直接计数(决策 6,design §5 坑 8):SQLAlchemy 不提供"当前排队数"的公开 API,PoolEvents.checkout在拿到连接之后才触发、拿不到等待时长。实现(metric_log.py#L198-L230):
_PoolSampler每窗口至多采样一次;- 只读稳定的公开方法
checkedout() / checkedin() / size()(坑 10:pool.overflow()语义随版本可能为负,max_overflow无公开方法,从DatabasePoolConf读); capacity = size + max_overflow,at_capacity = checked_out >= capacity;- 异步 engine 的池要从
async_engine.sync_engine.pool取(坑 9),AsyncAdaptedQueuePool同样有这三个方法; - 没有
checkedout方法的池(SQLite 开发/测试用的StaticPool/SingletonThreadPool)静默跳过,避免日志噪音。
4.4sql_op:只取首关键词,永不记 SQL 原文
坑 11 是最重要的安全约束:db_query若打完整 SQL 文本/bind 参数,会泄露密码等敏感值(违反docs/constitution.mdC6)、PII,且大IN(...)参数序列化本身极慢(实测 memory 场景 3.6w 参数序列化耗时 11.9s)。因此sql_op(statement)只取 SQL 前缀首个关键词(SELECT/INSERT/UPDATE/DELETE,大小写不敏感),否则返回OTHER,语句体与参数值立即丢弃、永不打日志(metric_log.py#L240-L252)。
4.5record_db_query:DB 指标完整管线
这是 DB 埋点的统一接线函数(metric_log.py#L255-L287),一次查询事件驱动整条链路:
status="error":只打db_query ... status=error明细行并提前返回——失败查询不污染延迟直方图;- 成功路径:耗时 ≥
db_slow_query_ms打db_query明细 → 直方图record→ 窗口期满maybe_flush打db_query_agg汇总行 → 触发db_pool池采样(同一时间窗驱动)。
5. 四类埋点接入:最细统一 choke point(Wave 2)
决策 5 是埋点落地的关键:既不在 ASGI/中间件层按请求埋(拿不到子系统内部粒度,且 Celery/Linsight worker 无请求上下文、完全覆盖不到),也不在每个调用点手动埋(调用点极多、必漏、维护灾难),而是选在各子系统唯一无旁路的统一边界。
5.1 DB:SQLAlchemy cursor 事件 + session 超时捕获(T005)
在 src/backend/bisheng/core/database/connection.py 中,sync/async engine 创建后立即安装事件监听(sync engine 在 connection.py#L201、async engine 走async_engine.sync_engine在 connection.py#L218):
_install_db_metric_events:before_cursor_execute记开始时间,after_cursor_execute计算耗时并调用record_db_query,异常路径以status="error"进入(connection.py#L22-L58);_emit_pool_wait_timeout_if_needed:session 上下文捕获sqlalchemy.exc.TimeoutError打db_pool engine=... result=wait_timeout后再重抛,不改原有异常语义(connection.py#L64-L70)。
事件监听走 SQLAlchemy 通用 event,对达梦(DM) + MySQL 双 DB 方言透明。
5.2 对象存储:MinioStorage方法层切面(T007)
在 src/backend/bisheng/core/storage/minio/minio_storage.py 的put_object* / get_object* / download_object_sync方法层统一切面:计时 + 分类 result——http_status取S3Error.response.status、err_code取S3Error.code;401/403 →result=excluded(不进失败分母,决策 4);NoSuchKey归 ok 语义(业务侧已处理,不算 error)。
坑 6 的边界处理:minioS3Error是 frozen dataclass,逃逸存储层后重抛会掩盖真错——所以埋点必须在存储层边界内读取.code/.response.status(此时尚未逃逸),避免埋点读到已冻结的异常字段而崩溃。
5.3 模型调用:复用既有 finally,并行补一行(T009)
模型调用的 TTFT / status / is_stream早已在采集(写 ESMODEL_INVOKE)。T009 在 src/backend/bisheng/llm/domain/utils.py 的 4 个 wrapper 的 finally 中,与既有upload_telemetry_log并行补一行emit_metric("model_invoke", ...),复用已采集的first_token_cost_time / status / is_stream——不重复造轮子、不动 ES 链路(坑 5)。TelemetryCallback.on_llm_new_token提供首 token 时间点。
5.4 E+ 站内信:client 真实调用 + forwarder 白名单后 skip(T011)
- src/backend/bisheng/notification/external/cofco_eplus_client.py 的
send_textcard:成功/失败/异常分别打eplus_notify result=ok/error,携带biz_code / http_status / elapsed_ms / action; - src/backend/bisheng/notification/forwarder.py 的
maybe_forward_external:过白名单后的收件人解析 skip 打result=skipped。
坑 12(T011 偏差):maybe_forward_external对每条站内信都会调用,而feature_disabled/not_in_whitelist分支几乎每条都命中——在这两个分支打 metric 会刷屏、淹没真实 E+ 事件。因此只 meter 过白名单后的收件人解析 skip(低频,仅 forwardable 消息才到),client 只 meter 真实调用(ok/error);eplus_notify另加reason(skipped) /err_code(异常) 字段。
四组埋点均受MetricLogConf对应域开关控制,且全部 additive + 异常隔离——共享基础设施文件的改动不改变任何原有控制流。验收标准统一为:关闭对应开关后,行为与改动前完全一致(tasks.md 开发模式)。
6. 对外契约:BS_METRIC日志行字典与监控层算法口径(T012)
契约文档为 docs/observability/metric-log-contract.md(新增交付物),是给监控团队的解析依据。统一行格式:
BS_METRIC domain=<域> key=value key=value ...采集正则建议锚定BS_METRIC domain=,并用domain=的精确 token(后接空格或行尾)分流,避免db_query误匹配db_query_agg。
6.1 字段字典
| domain | 触发时机 | 字段 |
|---|---|---|
db_query | 单条 SQL 且elapsed_ms >= db_slow_query_ms(慢查询明细);失败查询也打 | op(SELECT/INSERT/UPDATE/DELETE/OTHER)elapsed_msstatus(ok/error) |
db_query_agg | 每进程每db_agg_window_s秒 | window_scountsum_msle_5 le_10 le_25 le_50 le_100 le_250 le_500 le_1000 le_inf(累积桶计数,单位 ms) |
db_pool | 周期采样(由查询驱动);池等待超时时 | engine(sync/async)checked_outidlesizecapacityat_capacity(0/1)result(可选=wait_timeout) |
obj_storage | 每次上传/下载完成 | op(put/get)result(ok/error/excluded)http_statuserr_codeelapsed_ms |
model_invoke | 每次模型调用结束 | model_idstatus(success/failed)is_stream(0/1)ttft_mstotal_ms |
eplus_notify | E+ 每次真实调用(ok/error);forwarder 过白名单后的收件人 skip(skipped) | result(ok/error/skipped)http_statusbiz_codeerr_codeelapsed_msactionreason(skipped 时) |
真实样例行(与测试断言完全一致):
BS_METRIC domain=db_query op=SELECT elapsed_ms=253.1 status=ok BS_METRIC domain=db_query_agg window_s=10 count=8421 sum_ms=41230 le_5=6100 le_10=7300 le_25=8000 le_50=8250 le_100=8360 le_250=8408 le_500=8418 le_1000=8420 le_inf=8421 BS_METRIC domain=db_pool engine=async checked_out=37 idle=63 size=100 capacity=120 at_capacity=0 BS_METRIC domain=obj_storage op=put result=ok http_status=200 elapsed_ms=45.2 BS_METRIC domain=obj_storage op=get result=error http_status=500 err_code=InternalError elapsed_ms=1203.4 BS_METRIC domain=model_invoke model_id=123 status=success is_stream=1 ttft_ms=340.0 total_ms=5200.0 BS_METRIC domain=eplus_notify result=ok http_status=200 biz_code=0 elapsed_ms=88 action=request_channel6.2 指标算法口径(供监控层参考)
- DB QPS=
sum(db_query_agg.count) / 时间跨度秒(跨进程直接加); - DB 查询耗时 P95= 对
db_query_agg各le_*桶按进程/窗口求和后histogram_quantile(0.95, buckets),P50/P99 同理; - DB 慢查询 TopN / 错误率=
db_query明细行按op分组;错误率 =count(status=error) / count(*); - 连接池饱和度=
db_pool.checked_out / capacity;排队告警=at_capacity==1持续 N 个采样,或出现db_pool result=wait_timeout(池已耗尽、等待超时); - 存储成功率=
count(result=ok) / (count(result=ok) + count(result=error)),按op(put/get) 分;result=excluded不进分母(401/403 签证过期); - 存储耗时= 对
obj_storage.elapsed_ms原始样本按op分组算分位; - 模型 TTFT P95= 对
model_invoke.ttft_ms原始样本算分位;调用成功率 =count(status=success) / count(*); - E+ 调用事件=
eplus_notify按result分组计数;elapsed_ms分位;错误细分看biz_code/err_code。
6.3 查询示例(Loki LogQL / ES)
DB QPS(5 分钟速率):
sum(rate({app="bisheng"} |= "BS_METRIC domain=db_query_agg" | logfmt | unwrap count [5m]))存储成功率(put,排除 excluded):
sum(count_over_time({app="bisheng"} |= "BS_METRIC domain=obj_storage" | logfmt | op="put" | result="ok" [5m])) / sum(count_over_time({app="bisheng"} |= "BS_METRIC domain=obj_storage" | logfmt | op="put" | result=~"ok|error" [5m]))模型 TTFT P95:
quantile_over_time(0.95, {app="bisheng"} |= "BS_METRIC domain=model_invoke" | logfmt | unwrap ttft_ms [5m])ES 侧用 ingest/grok 把BS_METRIC domain=... k=v解析成结构化字段后:成功率filter result:ok / (result:ok OR result:error);耗时分位用percentilesagg onelapsed_ms;DB P95 对db_query_agg的le_*求sumagg 后在可视化层做 histogram_quantile(或转 Prometheus remote-write)。
契约变更约束:新增字段向后兼容可直接加;重命名/删除字段、改domain、改 markerBS_METRIC属破坏性变更,必须通知监控团队同步解析/告警规则。字段单位固定:*_ms恒为毫秒,le_*桶恒为累积计数。
7. 测试体系:从单元到真实引擎的验证
Feature 采用后端 Test-First(务实版)开发模式,测试全部落在新增目录 src/backend/test/metric_log/,asyncio_mode=auto。
| 测试文件 | 覆盖内容(对应任务) |
|---|---|
| test_metric_log.py(T002) | emit_metric格式化/转义/字段省略/布尔与浮点渲染;总开关与域开关 gating(含db_query/db_query_agg/db_pool共享db开关);never-raises 兜底;DbQueryHistogram桶归类(含边界值elapsed==bound)、窗口 flush/清零、空窗口不 flush;sql_op首关键词解析(含WITH cte→ OTHER、空串 → OTHER) |
| test_db_metric.py(T004) | record_db_query慢查询明细/快查询只进直方图/错误行不进直方图/关闭开关零输出;在真实 SQLite engine上安装事件后跑真实 SQL 断言db_query+db_query_agg;池采样字段齐全;TimeoutError→db_pool result=wait_timeout(非 TimeoutError 不打) |
| test_storage_metric.py(T006) | mock minio client:put/get 成功打result=ok http_status=200;500/超时/连接错 →result=error带err_code/http_status;401/403 →result=excluded;elapsed_ms存在 |
| test_model_metric.py(T008) | mock 流式/非流式模型调用,断言 finally 产model_invoke行含ttft_ms/status/is_stream/total_ms/model_id;失败路径status=failed |
| test_eplus_metric.py(T010) | mock httpx:send_textcard成功打eplus_notify result=ok;失败/异常 →result=error;maybe_forward_external各短路分支 →result=skipped |
| test_e2e_metric_log.py | e2e 场景验证(CI 运行) |
值得一提的测试设计细节:capture_metric_logs用 loguru sink 捕获日志消息体做断言;_line_for用精确 domain token 边界匹配避免db_query误命中db_query_agg;fixture 自动重置模块级db_histogram/_pool_sampler单例,保证测试间隔离——这些细节本身也在验证"契约可被精确解析"。
8. 手动验证与运维排障
design.md §7 给出了完整的手动验证路径:本地起后端后制造流量,再 grep 日志。
cd src/backend && uv run uvicorn bisheng.main:app grep 'BS_METRIC domain=' <stdout / 日志文件>分域确认:
domain=db_query_agg:每db_agg_window_s秒一行、桶计数非零 → QPS/P95 数据源正常;domain=db_pool:采样字段齐全(checked_out/idle/size/capacity/at_capacity);- 上传/下载文件看
domain=obj_storage; - 跑一次模型调用看
domain=model_invoke(与既有 ES telemetry 并存); - 触发一次站内信看
domain=eplus_notify。
强制产出慢查询明细的小技巧:把db_slow_query_ms调小(如 1ms),几乎所有查询都会打db_query明细行,便于排查。可观测性自身的健康度看emit_metric是否有异常被吞没(应只有 debug 级兜底日志,业务主流程不受任何影响)。
9. 已知坑与反直觉事实速查(design §5 全表)
| # | 反直觉事实 | 后果 | 处理 |
|---|---|---|---|
| 1 | 进程内算好的 P95 跨进程不可合并 | 多 worker 下 P95 完全失真 | 只打原始值/桶计数,聚合交监控层 |
| 2 | 纯"仅慢查询"日志算不出 QPS 与完整 P95 | 监控层拿不到 QPS | DB 必须配周期汇总行db_query_agg |
| 3 | 401/403 是签证过期的预期场景,非服务故障 | 计入失败率污染 SLA、误告警 | result=excluded单列,不进失败分母 |
| 4 | DMdmAsync是假异步(内部同步阻塞事件循环) | 埋点里任何同步 IO/锁竞争放大成 worker 冻结 | emit_metric零阻塞:内存计数 + 非阻塞日志,flush 不加全局锁 |
| 5 | 模型调用 TTFT/status 已在采集(写 ES) | 重复造轮子、埋错地方 | 复用 4 wrapper 的 finally,仅并行补日志行 |
| 6 | minioS3Error是 frozen dataclass,逃逸后重抛掩盖真错 | 埋点在存储层外读字段会崩 | 在存储层边界内读.code/.response.status |
| 7 | sessionmaker每次新建,只有 engine 是单例 | 计数器挂 session 会丢数据 | 计数器/直方图挂模块级单例,按进程聚合 |
| 8 | SQLAlchemy 无"当前排队数"公开 API,checkout在拿到连接后触发 | 直接拿等待数/等待时长无门 | 饱和度反推 + 捕获TimeoutError;精确wait_ms需子类化QueuePool._do_get(档位 2) |
| 9 | 异步池要从async_engine.sync_engine.pool取 | 直接取报错/拿错对象 | 采样处对 async 走sync_engine.pool |
| 10 | pool.overflow()语义随版本可能为负;max_overflow无公开方法 | 容量算错 | 只用checkedout()/checkedin()/size();max_overflow从DatabasePoolConf读 |
| 11 | 打完整 SQL/参数会泄密(C6)且大IN序列化极慢(3.6w 参数 11.9s) | 泄密 + 日志爆炸 + 拖慢主流程 | 只提取op首关键词,永不记参数值 |
| 12 | maybe_forward_external对每条站内信调用,feature_disabled/not_in_whitelist几乎每条命中 | 高频刷屏淹没真实事件 | 只 meter 过白名单后的收件人 skip;client 只 meter 真实调用 |
10. 后续规划与当前边界(design §8)
- 暂不做:进程内
/metrics端点、Grafana 看板、告警规则(监控层职责); - 暂不做:存储/模型也走"周期直方图汇总"——当前它们是低中频,原始样本足够;若未来 QPS 上升、日志量成问题,再照 DB 的
*_agg模式扩展; - 暂不做:连接池精确等待时长
wait_ms全分布 / "此刻几个在排队"精确计数(档位 2,需子类化QueuePool._do_get,同步+异步各注入一次);当前饱和度 Gauge(档位 1)+ 等待超时计数已足够; - 触发重写:若企业无集中日志管线、要求进程内暴露指标端点 → 迁移到 Prometheus multiproc 方案(决策 1 的方案 B)。
11. 实现偏差记录(tasks.md 附录)
- T011 偏差:E+ skipped 只在 forwarder 过白名单后的收件人解析 skip 打,
feature_disabled/not_in_whitelist不打(高频刷屏,坑 12);eplus_notify增加reason(skipped) /err_code(异常) 字段。 - T003 实现细节:直方图类公开命名为
DbQueryHistogram(便于单测)+ 模块级单例db_histogram;方法签名为record(elapsed_ms, now)/maybe_flush(now, window_s)/maybe_emit_pool_gauge(now, pools, window_s)——now/window_s均由调用方传入以保证窗口可确定性测试;另新增sql_op(statement)(只取首关键词)。
以上偏差均为纯实现细节,不改变 design.md 的任何决策。
总结
F042 是一个"约定大于实现"的可观测性落地范本:后端侧用不到 300 行的metric_log.py+ 4 处收敛点埋点 + 1 份契约文档,换来了 DB/对象存储/模型/E+ 四类子系统的完整指标覆盖,且全程零新依赖、零阻塞、不改变任何业务语义。其精髓在于三条边界划分:埋点与聚合的边界(后端打原始测量,监控层算 P95/QPS/成功率)、慢查询明细与周期汇总的边界(解决高 QPS 下的日志量与指标可计算性的矛盾)、契约与实现的边界(BS_METRIC一经发布即为对外契约,变更需通知监控团队)。对于任何需要为多进程架构补充可观测性的团队,这份 design + tasks + contract 的组合都是值得直接参考的实践。
【免费下载链接】bishengBISHENG is an open LLM devops platform for next generation Enterprise AI applications. Powerful and comprehensive features include: GenAI workflow, RAG, Agent, Unified model management, Evaluation, SFT, Dataset Management, Enterprise-level System Management, Observability and more.项目地址: https://gitcode.com/GitHub_Trending/bi/bisheng
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考