ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

BiSheng 指标日志契约(BS_METRIC)全解:结构化埋点、监控层聚合与可观测性落地指南

BiSheng 指标日志契约(BS_METRIC)全解:结构化埋点、监控层聚合与可观测性落地指南 BiSheng 指标日志契约BS_METRIC全解结构化埋点、监控层聚合与可观测性落地指南【免费下载链接】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 平台的指标日志契约BS_METRIC展开系统讲解后端各进程web / Celery / Linsight worker如何以统一 marker logfmt 格式输出结构化测量行以及监控团队如何基于 ELK / Loki / ES 日志管线解析并聚合成 DB QPS、P95、存储成功率、模型 TTFT 等关键指标。读完本文你将掌握BS_METRIC的完整行格式与字段字典、各指标算法的精确口径、Loki / ES 查询写法、后端开关配置以及埋点背后的源码级实现原理与踩坑约束。一、契约的定位后端只打原始测量聚合全在监控层BS_METRIC是 BiSheng 给监控团队的解析依据正式契约文档见 docs/observability/metric-log-contract.md设计稿见 features/v2.6.0/042-metric-log-observability/design.mdF042。该方案有几个核心定位理解它们才能正确使用这份契约只打原始测量后端各进程向标准日志输出结构化行P95 / QPS / 成功率等聚合计算全部在监控层日志管线完成后端不引入prometheus_client、不建/metrics端点、不做进程内百分位计算。多进程透明FastAPI web 进程 Celery workers Linsight worker 各自独立运行大量 DB 查询、模型调用、文件存储发生在 worker 内。日志方案天然对各进程透明——各写各的采集端集中汇合。数据流闭环业务代码在四类最细统一边界DB engine cursor 事件、MinIO 存储切面、模型调用 wrapper finally、E 站内信客户端埋点 →emit_metric格式化输出 → loguru 打到 stdout / 日志文件 → 监控层采集、按domain解析、聚合出指标并上屏告警。二、行格式与通用格式化规则统一的行格式是marker domain logfmt 风格 keyvalueBS_METRIC domain域 keyvalue keyvalue ...通用规则源码实现在 src/backend/bisheng/common/services/metric_log.py 的_fmt_value见 第 65-76 行规则说明数值字段*_ms单位恒为毫秒float 保留 3 位小数round(v, 3)避免科学计数法bool字段渲染为1/0而非True/False含空格 / 引号 // 换行 / 制表符 / 竖线的字符串加双引号包裹并转义反斜杠、双引号换行与制表符替换为空格None字段省略不打监控层解析时按缺省处理采集端正则建议锚定BS_METRIC domain用domain的精确 token后接空格或行尾做分流避免db_query误匹配db_query_agg。对应的单元测试见 src/backend/test/metric_log/test_metric_log.py其中_line_for正是用前缀 空格或行尾的边界匹配来区分db_query与db_query_agg第 30-36 行验证了契约中关于精确 token 分流的约定test_emit_metric_bool_rendered_as_int、test_emit_metric_float_rounded_not_scientific、test_emit_metric_escapes_values_with_spaces_or_quotes分别覆盖布尔、浮点、转义规则。三、字段字典六大 domain 及触发时机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累积桶计数单位 msdb_pool周期采样由查询驱动池等待超时时engine(sync/async)checked_outidlesizecapacityat_capacity(0/1)result(可选wait_timeout)obj_storage每次上传/下载完成op(put/get)result(ok/error/excluded)http_statuserr_codeelapsed_msmodel_invoke每次模型调用结束model_idstatus(success/failed)is_stream(0/1)ttft_mstotal_mseplus_notifyE 每次真实调用ok/errorforwarder 过白名单后的收件人 skipskippedresult(ok/error/skipped)http_statusbiz_codeerr_codeelapsed_msactionreason(skipped 时)各 domain 的开关映射在源码中由_DOMAIN_SWITCH定义metric_log.py 第 31-38 行db_query/db_query_agg/db_pool同受db开关控制obj_storage、model_invoke、eplus_notify各自独立未映射的新 domain 默认放行。3.1 db_query / db_query_agg慢查询明细 周期汇总双行制DB 是超高频埋点域逐条 SQL 打一行在高 QPS 下日志量不可接受而仅慢查询又算不出 QPS 与完整 P95。因此采用双行制设计文档决策 3db_query只打超过db_slow_query_ms阈值的慢查询明细同时失败查询无条件打一行statuserrordb_query_agg每进程每db_agg_window_s秒输出一行汇总携带count供 QPS与延迟直方图桶计数供 P95。调用链在record_db_querymetric_log.py 第 255-287 行中实现statuserror时只发错误明细行且不污染延迟直方图成功时按阈值决定是否发明细行随后无条件record进直方图、窗口期满 flush 出db_query_agg并顺带采样连接池。埋点接入点位于 src/backend/bisheng/core/database/connection.py 的_install_db_metric_events第 22-61 行通过 SQLAlchemy 通用事件监听不依赖 MySQL / 达梦方言before_cursor_execute记录time.monotonic()起点after_cursor_execute计算elapsed_ms并调用record_db_query(..., statusok)handle_error兜底打statuserror行同步 engine 与异步 engine取其sync_engine各自安装一次。3.2 db_pool饱和度 Gauge 等待超时计数SQLAlchemy不提供当前排队等连接数的公开 APIPoolEvents.checkout也在拿到连接之后才触发因此用饱和度反推设计决策 6周期采样pool.checkedout()/checkedin()/size()capacity size max_overflowchecked_out capacity即at_capacity1session 上下文捕获 SQLAlchemyTimeoutError时额外打一行resultwait_timeoutconnection.py 第 64-70 行、第 241 行。采样实现为maybe_emit_pool_gaugemetric_log.py 第 198-230 行由查询事件驱动、_PoolSampler保证每窗口最多一次只读稳定的公开方法checkedout/checkedin/size坑 10max_overflow从DatabasePoolConf读取而非私有字段异步池经sync_engine.pool采样坑 9SQLite 的StaticPool无饱和度语义检测到缺少checkedout属性时静默跳过。3.3 obj_storage成功率口径排除签证过期对象存储的成功率口径特殊401/403 签证/权限过期是预期内的客户端刷新场景不算服务故障单列resultexcluded且不进失败分母只有超时 / 5xx / 连接错误算resulterrorNoSuchKey对象不存在视为存储正确响应算ok。分类逻辑在 src/backend/bisheng/core/storage/minio/minio_storage.py 的_STORAGE_EXCLUDED_CODES第 95-97 行与_classify_storage_exc第 100-114 行NoSuchKey→okHTTP 状态 401 / 403 或 code 命中{AccessDenied, SignatureDoesNotMatch, ExpiredToken, InvalidAccessKeyId, TokenRefreshRequired}→excluded其余 S3 错误 →error携带http_status与err_code跨租户共享回退异常StorageSharingFallbackError→ok。埋点以_metered(put / get)装饰器 _storage_metric上下文管理器实现第 117-170 行同时支持同步与 async 方法只包住真正执行 S3 调用的叶子方法保证每次操作只计一次http_status/err_code在存储边界内读取——因为 minio 的S3Error是 frozen dataclass逃逸后重抛会掩盖真实异常坑 6。3.4 model_invoke复用已有 TTFT 采集模型调用的 TTFT / status / is_stream在 F042 之前已采集并写 ES telemetryMODEL_INVOKE因此不重复造轮子而是在 src/backend/bisheng/llm/domain/utils.py 的模型调用 wrapper finally 中并行补打一行日志第 242-251 行emit_metric( model_invoke, model_idself.model_id, statussuccess if status StatusEnum.SUCCESS else failed, is_streamis_stream, ttft_msfirst_token_cost_time, total_ms(end_time - start_time) * 1000.0, )其中ttft_ms是首 Token 延迟毫秒total_ms是完整调用耗时。3.5 eplus_notify只 meter 真实调用与低频 skipE 站内信埋点有明确的高频短路排除约束坑 12forwarder.maybe_forward_external对每条站内信都会调用feature_disabled/not_in_whitelist分支几乎每条都命中在这两个分支打指标会刷屏淹没真实事件——因此不打只有两类打客户端每次真实调用成功resultok/ 失败resulterror见 src/backend/bisheng/notification/external/cofco_eplus_client.py第 108-115 行 非 JSON 响应、第 128-135 行code0成功、第 145-152 行 业务失败、第 167-174 行 异常兜底forwarder过白名单之后的收件人解析 skip低频仅 forwardable 消息才走到见 src/backend/bisheng/notification/forwarder.py第 145 行带reason字段说明跳过原因。四、示例行与le_*累积桶语义BS_METRIC domaindb_query opSELECT elapsed_ms253.1 statusok BS_METRIC domaindb_query_agg window_s10 count8421 sum_ms41230 le_56100 le_107300 le_258000 le_508250 le_1008360 le_2508408 le_5008418 le_10008420 le_inf8421 BS_METRIC domaindb_pool engineasync checked_out37 idle63 size100 capacity120 at_capacity0 BS_METRIC domainobj_storage opput resultok http_status200 elapsed_ms45.2 BS_METRIC domainobj_storage opget resulterror http_status500 err_codeInternalError elapsed_ms1203.4 BS_METRIC domainmodel_invoke model_id123 statussuccess is_stream1 ttft_ms340.0 total_ms5200.0 BS_METRIC domaineplus_notify resultok http_status200 biz_code0 elapsed_ms88 actionrequest_channelle_*是累积桶Prometheus histogram 语义le_10包含le_5的全部计数le_inf count。累积桶跨进程、跨窗口可直接相加聚合后经histogram_quantile即可得到真实全局 P95。这正是预算好的百分位跨进程不可合并、原始桶计数可合并这一核心设计决策 2的落点。桶边界定义于源码_BUCKETS_MS (5, 10, 25, 50, 100, 250, 500, 1000)metric_log.py 第 108 行DbQueryHistogram.record用bisect_left实现elapsed_ms bound落入该桶的左闭语义第 127-135 行maybe_flush在窗口期满后按累积方式输出第 137-159 行。直方图与池采样器都是模块级单例db_histogram、_pool_sampler按进程聚合——因为sessionmaker每次调用都新建计数器不能挂在 session 上坑 7。五、指标算法口径监控层计算指南设采集周期内某窗口跨所有进程汇总各指标口径如下指标口径DB QPSsum(db_query_agg.count) / 时间跨度秒DB 查询耗时 P95 / P50 / P99对db_query_agg各le_*桶按进程/窗口求和后histogram_quantile(0.95, buckets)DB 慢查询 TopN / 错误率db_query明细行按op分组错误率 count(statuserror) / count(*)连接池饱和度db_pool.checked_out / capacity排队告警 at_capacity1持续 N 个采样或出现db_pool resultwait_timeout池耗尽、等待超时存储成功率count(resultok) / (count(resultok) count(resulterror))按op(put/get) 分resultexcluded不进分母401/403 签证过期存储耗时对obj_storage.elapsed_ms原始样本按op分组算分位模型 TTFT P95对model_invoke.ttft_ms原始样本算分位调用成功率 count(statussuccess) / count(*)E 调用事件eplus_notify按result(ok/error/skipped) 分组计数elapsed_ms分位错误细分看biz_code/err_code需要强调模型、存储、E 是低中频域打原始样本足够DB 是超高频域打直方图桶计数控制日志量。若未来存储/模型 QPS 上升导致日志量成为问题设计文档也明确了可照 DB 的*_agg模式扩展。六、查询示例6.1 LokiLogQLDB QPS5 分钟速率sum(rate({appbisheng} | BS_METRIC domaindb_query_agg | logfmt | unwrap count [5m]))存储成功率put排除 excludedsum(count_over_time({appbisheng} | BS_METRIC domainobj_storage | logfmt | opput | resultok [5m])) / sum(count_over_time({appbisheng} | BS_METRIC domainobj_storage | logfmt | opput | result~ok|error [5m]))模型 TTFT P95quantile_over_time(0.95, {appbisheng} | BS_METRIC domainmodel_invoke | logfmt | unwrap ttft_ms [5m])DB 查询 P95 需对le_*桶跨进程求和再做histogram_quantile若管线支持把db_query_agg的桶转成 Prometheus histogram 系列每个le_x一条后用histogram_quantile(0.95, sum by (le)(...))。6.2 ES聚合思路用 ingest / grok 把BS_METRIC domain... kv解析成结构化字段domain、op、result、elapsed_ms…成功率filter result:ok / (result:ok OR result:error)耗时分位percentilesagg onelapsed_msDB P95对db_query_agg的le_*求sumagg 后在可视化层做histogram_quantile或转 Prometheus remote-write。七、后端开关与运维配置config.yaml的metric_log段默认全开对应的配置模型MetricLogConf定义于 src/backend/bisheng/core/config/settings.py第 665-689 行并通过Settings.metric_log挂载到全局配置第 756 行键默认说明enabledtrue总开关关闭后不打任何BS_METRICdb/obj_storage/model_invoke/eplustrue各域独立开关db_slow_query_ms200慢查询明细阈值ms调小可看到更多db_query明细db_agg_window_s10db_query_agg汇总 db_pool采样窗口秒开关判断逻辑在_domain_enabledmetric_log.py 第 54-62 行配置未加载前fail-open默认放行避免启动早期丢指标总开关关闭直接短路再按 domain 映射查各域开关record_db_query入口同样先做开关闸门第 267-269 行关闭的 domain 是零开销的。运维提示调小db_slow_query_ms例如改为 1可强制让每条 SQL 都产出db_query明细行便于排查生产环境则建议保持阈值用db_query_agg承载全局 QPS / P95 计算。八、契约变更约束新增字段向后兼容可直接加重命名 / 删除字段、改domain、改 markerBS_METRIC破坏性变更必须通知监控团队同步解析/告警规则字段单位固定*_ms恒为毫秒le_*桶恒为累积计数。对外契约一旦发布即被监控层解析规则依赖设计文档 §2 关键约束这也是_DOMAIN_SWITCH对未知新 domain 默认放行、emit_metric对None字段省略不打的原因——契约演进以加字段为主路径。九、埋点实现的工程约束源码级剖析emit_metricmetric_log.py 第 79-98 行是唯一的输出出口其工程约束对理解契约行为至关重要零阻塞、绝不破坏业务流整个函数包在 try/except 中任何异常都只以 debug 级记录非静默吞掉后返回埋点失败绝不影响调用方无隐藏时钟时间一律由调用方传入now参数保证直方图/池窗口的确定性并便于测试不依赖请求 / 租户 ContextVarCelery / Linsight worker 内同样可用不记录 SQL 原文与 bind 参数安全约束 C6 / 坑 11sql_op第 240-252 行只取 SQL 前缀首个关键词SELECT/INSERT/UPDATE/DELETE否则OTHER语句体与参数值从不保留——既防泄密密码、PII也避免超大IN(...)参数序列化拖慢主流程。测试覆盖上除了前文提到的emit_metric格式化单测DbQueryHistogram的桶归类与窗口 flush 计数正确性、存储 result 分类401/403 边界也都有对应用例分布在 src/backend/test/metric_log/ 目录的test_metric_log.py、test_db_metric.py、test_storage_metric.py、test_model_metric.py、test_eplus_metric.py、test_e2e_metric_log.py中。十、本地验证方法手动验证步骤设计文档 §7本地启动后端cd src/backend uv run uvicorn bisheng.main:app制造流量后过滤日志grep BS_METRIC domain stdout / 日志文件分域确认domaindb_query_agg每db_agg_window_s秒一行、桶计数非零 → 可算 QPS / P95domaindb_pool采样字段齐全上传/下载文件看domainobj_storage跑一次模型调用看domainmodel_invoke与既有 ES telemetry 并存触发一次站内信看domaineplus_notify将db_slow_query_ms调小如 1可强制产出db_query慢查询明细行。十一、已知边界与后续演进从设计文档可以明确该方案的边界避免误用不做进程内/metrics端点、Grafana 看板、告警规则——这些全是监控层职责不做存储 / 模型也走周期直方图汇总当前低中频、原始样本足够不做连接池精确等待时长wait_ms全分布与此刻几个在排队的精确计数当前饱和度 Gauge 等待超时计数足够不够时再子类化QueuePool._do_get触发重写若企业无集中日志管线、要求进程内暴露指标端点则迁移到 Prometheus multiproc 方案采集盲区任何绕过统一边界的调用路径不经 engine 的裸 DBAPI 连接、不经get_minio_storage()直接 new minio client、旁路 wrapper 的模型调用不会被采集新增调用路径时需注意收敛回统一边界。【免费下载链接】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),仅供参考
返回列表