
bug: OTel ingestion serializes int64 as protobufjs Long internals after protobufjs 7.4 → 7.5 upgrade
快速结论:该报错发生在 Langfuse 自托管版本从 3.155.1 升级到 3.169.0 后,OTLP/protobuf ingestion 中所有 int64 字段(如 intValue、startTimeUnixNano、endTimeUnixNano、timeUnixNano)被序列化为 protobufjs 内部 Long 对象 {"low": <int>, "high": <int>, "unsigned": <bool>} 而非数字或字符串。优先排查 .toObject() 调用是否缺少 longs 转换选项。
问题场景
用户运行 Langfuse 自托管服务(版本 ≥ 3.169.0),配合 OpenTelemetry SDK(如 Java SDK 1.25.0 或 Python SDK 3.12.0)通过 OTLP/protobuf 协议(HTTP 或 gRPC)发送 trace 数据。配置了服务端 ingestion masking 回调(LANGFUSE_INGESTION_MASKING_CALLBACK_URL)时,回调接收到的 int64 字段变为 protobufjs 内部 Long 对象结构,而非符合 OTLP/JSON 规范的十进制字符串或数字。
报错原文
// 序列化结果示例(JSON.stringify 后):
{"v":{"low":1234567890,"high":0,"unsigned":false}}
// 期望结果(protobufjs 7.4 行为):
{"v":1234567890}
原因分析
在 Langfuse 3.169.0 版本中,PR #13232 将 protobufjs 从 7.4.0 升级到 7.5.5。关键变化:
- protobufjs 7.4:当 long.js 无法解析时,
util.Long未初始化,int64回退为普通 JS 数字。 - protobufjs 7.5:当 long.js 无法解析时,内部创建一个回退 Long 类,但该类缺少
toJSON()方法。因此JSON.stringify会暴露原始的可枚举属性{low, high, unsigned}。
根因是 web/src/pages/api/public/otel/v1/traces/index.ts 中的 .toObject(parsed) 调用未指定 longs 转换选项,导致 protobufjs 7.5 使用内部 Long 对象序列化 int64 字段。
环境排查
- Langfuse 自托管版本:≥ 3.169.0(3.155.1 为最后一个正常版本)
- protobufjs 版本:7.5.5(通过 PR #13232 引入)
- OpenTelemetry SDK:任何使用 OTLP/protobuf 协议的 SDK(如 Java SDK 1.25.0、Python telemetry SDK 1.39.1)
- 服务端配置:启用
LANGFUSE_INGESTION_MASKING_CALLBACK_URL时触发问题明显
解决步骤
- 定位文件
web/src/pages/api/public/otel/v1/traces/index.ts中的.toObject(parsed)调用(约第 96-98 行)。 - 可优先尝试将调用改为传递
longs选项,推荐使用{ longs: String }(符合 OTLP/JSON 规范,将 int64 编码为十进制字符串,避免纳秒时间戳精度丢失。如果对 >53 位数值的精度丢失可接受,也可使用{ longs: Number })。
resourceSpans =
$root.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest.toObject(
parsed,
{ longs: String },
).resourceSpans;
web/src/pages/api/public/otel/otlp-proto/generated/root.ts)已支持 longs: String 和 longs: Number 选项,适用于所有 int64 字段(包括 intValue、startTimeUnixNano、endTimeUnixNano、timeUnixNano)。验证方法
部署修复后,发送包含 int64 字段的 OTLP/protobuf trace(如使用 OpenTelemetry Java SDK 或 Python SDK),检查 masking 回调接收到的 payload 中 int64 字段是否为十进制字符串("1234567890")或数字,而非 {"low":..., "high":..., "unsigned":...} 对象格式。也可直接使用 Issue 中提供的 Docker 命令复现并验证修复前后的序列化差异。



