Langfuse 自建部署的遥测(Telemetry)机制解析:默认上报行为、采集字段边界与 TELEMETRY_ENABLED 关闭方式
【免费下载链接】langfuse🪢 Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. 🍊YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse
Langfuse 在 Docker 自托管部署中默认会向集中式统计服务(PostHog)上报基础用量数据,用于改进产品与汇总整体使用情况。本篇以 web/src/features/telemetry/README.md 为核心,结合仓库中 遥测实现 与 环境变量定义,完整讲解遥测的默认行为、调度与触发机制、采集字段清单、隐私边界,以及如何通过TELEMETRY_ENABLED=false一键关闭。读完你可以精确回答"Langfuse 自建实例到底上报了什么、报给谁、怎么关"这三个问题,并能在源码层面定位每一个采集字段的产生位置。
一、遥测是什么:默认开启的匿名用量统计
根据官方 README,Langfuse 默认会自动向集中式服务器(PostHog)上报基础使用统计信息,其目的有两点:
- 理解 Langfuse 的实际使用方式,从而改进最相关的功能特性;
- 汇总整体使用量,用于内部及外部(如融资)报告。
这里的关键词是"统计"而非"数据"。遥测不包含原始 traces、prompts、observations、scores 或数据集内容,这一点在 README 中被明确承诺,也在后续的源码分析中得到印证(采集的都是count(*)聚合值,详见第四节)。
遥测默认开启仅针对 Langfuse OSS(开源版)自托管实例;Langfuse Cloud 使用独立的遥测体系,不受本机制约束(源码中对此有显式判断,见第六节)。
二、如何关闭:TELEMETRY_ENABLED 环境变量
README 给出的关闭方式非常直接:
# 在 docker-compose.yml 或 .env 中设置 TELEMETRY_ENABLED=false在仓库的 docker-compose.yml 中可以看到该变量的默认值:
environment: TELEMETRY_ENABLED: ${TELEMETRY_ENABLED:-true}即:不显式设置时默认为true(开启);显式设置为false即退出遥测。同理,docker-compose.build.yml 也使用了完全一致的默认值写法。
在服务端启动时,该变量会经过 web/src/env.mjs 的 Zod 校验:
// Telemetry TELEMETRY_ENABLED: z.enum(["true", "false"]).optional(),注意其取值域被严格限制为字符串"true"/"false"(可选),非法取值会在环境校验阶段直接报错,而不是被静默忽略。这也是后续源码中使用env.TELEMETRY_ENABLED === "false"做精确字符串比较的前提。
三、触发点与调度机制:并非定时常驻任务
很多人会以为遥测是一个常驻的定时任务,但从源码看它其实是惰性触发的:遥测入口函数telemetry()被挂载在多个 Public API 路由上,只要这些端点被访问,就会顺带执行一次遥测检查。
3.1 触发入口
从仓库搜索可以看到telemetry()被以下路由导入并调用:
- web/src/pages/api/public/health.ts:健康检查端点,
await telemetry()在健康检查之前执行; web/src/pages/api/public/ready.ts:就绪探针;web/src/pages/api/public/ingestion.ts:遥测数据摄入端点(SDK 上报 trace 的必经之路);web/src/pages/api/public/traces/index.ts:获取 traces 的公开 API;web/src/pages/api/public/prompts.ts:获取 prompts 的公开 API。
因此,一个正常运转的自建实例只要存在任意客户端访问(例如 SDK 上报数据、健康检查探针),遥测就有机会被触发——这也是它不需要独立定时任务调度器的原因。
3.2 多重前置过滤
telemetry()函数在执行真正逻辑前,会依次做以下判断(见 index.ts):
export async function telemetry() { try { // Only run in prod if (process.env.NODE_ENV !== "production") return; // Do not run in Langfuse cloud, separate telemetry is used if (env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION !== undefined) return; // Check if telemetry is not disabled, except for EE if ( env.TELEMETRY_ENABLED === "false" && env.LANGFUSE_EE_LICENSE_KEY === undefined ) return; // Do not run in CI if (process.env.CI) return; ...这四个条件值得逐条拆解:
| 条件 | 含义 |
|---|---|
NODE_ENV !== "production" | 仅在生产环境运行,开发/测试环境直接跳过 |
NEXT_PUBLIC_LANGFUSE_CLOUD_REGION !== undefined | 运行在 Langfuse Cloud 时跳过(Cloud 有独立遥测体系) |
TELEMETRY_ENABLED === "false"且未配置LANGFUSE_EE_LICENSE_KEY | OSS 实例可关闭遥测;但持有 EE License 的实例即使设置 false 也不会退出 |
process.env.CI | CI 环境直接跳过 |
值得特别强调的是第三个条件的例外逻辑:TELEMETRY_ENABLED=false的关闭效果仅对 OSS 生效;一旦检测到LANGFUSE_EE_LICENSE_KEY(企业版授权),遥测即使被显式关闭也依然会运行。README 中"For Langfuse OSS, you can opt out"的表述与源码完全吻合——关闭遥测是 OSS 用户的专属能力。
3.3 数据库驱动的作业调度(cron_jobs 表)
通过前置过滤后,jobScheduler()借助 Postgres 的cron_jobs表决定是否真正执行,其核心参数为:
const JOB_INTERVAL_MINUTES = Prisma.raw("720"); // 12 hours const JOB_TIMEOUT_MINUTES = Prisma.raw("10"); // 10 minutes- 执行间隔 12 小时:
cron_jobs表中last_run距当前时间超过 12 小时才允许再次运行; - 超时保护 10 分钟:
job_started_at超过 10 分钟视为执行超时,允许重新调度(防止进程崩溃导致作业永久卡死)。
调度过程分两步(index.ts):
- 无锁预检查:通过一条 SQL 判断是否该运行(
last_run是否过期,或表中尚无telemetry记录),此查询不拿锁,避免影响性能; - 加锁提交:
LOCK TABLE cron_jobs IN SHARE ROW EXCLUSIVE MODE后在事务中执行INSERT ... ON CONFLICT DO UPDATE,更新job_started_at,若并发下已有其他进程先拿到锁,则返回shouldRunJob: false。
clientId的生成也在这里完成:首次运行时取cron_jobs.state字段,若为空则用uuidv4()生成并写入,后续所有遥测上报都复用同一 ID,用于跨多次上报识别同一实例。
四、采集字段全清单:只有聚合计数,没有业务数据
当调度器判定"应该运行"时,posthogTelemetry()开始采集数据(index.ts)。逐一核对源码,采集内容全部是聚合计数或元数据,没有任何一条原始业务记录:
4.1 数据量计数(来自 ClickHouse 聚合查询)
traces、scores、observations、datasetRunItems 四类计数均通过 ClickHouse 按"创建时间区间"聚合查询获得,例如 traces 的查询定义在 packages/shared/src/server/repositories/traces.ts#L365-L394:
SELECT project_id, count(*) as count FROM traces WHERE created_at >= {start: DateTime64(3)} AND created_at < {end: DateTime64(3)} GROUP BY project_id其余三个统计函数同理,分别位于:
- scores.ts 的
getScoreCountsByProjectInCreationInterval - observations.ts 的
getObservationCountsByProjectInCreationInterval - dataset-run-items.ts 的
getDatasetRunItemCountsByProjectInCreationInterval
查询结果在服务端reduce为总量后再上报——ClickHouse 中原始的 project_id、时间戳等明细绝不会出库。
4.2 数据库表计数(Postgres)
- 总项目数:
prisma.project.count()(过滤deletedAt: null); - 数据集相关:
datasets、datasetItems、datasetRuns按创建时间区间计数; - Langfuse Assistant(In-App Agent)运行次数:
inAppAgentRun计数。源码注释明确写道该计数"无条件统计"——即使实例从未启用 Assistant,返回 0 本身就是有价值的答案。
4.3 用户邮箱域名(去标识化)
这是唯一涉及用户数据的采集项,但做了严格去标识化处理(index.ts):
SELECT substring(email FROM position('@' in email) + 1) as domain, count(id)::int as "userCount" FROM users WHERE email ILIKE '%@%' GROUP BY 1 ORDER BY count(id) desc LIMIT 30只取邮箱@之后的域名部分(如example.com)并按用户数排序取前 30,用于了解用户所属组织规模分布,不含任何邮箱地址本身。源码注释// Domains (no PII)直接点明了这一设计意图。
4.4 实例元数据
上报事件整体封装在posthog.capture()中(index.ts),事件属性完整清单如下:
| 属性 | 含义 |
|---|---|
distinctId | "docker:" + clientId,标识实例 |
event | 固定为"telemetry" |
langfuseVersion | 实例版本,来自 web/src/constants/VERSION.ts(当前为v4.32.0) |
userDomains | 用户邮箱域名 Top30 |
totalProjects/traces/scores/observations/datasets/datasetItems/datasetRuns/datasetRunItems/assistantRuns | 各类聚合计数 |
startTimeframe/endTimeframe | 本次统计的时间区间(ISO 字符串) |
eeLicenseKey | 是否配置了 EE License(透传环境变量) |
langfuseCloudRegion | 是否处于 Cloud 区域(透传环境变量) |
$set | 附加实例画像:environment(NODE_ENV)、userDomains、docker: true、langfuseVersion |
可以确认:README 中"不包含原始 traces、prompts、observations、scores、数据集内容"的承诺在实现层面完全成立——上报的只有count(*)级别的数字与版本、域名等元数据。
五、发送链路:ServerPosthog 与降级配置
数据上报走的是服务端 PostHog Node SDK,封装在 web/src/features/posthog-analytics/ServerPosthog.ts:
const FALLBACK_POSTHOG_KEY = "phc_zkMwFajk8ehObUlMth0D7DtPItFnxETi3lmSvyQDrwB"; const FALLBACK_POSTHOG_HOST = "https://eu.posthog.com"; const apiKey = env.NEXT_PUBLIC_POSTHOG_KEY ?? (telemetryEnabled ? FALLBACK_POSTHOG_KEY : null); const host = env.NEXT_PUBLIC_POSTHOG_HOST ?? (telemetryEnabled ? FALLBACK_POSTHOG_HOST : null);关键逻辑:
- 默认发送到
https://eu.posthog.com(PostHog EU 区域),使用仓库内置的 Langfuse 遥测 key; - 自建方可通过
NEXT_PUBLIC_POSTHOG_KEY/NEXT_PUBLIC_POSTHOG_HOST覆盖为自己的 PostHog 实例——这既是自托管 PostHog 的接入点,也意味着遥测目的地是可审计、可替换的; TELEMETRY_ENABLED=false时,fallback key 与 host 均被置为null,ServerPosthog构造出的实例为null,capture()成为空操作,从发送端彻底杜绝外发;- HIPAA 云区域(
NEXT_PUBLIC_LANGFUSE_CLOUD_REGION === "HIPAA")下会调用posthog.disable(),本地关闭 SDK(见 productAnalyticsAvailability.ts),这与遥测的 OSS 场景正交,但体现了同一套组件在合规区域的复用。
上报结束后调用await posthog.shutdown()确保缓存队列中的事件被刷出并优雅关闭连接。
六、异常兜底与部署注意事项
6.1 遥测绝不能拖垮业务
telemetry()整体包在 try/catch 中,任何异常只会记录logger.error("Telemetry, unexpected error:", error)而不会向上抛出(index.ts)。遥测被调用点如 health.ts 位于try块内且telemetry()自身已吞掉异常,因此即使 PostHog 不可达、数据库查询失败,也不会影响健康检查、数据摄入等核心业务路径。
6.2 部署时的配置建议
结合上述机制,自建 Langfuse 时的遥测配置实践可归纳为:
- 接受默认遥测:什么都不用配,默认开启,数据只发往 Langfuse 的 PostHog;
- 完全关闭:设置
TELEMETRY_ENABLED=false(仅 OSS 生效,配了 EE License 的实例不受此开关控制); - 数据自控:设置
NEXT_PUBLIC_POSTHOG_KEY与NEXT_PUBLIC_POSTHOG_HOST指向自己的 PostHog,把遥测数据纳入自己的分析平台; - 审计确认:可通过
cron_jobs表中name='telemetry'的记录查看last_run与state(clientId),确认真实的上报节奏与实例身份。
七、小结
Langfuse 的 Docker 遥测机制可以概括为一张清晰的因果链:公开 API 被访问 → 前置条件过滤(生产环境 / 非 Cloud / 非 CI / OSS 可关)→ cron_jobs 表按 12 小时间隔加锁调度 → 聚合计数 + 邮箱域名 + 版本元数据 → 经 ServerPosthog 发往 PostHog EU。其中每个环节都能在仓库源码中找到对应实现:README 声明、调度与采集实现、发送封装、环境变量定义、Compose 默认值。如果你关心自建实例的数据隐私边界,结论很明确:遥测只报"有多少"和"是什么版本",从不报"具体是什么内容",且 OSS 用户可以随时用一行环境变量彻底关闭。
【免费下载链接】langfuse🪢 Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. 🍊YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考