开发环境里,埋点最容易犯的错是看不到数据。生产环境更麻烦:数据太多、送达不稳、内容过于敏感,或者离开观测平台后失去口径。把每条 Trace 都保存下来并不能同时解决这些问题。生产数据链需要回答四件事:哪些记录值得采,事件如何送达,谁可以使用,以及数据怎样被安全复用。
先决定保留什么
采样不是随手填一个 10%。它是用有限的网络、存储和查询成本,换取足以支持判断的证据。要估算模型分布、平均延迟和总体趋势,可以对稳定的大流量场景做概率采样;要调查安全事件、人工投诉或高价值客户失败,就需要优先保留对应 Trace。
实践中常见的策略是:高风险低频请求全量保留,错误、超时、异常重试和负反馈按条件保留,其余正常流量做比例采样。难点在于决定发生的时间。请求开始时采样,成本低,也容易保证整条 Trace 要么留下、要么不发送;请求结束后才知道它是否报错,但此时做决定需要暂存完整过程,这就是尾部采样的额外成本。
采样应尽量以 Trace 为单位。随机丢掉内部 Observation,会留下一条缺少检索或工具调用的残缺链路。Langfuse 的 LANGFUSE_SAMPLE_RATE、sample_rate 或对应 OTel 比例 Sampler,主要在 Trace 入口做比例决策;采中后,内部 Observations 和 Scores 会一起上报。错误优先、结束后决策等策略,需要应用层、OTel Collector 或其他管道承接,不能把一个比例参数理解成完整的尾部采样能力。
条件采样还会改变统计口径。假设正常流量只留 20%,错误流量全留,样本里的错误率一定高于总体。Dashboard 必须知道哪些数据来自随机样本,哪些是条件保留;采样策略和生效版本也应进入字段字典。否则成本降下来了,指标却无法解释。
批处理让业务更快,也让送达变得不确定
观测 SDK 通常先把事件写进进程内队列,再按数量或时间合并发送。这样不会把每次上报的网络延迟叠加到用户请求上,但业务结束时,事件可能还躺在内存里。容器被回收、Serverless 运行时冻结或队列满载,都会造成缺口。
批次大小和等待间隔需要一起看。大批次减少请求次数,却增加可见延迟和单次损失;短间隔数据出现得快,网络开销也更高。队列必须有容量上限,并明确满载后的动作:阻塞业务、丢旧事件还是丢新事件。网络超时、限流和临时服务端错误可以有限重试并退避;认证失败、非法格式这类永久错误反复重试没有意义。
几个刷新接口很像,语义却不同:
- Langfuse
flush()立即尝试发送当前批次,客户端继续工作;网络异常时会记录错误并重试,不代表客户端关闭。 - Langfuse
shutdown()用在进程退出阶段,它会刷新、等待待处理请求,再关闭客户端。成功后不再发送新事件。 - OTel Span Processor 的
forceFlush()要求导出当前缓冲 Span,但不会关闭 Provider。
长驻服务偶尔要求数据尽快可见,可以使用 flush();命令行任务退出时应走 shutdown();Serverless 或 OTel 管道则按运行时生命周期调用 forceFlush() 或对应关闭接口。三者都不是绝对送达承诺。进程被强制杀死或机器断电时,内存事件仍可能丢失。审计、计费等不能容忍缺口的场景,需要持久队列、可重放事件或 Collector 中转,并处理去重。
观测管道本身也要有指标:队列深度、批次耗时、成功率、重试和丢弃数量,以及最后一次成功时间。周期性发送一条已知 Trace,再检查平台侧是否可查询,能把“业务没发生”“采样未命中”和“数据途中丢失”区分开。
敏感信息要在离开应用前处理
AI Trace 可能包含用户原话、历史对话、检索文档、工具参数和模型输出。第一道防线是少采:只需文档 ID 就别传全文,只需稳定用户标识就别传姓名和邮箱,密钥与访问令牌不应进入 Trace。
必须记录的内容,优先在客户端脱敏。这样明文不会离开应用的信任边界。Langfuse SDK 支持在导出前修改输入、输出、Metadata 或 OTel Span 属性;其他 SDK 和 Collector 也可以承担类似处理。规则上线前要用代表性样本验证漏识别、误伤、异常返回和性能,并记录规则版本。Python mask_otel_spans 抛出异常或返回非法结果时,可能丢弃整个导出批次;单个 Patch 非法也可能让对应 Span 丢失。因此脱敏函数还需要错误监控和批次缺口告警。
Langfuse 自托管企业版提供服务端 ingestion masking,但这项能力有几个容易漏掉的边界:它只处理通过 OTel 端点 /api/public/otel 进入的事件;原始事件会先写入 Blob Storage,之后 Worker 调用脱敏回调,脱敏结果再进入 ClickHouse;旧的 /api/public/ingestion 不经过该回调。也就是说,服务端脱敏是集中治理的第二道防线,不能证明原始明文从未进入 Langfuse 基础设施。
回调失败时,默认配置是 fail-open:超时、网络错误、HTTP 错误或非法 JSON 都可能让事件以未脱敏状态继续处理,并记录警告。高敏场景可以配置 fail-closed,让失败事件被丢弃,但要接受观测缺口,并为回调服务提供高可用和低延迟。无论采用哪种模式,都要主动验证失败行为,不能把默认值留给生产事故来解释。
数据进入平台后,权限、保留和删除继续生效。查看原始输入输出、管理项目、导出数据和执行删除不应共享同一权限。删除 Trace 时还要考虑关联 Observation、Score 和媒体;数据已导出到数仓或对象存储后,原平台的权限和删除策略不会自动跟随,下游需要重新建立控制。
数据离开页面后仍要可解释
单条排障适合在 UI 中完成,长期报表、离线评估和跨产品分析则要复用数据。先根据问题选接口:还原调用树、构建 Dataset 时读取 Observation 和 Score 明细;只分析每日成本、Token、延迟或评分分布时,优先使用聚合接口;大规模长期同步可以落对象存储,再进入数仓。
导出时至少保留 traceId、Observation ID、父子 ID、Session ID、时间、Environment 和 Version。缺少这些关系,得到的只是一批孤立 JSON。Langfuse 的公共 API、Observations API、Scores API、Metrics API 和 Blob Storage Export 对应不同规模。大规模导出使用 manifest 标识一轮文件已经生成完整;消费端应按 manifest 读取,并根据 ID 去重,避免相邻窗口边界重复。
数仓还需要自己的语义层。“请求数”究竟是 Trace、根 Observation 还是模型调用?“成功”来自无异常、用户反馈还是评分阈值?这些口径应保存在平台之外,并记录版本。API 升级或字段变化时,新旧链路先并行校验,再切换下游。数据能够下载,只代表拿到了文件;能重建关系、解释指标并继续执行权限和删除策略,才算真正复用。
参考资料
系列目录:AI 应用可观测性与评估
