一句话:SDK 自带第一方数据分析与错误上报——logEvent、logScreen、trace 和 recordError,并可选地关联到真实的 setUserId。事件在客户端缓冲,刷新发送到你自己的后端,所以既不用额外引入第三方分析库,数据也从不脱离你的掌控。
大多数应用第一天就会接入一个托管分析 SDK,不出一周就把用户行为发往了别人的服务器,还要在构建里再接一套账号。LingCode Cloud 把这些基础能力——事件、屏幕浏览、计时和捕获到的错误——折叠进你已经用于数据和认证的同一个客户端。你调用一个方法,SDK 把它批处理,最后它落进你自己的后端。本页讲清这种拆分为什么重要,以及每个调用具体怎么用。
数据分析与崩溃上报,是几乎每个应用都需要、又几乎都外包出去的两件事。外包意味着打包里多一个依赖、多一个手握用户行为副本的厂商,以及又一个要登录的仪表盘。SDK 的 telemetry 接口把这三样都去掉了:你记录的事件被发到与你数据所在相同的后端,由客户端批处理并刷新发送,不必再安装任何额外的库。
这套模型刻意做得很小。你记录四类信号——事件、屏幕浏览、性能 trace 和错误——并且可以选择性地附上一个真实的用户 id,让数据按「谁做了什么」分群。其余的一切(缓冲、批处理、刷新失败重试)都已替你处理好。
遥测是第一方的。事件累积在一个小的客户端缓冲里,被投递到你后端的遥测端点——它们不经过任何第三方分析服务。由于目的地就是你自己的 LingCode Cloud 后端,分析数据就停留在你应用其余数据所在的同一个地方。
一切都挂在 client.telemetry 上。下面的示例里客户端都叫 lingcode——在 /try 和 Mac 预览里它已经是 window.lingcode;在你自己的应用里,它是 LingCode.createClient(...) 返回的那个对象。
logEvent(name, params?) → void —— 记录一个具名事件。params 是一个可选的标量值记录(string | number | boolean),最多 25 个键。logScreen(name) → void —— 记录 screen_view 事件的便捷写法。trace(name, ms) → void —— 记录一次自定义性能测量,单位毫秒。recordError(err) → void —— 上报一个捕获到的错误。err 可以是 Error、一个 { message?, stack? } 对象,或一个字符串。立即刷新发送。setUserId(id) → void —— 把分析关联到一个真实的应用用户 id(需主动开启)。传 null 或 '' 来清除(例如登出时)。setUserProperties(props) → void —— 设置分群属性;它们会被合并、持久化,并附加到事件上。flush() → Promise<Result> —— 立即强制发送缓冲中的事件。主力是 logEvent。给它一个名字,并可选地附上一袋扁平的标量参数作为上下文:
lingcode.telemetry.logEvent('checkout_started', { plan: 'pro', amount: 29 });
参数是为了之后切片分析——套餐档位、商品数量、用户看到的实验变体。保持它们为标量(string、number 或 boolean),并把数量控制在 25 个键以内;嵌套对象和超量的键不该出现在一个事件里。
屏幕浏览有自己的简写,这样你就不必记住某种事件命名约定:
lingcode.telemetry.logScreen('Pricing');
这与一个针对 Pricing 屏幕的 screen_view 事件完全等价——在用户导航时,从每个路由或视图里调用它。
默认情况下,事件不与任何人关联。一旦有人登录,就通过把他们的应用用户 id 交给遥测层来主动开启;登出时再次清除它,让下一次会话从干净状态开始:
// 登录成功后
lingcode.telemetry.setUserId(user.id);
// 登出时
lingcode.telemetry.setUserId(null);
为了分群——套餐档位、语言区域、分组——设置用户属性。它们会与你之前设置的合并,在客户端持久化,并附加到后续事件上:
lingcode.telemetry.setUserProperties({ plan: 'pro', locale: 'en-US', cohort: '2026-Q2' });
在你的全局错误处理器,以及任何值得知道的 catch 块里,使用 recordError。它接受 Error、一个普通的 { message?, stack? } 对象,或一个裸字符串:
// 一个全局处理器
window.addEventListener('error', (e) => {
lingcode.telemetry.recordError(e.error ?? e.message);
});
// 或在一个 catch 块里
try {
await doRiskyThing();
} catch (err) {
lingcode.telemetry.recordError(err);
}
与其它调用不同,recordError 会立即刷新发送——一次把页面一起带走的崩溃,不该把它自己的报告也一并带走。
用 trace 记录一次自定义性能测量,单位毫秒——一张图片加载花了多久、一次查询跑了多久、一个屏幕用多久变得可交互:
const start = performance.now();
await loadHeroImage();
lingcode.telemetry.trace('image_load', performance.now() - start);
// 或者传入你已经有的测量值
lingcode.telemetry.trace('image_load', 320);
事件会自动批处理并刷新发送——通常你不必自己调用 flush()。它真正派上用场的两种情形:在一次硬跳转(整页重定向或关闭)之前,你需要保证投递;以及任何你想让缓冲事件立刻发出去的时刻。recordError 本身已经会刷新,所以崩溃报告不会丢。事件参数保持标量且不超过 25 个键,并依靠 setUserProperties 来承载你将用于筛选的分群——套餐档位、语言区域、分组。
flush() 返回一个 promise,让你能在页面离开前等待投递完成:
// 在硬跳转之前保证缓冲事件已发出
await lingcode.telemetry.flush();
window.location.href = '/checkout';
SDK 把批处理后的事件 POST 到你后端的遥测端点。你很少会直接调用它,但这里列出来,供非 JS 客户端和调试使用:
POST https://lingcode.dev/api/cloud/be/<backend-id>/telemetry
Content-Type: application/json
{
"events": [
{ "name": "checkout_started", "params": { "plan": "pro", "amount": 29 }, "timestamp": 1718064000000 }
]
}
一次失败的发送会以 telemetry_failed 的形式呈现。
setUserId 之前,事件都是匿名的。只有当你有依据时——一个已登录的会话以及你应用所要求的任何同意——才附上真实的用户 id,并在登出时用 setUserId(null) 清除它,这样一台共用设备就不会把两个人的分析数据混在一起。