0 核对基准
| 对象 | 版本 | 说明 |
| langfuse/langfuse | 6918181(2026-11-05),web 4.76.0 | 服务端:web、worker、packages/shared、ClickHouse 迁移 |
| langfuse/langfuse-python | 5.2.0 | 客户端:请求头、属性名、传播、mask、媒体 |
| 官方文档 | v4 changelog(2026-08-17)、v3→v4 升级指南、OTel 迁移指南、导出文档、2026-03-10 博客 | 与代码不一致处见 §13 |
1 架构与数据流
两个自有进程:Web(Next.js,界面与所有接口,含上报)和 Worker(Express,处理上报、评估、导出)。v4 没有新增服务。
| 存储 | 内容 |
| PostgreSQL ≥ 15 | 用户、组织、项目、API 密钥、提示词、数据集、评估配置、媒体记录(media 表) |
| ClickHouse ≥ 25.12 | 追踪数据(events_full / events_core)、评分(scores);v3 表 traces / observations 仍存在 |
| Redis / Valkey ≥ 7.0 | BullMQ 队列,API 密钥与提示词缓存,双写游标 |
| S3 / Blob | 原始上报事件(事件桶)、媒体与溢出字段(媒体桶)、导出文件 |
SDK / 埋点→ OTLP →Web 校验→ 整批 JSON →S3 事件桶+ 地址 →Redis 队列→Worker 下载·(脱敏)·转换·补全→ 批量 →ClickHouse events_full→ MV →events_core
2 客户端对接的六种情况
写入路径由 resolveOtelWritePath 决定,优先级:direct_header > direct_env > direct_scope > direct_org_cutoff(仅 Cloud)> dual。worker/src/queues/otelIngestionQueue.ts
| # | 客户端 | 请求头 / 识别 | 路径(dual 模式 / events_only 模式) | trace 属性 | 注意 |
| 1 | 新版 SDK:Python 5.x、JS 6.x | x-langfuse-ingestion-version: 4 + sdk 名与版本 | 直写 / 直写 | SDK 复制到每个 span | 要求 v4 服务端 |
| 2 | 过渡 SDK:Python 4.x、JS 5.x | sdk 名与版本(Python ≥ 4.0.0、JS ≥ 5.0.0 即直写) | 直写 / 直写 | Python ≥ 4.7.0、JS ≥ 5.4.0 才复制;更早只在根 span | 同时兼容 v3/v4 服务端 |
| 3 | 旧 OTel SDK:Python 3.x、JS 4.x | 无版本头;作用域名含 langfuse | 默认(NATIVE_OTEL_BEHAVIOUR=direct)直写;设为 dual_write(仅 legacy/dual 模式可用)才走双写,约 10–15 min 延迟 | dual_write:作业从 traces 表补;direct:不补 | 直写时子 span 按用户查不到 |
| 4 | 非 OTel SDK(Python 2.x、JS 3.x)或自调 /api/public/ingestion | REST 批量接口 | 旧路径 / 只收 score-create,其余 400 | — | 升级时最容易漏 |
| 5 | 第三方 OTel(OpenInference、Vercel AI SDK、框架自带) | 建议手动加版本头 4 | 无头:dual 下走 dual;NATIVE_OTEL_BEHAVIOUR=direct(默认)则直写 | 需自己写到每个 span | 未识别属性进 metadata.attributes |
| 6 | 企业 OTel Collector 转发 | 透传 Authorization 与版本头 | 同上游客户端 | 可在采集器补资源属性 | 采样不能丢根 span;同一 span 不要两路发送(读取不去重) |
SDK 主版本与服务端:Python SDK v4 / JS SDK v5 可连 v3 和 v4 服务端;Python SDK v5 / JS SDK v6 需要 v4 服务端(官方升级指南)。
3 上报协议与属性
入口
| 项 | 值 | 源码 |
| 地址 | POST /api/public/otel/v1/traces | web/src/pages/api/public/otel/v1/traces/index.ts |
| 内容类型 | application/x-protobuf 或 application/json;其他 400 | web/src/server/otel/processOtelIngestion.ts |
| 压缩 | Content-Encoding: gzip | 同上 |
| 认证 | Authorization: Basic base64(publicKey:secretKey) | |
| 体积 | LANGFUSE_OTEL_INGESTION_MAX_BODY_BYTES 默认 512 MiB,压缩前后都检查;超限 413 + Connection: close;> 16 MiB 记警告 | web/src/server/otel/otelRequestBody.ts |
| 编号校验 | 任一 span 缺 traceId/spanId 或集合不是数组 → 整批 400(常见:OTLP 日志打到 traces 端点) | validateOtelSpanIds |
| 版本头 | x-langfuse-ingestion-version:> 4 → 400;= 4 → 直写候选 | processOtelIngestion.ts |
Python SDK 默认请求头
Authorization: Basic …
x-langfuse-sdk-name: python
x-langfuse-sdk-version: 5.2.0
x-langfuse-public-key: pk-lf-…
x-langfuse-ingestion-version: 4
langfuse/_client/span_processor.py
属性名(Langfuse 约定)
| 含义 | 属性 |
| trace 级(每个 span 都要带) | langfuse.trace.name · user.id/langfuse.user.id · session.id/langfuse.session.id · langfuse.trace.tags · langfuse.trace.metadata.<key> · langfuse.version · langfuse.release · langfuse.environment |
| observation | langfuse.observation.type(span、generation、event、embedding、agent、tool、chain、retriever、guardrail、evaluator)· .input · .output · .metadata.<key> · .level · .status_message |
| 生成类 | langfuse.observation.model.name · .model.parameters · .usage_details · .cost_details · .completion_start_time · .prompt.name · .prompt.version |
| 实验 | langfuse.experiment.id · .name · .dataset.id · .item.id · .item.expected_output … |
| 第三方约定(服务端识别) | gen_ai.*(OTel GenAI)· ai.*(Vercel AI SDK)· llm.*(OpenInference)等 |
langfuse/_client/attributes.py · packages/shared/src/server/otel/OtelIngestionProcessor.ts
user_id / session_id 的查找顺序
- span 属性:
langfuse.user.id、user.id、langfuse.observation.metadata.langfuse_user_id、langfuse.trace.metadata.langfuse_user_id、ai.telemetry.metadata.userId - 资源属性:
langfuse.user.id、user.id - 最后:
user.name(span,再资源)
session 还识别 gen_ai.conversation.id。服务端不会在批内从根 span 继承;资源属性对整个服务生效,可用于“一个服务 = 一个租户”。
SDK 传播
from langfuse import get_client, propagate_attributes
with propagate_attributes(user_id="u-42", session_id="s-7", tags=["prod"], metadata={"tenant": "acme"}):
... # 这里创建的每个 span 都带上这些属性
# as_baggage=True:通过 OTel Baggage(HTTP 头)传给下游服务 —— 会发给所有下游,不要放敏感值
langfuse/_client/propagation.py
4 转存与队列
| 路径 | S3 对象键 | 队列 | 入队延迟 |
| OTel | {LANGFUSE_S3_EVENT_UPLOAD_PREFIX}otel/{projectId}/yyyy/mm/dd/hh/mm/{uuid}.json,内容为整批 resourceSpans 数组 | otel-ingestion-queue / secondary-otel-ingestion-queue | 0 |
| 旧 REST | {PREFIX}{projectId}/{entityType}/{entityId}/{eventId}.json,同一实体的多次更新同目录 | ingestion-queue / secondary-ingestion-queue | 非评分 ≤ 5 s;UTC 23:45–00:15 用 LANGFUSE_INGESTION_QUEUE_DELAY_MS |
OTel 队列任务只带指针:fileKey、authCheck.scope(projectId、orgId)、sdkName、sdkVersion、ingestionVersion、propagatedHeaders。分片键 {projectId}-{fileKey}。OtelIngestionProcessor.ts publishToOtelIngestionQueue · processEventBatch.ts · eventBucketPath.ts
事件桶加密:LANGFUSE_S3_EVENT_UPLOAD_SSE(AES256 / aws:kms)+ LANGFUSE_S3_EVENT_UPLOAD_SSE_KMS_KEY_ID。媒体桶有对应的 LANGFUSE_S3_MEDIA_UPLOAD_SSE*。
5 后台处理与写入路径
下载 S3→脱敏回调(EE,可选)→processToEvent→createEventRecord 补全→观测级评估调度→字段溢出(可选)→ClickhouseWriter
| 补全 | 做法 |
| 提示词 | prompt.name + version → Postgres → prompt_id |
| 模型 | provided_model_name → 价格表匹配 → model_id |
| 用量 | 未提供时按模型分词器计算 token |
| 用量与成本 | 客户端提供的保留在 provided_usage_details / provided_cost_details;补全结果写入 usage_details / cost_details |
| 元数据 | flattenJsonToPathArrays → metadata_names[] / metadata_values[] |
写入缓冲:LANGFUSE_INGESTION_CLICKHOUSE_WRITE_BATCH_SIZE=1000、…_WRITE_INTERVAL_MS=1000、…_MAX_ATTEMPTS=3。worker/src/services/IngestionService/index.ts · worker/src/env.ts
6 表结构
events_full(迁移 0039)
ENGINE = ReplacingMergeTree(event_ts, is_deleted)
PARTITION BY toYYYYMM(start_time)
PRIMARY KEY (project_id, toStartOfMinute(start_time), xxHash32(trace_id))
ORDER BY (project_id, toStartOfMinute(start_time), xxHash32(trace_id), span_id, start_time)
SAMPLE BY xxHash32(trace_id)
SETTINGS index_granularity_bytes = '64Mi', merge_max_block_size_bytes = '64Mi', enable_full_text_index = 1, …
| 组 | 列 | 类型 / 说明 |
| 标识 | project_id, trace_id, span_id, parent_span_id | String;根 span 的 parent 为空串;历史 trace 为虚拟根 t-<trace_id> |
| 时间 | start_time, end_time, completion_start_time | DateTime64(6)(v3 为 (3)) |
| trace 属性 | trace_name, user_id, session_id, tags, release, version, environment | 直接在行上 |
| 状态 | name, type, level, status_message, is_app_root, bookmarked, public | bookmarked/public 用轻量更新 |
| 模型 | model_id, provided_model_name, model_parameters | model_parameters 为 JSON 字符串 |
| 用量成本 | provided_usage_details, usage_details, provided_cost_details, cost_details | Map(LowCardinality(String), UInt64 / Decimal(18,12)) |
| 派生 | calculated_input_cost, calculated_output_cost, calculated_total_cost | MATERIALIZED:按键名含 input/output 求和 |
| total_cost | ALIAS cost_details['total'] |
| I/O | input, output, input_length, output_length | String ZSTD(3);长度 MATERIALIZED lengthUTF8 |
| 元数据 | metadata_names, metadata_values | 两个等长 Array(String) |
| 工具 | tool_definitions, tool_calls, tool_call_names | Map / Array |
| 实验 | experiment_id, experiment_name, experiment_item_* … | |
| 来源 | source, service_name/version, scope_name/version, telemetry_sdk_*, ingestion_api_key, ingestion_sdk_name/version | source 取值含 otel-dual-write、ingestion-api-dual-write 等 |
| 存储 | blob_storage_file_path, event_bytes, created_at, updated_at, event_ts, is_deleted | file_path 指回 S3 原始文件 |
索引:bloom_filter(span_id、trace_id、user_id、session_id);text 索引 splitByNonAlpha(lower(input)、lower(output)、metadata_names、metadata_values);ngrambf(metadata 拼接值、lower(name)、lower(trace_name)、lower(user_id)、lower(session_id))。canonical/0039, 0043, 0049
events_core(0040)与 events_core_mv(0041)
结构相同;物化视图写入时 leftUTF8(input, 200)、leftUTF8(output, 200)、每个 metadata 值截到 200。查询写逻辑表 events,编译期路由:读到被截断列 → events_full,否则 events_core;短值等值/前缀的 metadata 过滤在 core 上也正确;events_truncated 强制 core。packages/shared/src/server/query-ast/eventsRouting.ts
元数据展平规则
| 输入 | names | values |
{"tenant":"acme"} | tenant | acme |
{"req":{"id":"r-1","retry":2}} | req.id, req.retry | r-1, 2(字符串) |
{"flags":["a","b"]} | flags | ["a","b"](整个数组 JSON) |
{"pii":true,"note":null} | pii, note | true, "" |
读取还原:mapFromArrays(arrayReverse(metadata_names), arrayReverse(metadata_values))。键名本身含点时与路径无法区分。packages/shared/src/server/otel/utils.ts · query-ast/compile.ts
7 格式转换总表
| 层 | 编号 | 时间 | input / output | metadata | 其他 |
| ① OTel span(客户端) | traceId 16 字节、spanId 8 字节 | startTimeUnixNano 整数 | 属性中的 JSON 字符串 | langfuse.*.metadata.<k> 多个属性 | 属性为 {key, value} 列表 |
| ② S3 原始文件 | protobuf 解析后的对象形式 | int64 → 十进制字符串 | 同上 | 同上 | 不脱敏、不修改 |
| ③ EventInput | 32 / 16 位十六进制字符串 | ISO 字符串 | 可为对象 | 对象 | 未识别属性 → metadata.attributes / resourceAttributes |
| ④ EventRecordInsertType | 同上 | ClickHouse 时间字符串 | 字符串 | names[] / values[] | + model_id、usage、cost、prompt_id、source、blob_storage_file_path |
| ⑤ events_full | String | DateTime64(6) | String(可选:溢出为媒体引用) | Array(String) ×2 | Map / Decimal(18,12) |
| ⑥ events_core | 同上 | 同上 | ≤ 200 字符 | 每个值 ≤ 200 | MV 生成 |
| ⑦ API v2 / 导出 | id、trace_id… | ISO;latency 秒或毫秒 | 按字段组;core 表时为截断内容 | 对象 | 按 span 去重 |
8 大体积数据:七道关口
| 关口 | 规则 | 默认 | 源码 |
| ① 客户端 | 每批字节上限;媒体识别后抽走(data:*;base64、Anthropic {type: base64}、Gemini inline_data) | LANGFUSE_OTEL_MAX_BATCH_SIZE_BYTES 未设则用导出器默认 | langfuse/_client/span_processor.py · _task_manager/media_manager.py |
| ② 入口 | OTel 请求体上限;旧 REST 4.5 MB | 512 MiB;>16 MiB 警告 | otelRequestBody.ts · pages/api/public/ingestion.ts |
| ③ 媒体上传 | POST /api/public/media 申请预签名 URL;sha256 去重(已存在则 URL 为空);键 {prefix}{projectId}/{mediaId}.{ext} | 单文件 LANGFUSE_S3_MEDIA_MAX_CONTENT_LENGTH = 1,000,000,000 | pages/api/public/media · server/media/mediaService.ts |
| ④ 服务端抽取 / 字段溢出 | 服务端媒体抽取;input/output/每个 metadata 值 > 阈值 → 上传媒体桶,原位替换为 @@@langfuseMedia:type=text/plain|id=…|source=field_size_limit@@@;上传失败保留原值 | LANGFUSE_OTEL_MEDIA_UPLOAD_ENABLED=false;LANGFUSE_OBSERVATION_FIELD_OVERFLOW_ENABLED=false;阈值 2 MiB | features/observation-field-overflow |
| ⑤ 写入兜底 | ClickHouse “JSON 过大”或 Node “Invalid string length” → 批次对半拆分;单条仍失败 → input/output > 1M 字符截到前 500K + [TRUNCATED: Field exceeded size limit] | — | services/ClickhouseWriter/jsonRecords.ts |
| ⑥ events_core | 200 字符截断 | — | 0041 |
| ⑦ 导出 | 选了 io 或 metadata 字段组才读 events_full | — | repositories/events.ts buildEventsForBlobStorageExportQuery |
写入兜底按对象形式的 metadata 处理,不覆盖 events 表的 metadata_values 数组。元数据里的大值只能靠字段溢出处理。
建议:媒体在客户端抽走;打开字段溢出;检索结果等长文本在客户端或采集器裁剪;导出选 io / metadata 字段组;元数据只放短值。
9 脱敏
| 位置 | 机制 | 覆盖 | 失败行为 | 局限 |
| ① 客户端 SDK | Langfuse(mask=fn),签名 fn(*, data, **kwargs);对 input/output/metadata 先抽媒体再 mask | 该 SDK 创建的 span | 抛异常 → 整段替换为 <fully masked due to failed mask function>(metadata 字典逐键) | 每个应用都要接;第三方埋点不经过 |
| ② OTel Collector | attributes / transform 处理器改写或删除属性 | 经过采集器的所有 span | 取决于采集器配置 | I/O 是整段 JSON 字符串;规则语言弱 |
| ③ 服务端回调(EE) | Worker 下载 S3 后 POST 整批给回调,用返回结果继续 | 仅 OTel 路径 | 默认放行(原始数据入库);FAIL_CLOSED=true 丢弃整批并记录 S3 key | 需企业许可证;旧 REST、Scores API 不经过;S3 原件未脱敏 |
回调契约
POST {LANGFUSE_INGESTION_MASKING_CALLBACK_URL}
Content-Type: application/json
X-Langfuse-Org-Id: <orgId>
X-Langfuse-Project-Id: <projectId>
<LANGFUSE_INGESTION_MASKING_PROPAGATED_HEADERS 中列出的上报请求头>
[ { "resource": {...}, "scopeSpans": [ { "scope": {...}, "spans": [ ... ] } ] } ] ← 原始 resourceSpans
→ 200,响应体:同结构的 JSON(改写后的 resourceSpans)
| 变量 | 默认 | 说明 |
LANGFUSE_INGESTION_MASKING_CALLBACK_URL | 未设 | 设置且有 EE 许可证才启用;无许可证时告警并忽略 |
…_CALLBACK_TIMEOUT_MS | 500 | 单次超时 |
…_MAX_RETRIES | 1 | 退避 100·2^(n−1) ms,封顶 1 s |
…_CALLBACK_FAIL_CLOSED | false | true:丢弃并记录 S3 key,可用 worker/src/scripts/replayIngestionEventsV2 重放 |
…_PROPAGATED_HEADERS | 空 | 逗号分隔,大小写不敏感;Web 写进队列任务,Worker 透传给回调 |
packages/shared/src/server/ee/ingestionMasking · worker/src/queues/otelIngestionQueue.ts · worker/src/features/otel-ingestion/prepareOtelBatch.ts(开启 early media 时顺序:脱敏 → 校验 → 抽取媒体)
建议的三层方案
- 客户端:SDK mask 处理确定字段(手机号、证件号);媒体在此抽走。
- 网关:OTel Collector 作为唯一入口;统一加
x-langfuse-ingestion-version: 4;补租户资源属性;删除不允许出域的属性;限制批大小;采样保留根 span。
- 服务端:脱敏回调 +
FAIL_CLOSED=true,做模型识别等复杂处理;事件桶与媒体桶 SSE-KMS + 最小权限 + 短生命周期;打开字段溢出。
- 导出侧:导到自有桶,先读清单,按 id 去重,用 metadata 数组还原结构;回查原件用
blob_storage_file_path。
10 双写与迁移
写入模式
| legacy | dual | events_only(默认) |
| 写 v3 表 | 是 | 是 | 否 |
| 写 events 表 | 否 | 是 | 是 |
NATIVE_OTEL_BEHAVIOUR=direct | 启动报错 | 可用 | 可用(默认) |
NATIVE_OTEL_BEHAVIOUR=dual_write | 可用 | 可用 | 启动报错 |
另:events_only 要求 LANGFUSE_MIGRATION_V4_ALLOW_PREVIEW_OPT_IN=true。worker/src/env.ts validateV4Flags
双写传播作业
- 暂存表
observations_batch_staging:按 s3_first_seen_timestamp 3 分钟分区,TTL 48 h,ttl_only_drop_parts。
- Redis 游标
langfuse:event-propagation:last-processed-partition;只处理早于 now() − LANGFUSE_EXPERIMENT_EVENT_PROPAGATION_PARTITION_DELAY_MINUTES(默认 10)的分区。
- SQL:
observations_batch_staging FINAL LEFT JOIN traces(LIMIT 1 BY project_id, id 取最新,回看 ≤ 7 天)→ INSERT events_full;mapConcat(obs.metadata, trace.metadata),observation 优先;缺父节点 → t-<trace_id>;排除 24 h 内的实验 trace;parallel_view_processing=1;超时 30 min。
- 健康检查:
GET :3030/api/health?failIfEventPropagationStuck=true,阈值 LANGFUSE_EVENT_PROPAGATION_STUCK_THRESHOLD_MINUTES(代码默认 35)。
worker/src/features/eventPropagation/handleEventPropagationJob.ts · canonical/0038
历史回填(后台迁移链)
createRootSpansFromTraces:span_id = t-||trace_id,type SPAN,按 yyyymm 分区分块rewriteObservationsToPidTidSortingbackfillEventsFullFromObservations(JOIN traces 补属性,trace 元数据不复制)backfillEventsFullFromDatasetRunItemsdropPidTidSortingTables(LANGFUSE_BACKGROUND_MIGRATION_V4_DROP_PID_TID_SORTING_TABLES)
升级前 SELECT * FROM background_migrations WHERE finished_at IS NULL 必须 0 行;回填约需现有 ClickHouse 数据 3 倍磁盘;也可不回填,双写一个完整保留周期。
11 导出
| 项 | 值 |
| 对象键 | {prefix}{projectId}/{observations_v2|scores}/{maxTimestamp}.{ext}[.gz];时间格式如 2026-11-06T10-00-00(ISO 去毫秒、冒号换连字符) |
| 清单 | {prefix}{projectId}/manifests/{maxTimestamp}.json;清单出现才算完成,按 files[].key 读取 |
| 格式 | Parquet(默认)、CSV、JSON、JSONL;文本可 gzip |
| 频率 | 20 分钟、每小时、每天、每周 |
| 时间窗 | 按 start_time [min, max);相邻窗口共享边界 → 读取方按 id 去重;查询内 LIMIT 1 BY span_id, project_id |
| 读哪张表 | 字段组含 io 或 metadata → events_full,否则 events_core |
| JSONL 直通 | ClickHouse JSONEachRow 直出,Worker 不逐行解析;整形写在 SQL 中 |
| 单位 | latency / time_to_first_token:2026-04-01 后创建的配置为秒,之前毫秒 |
| Parquet | 不含 input_price / output_price / total_price;清单行数为 null;ClickHouse < 25.11 可能不完整 |
| 重试 | 覆盖同名对象 |
字段组:core(必选)· basic · time · io · metadata · model · usage · prompt · metrics · trace_context · tools。packages/shared/src/domain/observation-field-groups.ts · worker/src/features/blobstorage
12 环境变量速查
| 变量 | 默认 | 作用 |
LANGFUSE_MIGRATION_V4_WRITE_MODE | events_only | legacy / dual / events_only |
LANGFUSE_MIGRATION_V4_NATIVE_OTEL_BEHAVIOUR | direct | direct / dual_write |
LANGFUSE_MIGRATION_V4_ALLOW_PREVIEW_OPT_IN | true | v4 读路径与 v2 API |
LANGFUSE_MIGRATION_V4_OTEL_DIRECT_WRITE_ORG_CREATED_CUTOFF | 未设 | Cloud 用,自部署保持未设 |
LANGFUSE_BACKGROUND_MIGRATION_V4_ENABLE_HISTORIC_BACKFILL | true | 历史回填 |
LANGFUSE_BACKGROUND_MIGRATION_V4_DROP_PID_TID_SORTING_TABLES | false | 回填后清中间表 |
LANGFUSE_EXPERIMENT_EVENT_PROPAGATION_PARTITION_DELAY_MINUTES | 10 | 双写分区延迟 |
LANGFUSE_EVENT_PROPAGATION_STUCK_THRESHOLD_MINUTES | 35 | 双写卡住阈值 |
LANGFUSE_OTEL_INGESTION_MAX_BODY_BYTES | 536870912 | OTel 请求体上限 |
LANGFUSE_OTEL_MEDIA_UPLOAD_ENABLED | false | 服务端媒体抽取 |
LANGFUSE_OTEL_EARLY_MEDIA_EXTRACTION_ENABLED | false | Rust 原生提前抽取 |
LANGFUSE_OBSERVATION_FIELD_OVERFLOW_ENABLED | false | 字段溢出到媒体桶 |
LANGFUSE_OBSERVATION_FIELD_SIZE_LIMIT_BYTES | 2097152 | 溢出阈值 |
LANGFUSE_S3_MEDIA_MAX_CONTENT_LENGTH | 1000000000 | 单个媒体文件上限 |
LANGFUSE_INGESTION_CLICKHOUSE_WRITE_BATCH_SIZE / _INTERVAL_MS / _MAX_ATTEMPTS | 1000 / 1000 / 3 | 写入缓冲 |
LANGFUSE_INGESTION_MASKING_* | 见 §9 | 脱敏回调 |
LANGFUSE_S3_EVENT_UPLOAD_SSE / _SSE_KMS_KEY_ID | 未设 | 事件桶加密 |
13 文档与代码的差异
| 项 | 文档 | 代码(6918181) |
| “实时写入”SDK 版本 | Python ≥ 4.7.0、JS ≥ 5.4.0 | 服务端直写:Python ≥ 4.0.0、JS ≥ 5.0.0(checkHeaderBasedDirectWrite);4.7 / 5.4 是 SDK 复制 trace 属性的版本 |
| 双写卡住阈值 | 15 分钟 | 35 分钟 |
| 双写延迟 | 约 15 分钟 / 博客“3 分钟 + 5 分钟” | 3 分钟分区 + 10 分钟延迟 |
| JSON 类型 | 25.12 用于 JSON 类型 | events 表 metadata 为两个 Array(String),未用 JSON 列 |
14 改造风险清单
- 同一 span 重复上报 → 两条记录(读取不保证去重)。
- trace 属性未复制到子 span → 子 span 按用户/会话查不到。
- 未识别属性进
metadata.attributes / metadata.resourceAttributes,嵌套键不能直接过滤。
events_only 下 /api/public/ingestion 只收评分。
- 脱敏回调放行模式下,回调故障期间未脱敏数据入库;S3 原始事件始终未脱敏。
- 大值放在 metadata 时不受写入兜底截断,可能导致写入失败;打开字段溢出。
- 导出:latency 单位随创建日期不同;Parquet 缺单价列;导出未选 io/metadata 时内容是截断的。
- 采集器采样丢掉根 span → 层级断裂、根上的整体输入输出缺失。
15 代码位置索引
| 主题 | 文件 |
| OTel 入口 | web/src/pages/api/public/otel/v1/traces/index.ts · web/src/server/otel/processOtelIngestion.ts · otelRequestBody.ts |
| 旧 REST 入口 | web/src/pages/api/public/ingestion.ts · packages/shared/src/server/ingestion/processEventBatch.ts · eventBucketPath.ts |
| span → 事件 | packages/shared/src/server/otel/OtelIngestionProcessor.ts · otel/utils.ts |
| Worker OTel 队列 / 写入路径 | worker/src/queues/otelIngestionQueue.ts · worker/src/features/otel-ingestion/ |
| 补全与写入 | worker/src/services/IngestionService/index.ts · worker/src/services/ClickhouseWriter/ |
| 字段溢出 | worker/src/features/observation-field-overflow/processObservationFieldOverflow.ts |
| 媒体 | web/src/pages/api/public/media/ · packages/shared/src/server/media/mediaService.ts · otel/OtelMediaProcessor.ts |
| 脱敏回调 | packages/shared/src/server/ee/ingestionMasking/ |
| ClickHouse 迁移 | packages/shared/clickhouse/migrations/canonical/0038–0049 |
| 读路由 | packages/shared/src/server/query-ast/eventsRouting.ts · repositories/events.ts |
| 双写传播 | worker/src/features/eventPropagation/handleEventPropagationJob.ts |
| 回填 | worker/src/backgroundMigrations/ |
| 导出 | worker/src/features/blobstorage/ · packages/shared/src/domain/observation-field-groups.ts |
| 环境变量 | worker/src/env.ts · web/src/env.mjs · packages/shared/src/env.ts |
| Python SDK | langfuse/_client/span_processor.py · attributes.py · propagation.py · span.py · _task_manager/media_manager.py · media.py |