本文详细介绍用户体验监控 Web SDK 的各功能接口,帮助您更灵活、深度地使用 SDK。
初始化
自定义事件
// 上报自定义事件tdem.track('button_click', {tags: { button_id: 'submit' },properties: { page: '/checkout' },});
自定义测速
// 直接上报耗时(毫秒)tdem.measure('api_latency', 320, {tags: { api: '/user/info' },});// 或使用计时器tdem.startMeasure('render');// ... 执行操作 ...tdem.endMeasure('render');
日志上报
tdem.captureMessage('用户完成注册', {level: 'info',tags: { step: 'register' },});
异常上报
try {riskyOperation();} catch (e) {tdem.captureException(e, {tags: { module: 'payment' },});}
全局标签管理
tdem.setTags({ role: 'admin', team: 'dev' }); // 设置/追加标签tdem.removeTags(['team']); // 移除指定标签tdem.clearTags(); // 清空所有标签
用户标识管理
tdem.setUser('user-456'); // 设置用户tdem.clearUser(); // 清除用户
页面管理
// 手动上报 PVtdem.reportPageView('/checkout', { pageTitle: '结算页' });// 虚拟页面(弹窗、Tab 等)tdem.setPage('/modal/confirm', { pageTitle: '确认弹窗' });// 弹窗关闭后恢复tdem.leavePage();
挣扎事件上报
// 业务主动上报自定义挣扎事件(无需理解或填写 owner)tdem.trackStruggle('payment_declined', {severity: 'high', // 可选:low | medium | high,默认 medium;传其他值本次上报会被丢弃tags: { channel: 'checkout' },properties: {reason: 'risk_rejected',retry_count: 2,},});
表单提交结果上报
// 上报表单提交结果(需传 status)tdem.reportFormSubmitResult({status: 'success', // 必填:success | faileventName: 'form_submit', // 可选:事件名formId: 'register-form', // 可选:表单标识fieldId: 'email', // 可选:字段标识validationCode: 'E001', // 可选:校验码validationMessage: '邮箱格式错误', // 可选:校验信息(最长 256 字符)tags: { channel: 'web' }, // 可选:标签});
原始事件上报
// 直接上报 TDEMEvent 格式的事件(高级用法,SDK 会自动补齐 page_url、replay_id 与 tags)tdem.reportTDEMEvent({event_id: 'evt_xxx',event_type: 'custom',event_category: 'custom',timestamp: Date.now(),page_url: location.href,data: {event_name: 'my_event',tags: { key: 'value' },},});
手动上报 Web Vitals
// 需配置 webVitals: { manualReport: true }tdem.reportWebVitals(); // 使用 SDK 采集的数据tdem.reportWebVitals({ LCP: 2500, FCP: 1800 }); // 使用自定义数据
扩展设备信息(Bean)
tdem.extendBean('customKey', 'customValue'); // 追加自定义键值到公共 Beanconst bean = tdem.getBean(); // 获取 Bean 查询串,如 id=xxx&uin=yyy&from=zzz
配置修改与销毁
tdem.setConfig({ userId: 'new-user', env: 'gray' }); // 动态修改配置tdem.destroy(); // 销毁实例
setConfig 对象浅合并进当前配置,不会重新初始化插件。因此只有后续事件读取的字段才真正生效,例如 userId、defaultTags、env、version、pageUrl;插件级开关(如 sessionReplay、struggleMonitor 的 enabled)需在初始化时确定,运行期修改不会重新挂载对应插件。与之相对,
setUser / setTags 这类接口本身就是对上述字段的封装,可放心在运行期调用:tdem.setUser('user-456'); // 等价于 setConfig({ userId: 'user-456' })tdem.setTags({ role: 'admin' }); // 追加进 defaultTags,与事件级 tags 自动合并
destroy() 用于彻底结束实例:注销已挂载的插件、清空生命周期,并用空实现替换实例方法。默认保留对象外壳以规避外部持有的引用报错,传入 destroy(true) 会进一步清空对象属性并断开原型链。回调钩子
SDK 提供一组回调钩子,用于接管上报链路上的关键判定。除
api.retCodeHandlerAsync 为异步形式外,其余回调都在 SDK 内部同步调用。各回调对异常的处理并不统一:一部分在调用处带
try/catch,抛错会被吞掉并按原值继续(如 beforeReport、beforeRequest、api.retCodeHandler、api.isSlowApi);另一部分没有兜底,抛错会向上抛出(如 afterRequest、pagePerformance.urlHandler、api.resourceTypeHandler)。因此建议所有回调内部自行 try/catch,不要依赖 SDK 的兜底。接口类回调(配置在
api 下):配置项 | 说明 |
api.retCodeHandler | (responseBody, url, ctx, payload) => { code, isErr },自定义业务返回码解析。返回的 code 作为上报中的返回码,isErr 为 true 时按失败请求处理;未返回 code 或回调抛错时回落为 unknown。 |
api.retCodeHandlerAsync | (responseBody, url, ctx, callback) => void,异步版返回码解析。参数 callback 是回调函数,需在其内部调用 callback({ code, isErr }) 回填结果。该回调只在 XHR 链路上生效,fetch 请求不会读取它;在 XHR 链路上它优先于 retCodeHandler。 |
api.resourceTypeHandler | (url) => string,自定义资源类型判定,返回值作为上报的资源类型,未返回时回落为 static。 |
api.reqParamHandler | (body, url, ctx) => any,自定义请求参数处理,仅在 apiDetail 为 true 时生效;返回值会被序列化后截断至 10240 字符写入上报。 |
api.resBodyHandler | (response, url, ctx) => any,自定义响应体处理,仅在 apiDetail 为 true 时生效;返回值同样序列化后截断至 10240 字符。 |
api.isSlowApi | ({ duration }) => boolean,自定义慢请求判定,返回 true 即按慢请求上报;抛错时回落为 false 并输出 [TDEM] isSlowApi function happen error 警告。 |
说明:
慢请求判定是一条短路链:配置了
api.isSlowApi 就只以它的返回值为准(不再看阈值);未配置时用 api.slowThreshold;两者都没有时用内置默认 1000ms。生命周期回调(配置在顶层):
配置项 | 说明 |
beforeReport | (log) => boolean,单条事件上报前过滤,返回 false 丢弃该条。 |
beforeRequest | ({ logs, logType }) => logs | false,请求发出前处理,可整体替换 logs,返回 false 丢弃。 |
afterRequest | (log) => boolean,请求发出后回调,返回 false 阻断上报。 |
beforeReportSpeed | (log) => boolean,测速事件上报前过滤。 |
插件回调:
配置项 | 说明 |
pagePerformance.urlHandler | () => string,自定义页面地址获取函数,返回值经 encodeURIComponent 后作为上报的 page_url。 |
blankScreen.customBlankScreenDector | () => boolean,自定义白屏检测,返回 true 判定为白屏;抛错时回落为 false,默认 null。 |
sessionReplay.beforeSend | (payload) => payload | null,回放数据上传前回调,返回 null 取消本次上传。 |
sessionReplay.onError | (error: Error) => void,回放数据上传失败回调。回放上传有独立于 reportRetry 的失败重试:最多重试 3 次,间隔按 1s / 2s / 4s 指数退避。此回调仅用于业务侧旁路记录,不影响上述重试行为。 |
struggleMonitor.custom.detector | ({ event, tdem }) => ReportStruggleParams | ReportStruggleParams[] | null | void,自定义挣扎事件检测器,入参可拿到当前事件与 SDK 实例;返回空值表示本次不产生挣扎事件。 |
retCodeHandler 用于把业务自定义的返回码约定翻译成 SDK 可理解的成功/失败,适用于后端统一在响应体里返回 code 字段、HTTP 状态码恒为200的场景:const tdem = new TDEM({id: 'YOUR_APP_ID',api: {// 4 个参数:响应体、请求 URL、请求上下文、请求体retCodeHandler(responseBody, url, ctx, payload) {let data = responseBody;if (typeof data === 'string') {try { data = JSON.parse(data); } catch (_e) {}}const code = String(data?.body?.code ?? '');// isErr 为 true 时按失败请求上报return { code, isErr: code !== '0' && code !== '200' };},},});
若返回码需要异步解析(例如要先解密或查缓存),改用
retCodeHandlerAsync。此时第4个参数是回调函数,必须在拿到结果后调用它:const tdem = new TDEM({id: 'YOUR_APP_ID',api: {// 第 4 个参数是回调函数,必须调用它回填结果retCodeHandlerAsync(responseBody, url, ctx, callback) {checkCode(responseBody).then(({ code, isErr }) => {callback({ code: String(code), isErr });});},},});
同时在 XHR 链路配置两个回调时,只有
retCodeHandlerAsync 会执行;fetch 链路始终走 retCodeHandler,因为它不读取异步版。isSlowApi 适合按业务语义判定慢请求,优先级高于 slowThreshold:const tdem = new TDEM({id: 'YOUR_APP_ID',api: {slowThreshold: 2000,// 优先级高于 slowThreshold;返回 true 即按慢请求上报isSlowApi({ duration }) {return duration > 3000;},},});
beforeReport 可在事件真正入队前做最后一道过滤,返回 false 的事件会被丢弃:const tdem = new TDEM({id: 'YOUR_APP_ID',// 返回 false 丢弃该条事件beforeReport(log) {return !(log.url && log.url.includes('/health'));},});
struggleMonitor.custom.detector 用于把业务自己的「受挫信号」接入挣扎事件,返回的对象会被 SDK 自动补齐 type(默认 custom_struggle)、ownerType(默认 app)与 ownerKey(默认当前页面地址):const tdem = new TDEM({id: 'YOUR_APP_ID',struggleMonitor: {enabled: true,custom: {enabled: true,// 返回空值表示本次不产生挣扎事件detector({ event, tdem }) {if (event.data?.event_name !== 'checkout_failed') return null;return {ownerKey: '/checkout',payload: { reason: 'risk_rejected' },};},},},});
reportRetry 的 retryInterval 同样支持传函数,按已重试次数动态计算下次重试间隔(毫秒),默认实现为 (retriedCount) => 1000 * (2 << retriedCount),即按2s / 4s / 8s 指数退避:const tdem = new TDEM({id: 'YOUR_APP_ID',reportRetry: {maxRetryCount: 10,retryInterval: (retriedCount) => 1000 * Math.pow(2, retriedCount),whenRetryEndStillFail: 'discard',},});
另有2个回调在运行时生效,但未出现在
.d.ts 类型声明中。接入方从类型提示看不到它们,属于「可用未公开」,使用前请以实际接入版本的运行结果为准:onBeforeRequest:(requestOptions, tdem) => requestOptions | false,上报请求真正发出前的最后一站。可改写 requestOptions(含 url / method / data / sendBeacon 等)后返回;返回假值则丢弃本次请求并输出 Sending request blocked 警告;返回对象但缺少 url 时会输出参数提示警告。modifyRequest:(requestOptions) => requestOptions,同为请求改写钩子,但只接受「返回带 url 的对象」这一种改写方式;抛错不会中断链路,仅输出 console.error。其余回调的配置示例
api.resourceTypeHandler:自定义资源类型,返回值写进上报的 type 字段,未返回时回落 static:const tdem = new TDEM({id: 'YOUR_APP_ID',api: {// 入参是资源 URL,返回自定义类型字符串resourceTypeHandler(url) {if (/\\.(png|jpg|jpeg|webp|gif)$/i.test(url)) return 'image';if (/\\.(js|css)$/i.test(url)) return 'static';return 'static';},},});
api.reqParamHandler / api.resBodyHandler: 仅在 apiDetail 为 true 时生效,用于把请求体 / 响应体里的业务字段带进上报。注意是位置参数,ctx 在 fetch 链路下为 undefined:const tdem = new TDEM({id: 'YOUR_APP_ID',api: {apiDetail: true,// (请求体, 请求 URL, 上下文)reqParamHandler(body, url, ctx) {return typeof body === 'string' ? body.slice(0, 500) : JSON.stringify(body);},// (响应体, 请求 URL, 上下文)resBodyHandler(response, url, ctx) {const text = typeof response === 'string' ? response : JSON.stringify(response);return text.slice(0, 500);},},});
beforeRequest:请求发出前可整体替换或丢弃:const tdem = new TDEM({id: 'YOUR_APP_ID',beforeRequest({ logs, logType }) {// 返回 false 丢弃;返回 { logs, logType } 可整体替换if (logType === 'speed') return false;return { logs, logType };},});
afterRequest:请求发出后的最后一道闸门,返回 false 阻断上报:const tdem = new TDEM({id: 'YOUR_APP_ID',// 返回 false 阻断上报;此处屏蔽自身采集接口,避免自采集afterRequest(log) {return !String(log.url || '').includes('/api/v1/collect');},});
beforeReportSpeed:测速事件上报前过滤,返回 false 丢弃:const tdem = new TDEM({id: 'YOUR_APP_ID',beforeReportSpeed(log) {return !String(log.url || '').includes('favicon');},});
pagePerformance.urlHandler:自定义页面地址来源,SDK 会在返回值外层再套一次 encodeURIComponent:const tdem = new TDEM({id: 'YOUR_APP_ID',pagePerformance: {urlHandler() {return location.pathname + location.search;},},});
blankScreen.customBlankScreenDector:必须严格返回 true 才判定白屏;返回其他值或抛错都视为非白屏:const tdem = new TDEM({id: 'YOUR_APP_ID',blankScreen: {enabled: true,customBlankScreenDector() {const root = document.getElementById('app');return !!root && root.children.length === 0;},},});
sessionReplay.beforeSend / sessionReplay.onError:回放上传前的改写与失败旁路:const tdem = new TDEM({id: 'YOUR_APP_ID',sessionReplay: {enabled: true,// 返回假值(如 null)取消本次回放上传beforeSend(payload) {if (payload.events && payload.events.length > 5000) return null;return payload;},// 上传失败时回调;SDK 自身仍会按 3 次指数退避重试onError(error) {console.warn('[replay] upload failed', error);},},});
onBeforeRequest / modifyRequest:请求发出前的改写钩子(未在类型声明中暴露):const tdem = new TDEM({id: 'YOUR_APP_ID',// 返回假值则丢弃本次上报请求onBeforeRequest(options, tdem) {return options;},// 只接受「返回带 url 的对象」这一种改写方式modifyRequest(options) {return options;},});
上报就绪与延迟上报
SDK 默认在采集后立即上报(
reportImmediately 默认 true)。若将其设为 false,事件不会立刻发出,而是先进入等待队列:const tdem = new TDEM({id: '<project-key>',url: 'https://dem.rumt-zh.com',reportImmediately: false, // 采集后不立即上报,先入队等待});// 业务关键节点就绪后放行队列tdem.ready();
ready() 会逐个消费等待队列并开始上报;重复调用同样安全,队列为空时不做任何事。未调用
ready() 时队列会持续积压,控制台看不到任何上报请求:排查「配置了 reportImmediately: false 后没有数据」时,先确认是否遗漏了这一调用。版本与环境
TDEM.version; // SDK 版本号(静态属性)tdem.sdkVersion; // 当前实例的 SDK 版本号TDEM.environment; // 环境枚举,同时兼容其他端 SDK 的环境名tdem.config.env; // 当前实例实际生效的环境值
TDEM.version 与 tdem.sdkVersion 取值一致,均来自构建期注入的 SDK 版本。env 传给 SDK 后若不属于环境枚举,会被归一为 others。回放脱敏标记
会话回放会录制页面 DOM 与文本,SDK 默认开启高强度脱敏。除配置式开关外,还支持在 HTML 上直接标注,按元素粒度控制录制内容。
在元素上加下列属性即可(对子元素就近生效,即父容器标注会覆盖其后代):
HTML 属性 | 效果 |
data-tdem-mask | 该元素文本被遮罩,等价于默认遮罩类名 tdem-mask |
data-tdem-block | 该元素整体不录制,回放中呈现为空占位 |
data-tdem-ignore | 该元素及其子树完全忽略,不产生任何录制数据 |
<div data-tdem-mask>这段文本在回放中会被遮罩</div><div data-tdem-block>这块区域整体不录制</div><div data-tdem-ignore>整个子树都会被忽略</div>
SDK 同时内置一批默认选择器,语义与上表相互对应:
类别 | 默认选择器 |
遮罩 | .tdem-mask、[data-tdem-mask]、[data-sensitive]、.sensitive、.private |
阻止录制 | .tdem-block、[data-tdem-block]、.secret、[data-secret]、iframe[srcdoc]:not([src])、.replayer-wrapper |
忽略录制 | .tdem-ignore、[data-tdem-ignore]、input[type="file"]、input[type="password"] |
自定义选择器与默认选择器是累加关系,不会覆盖掉内置的默认值。也可通过
sessionReplay.privacy 统一声明:const tdem = new TDEM({id: '<project-key>',url: 'https://dem.rumt-zh.com',sessionReplay: {enabled: true,privacy: {mask: ['.my-mask'], // 追加遮罩选择器block: ['.my-block'], // 追加阻止录制选择器ignore: ['.my-ignore'], // 追加忽略录制选择器},},});
默认遮罩策略为全量遮罩:所有文本与所有输入框均被遮罩,媒体元素整体阻止录制。
maskAllText 不是独立旋钮,它随 maskAllInputs 联动。maskAllInputs 为 false 时它才为 false,该字段的独立配置不生效。此外 SDK 默认开启敏感数据自动检测,会在文本被记录前做脱敏,覆盖手机号、身份证号、邮箱、银行卡号四类。如需完全接管脱敏,可将
privacy.autoDetectSensitiveData 设为 false,并通过 privacy.maskTextFn 提供自定义脱敏函数:sessionReplay: {enabled: true,privacy: {autoDetectSensitiveData: false,maskTextFn: (text) => text.replace(/机密/g, '[已脱敏]'),},}
纳入自动检测的四类内容与脱敏后的形态:
类型 | 脱敏后形态 |
手机号 | 保留前3位与后4位,中间替换为 **** |
身份证号 | 保留前6位与后4位,中间替换为 ******** |
邮箱 | 用户名保留首尾字符,中间替换为 ***,域名不变 |
银行卡号 | 保留前4位与后4位,中间替换为 **** **** |
注意:
SDK 侧脱敏只作用于录制数据,不能替代服务端自身的隐私处理流程。
行为属性标记
除主动上报接口外,SDK 支持用 HTML 属性向挣扎检测声明业务语义,用于避免误判。两者都支持就近祖先匹配:
HTML 属性 | 效果 |
data-tdem-ignore-dead-click | 命中时不为该元素安排死点击判定 |
data-tdem-effective-action | 命中时将该元素视为有效操作,消解同期待判定的死点击 |
<!-- 明确不需要判定的装饰性入口 --><button data-tdem-ignore-dead-click>查看更多</button><!-- 点击后由业务自行处理跳转,SDK 不应判为死点击 --><div data-tdem-effective-action>提交订单</div>
这两项标记仅在开启
struggleMonitor 后生效(该插件默认关闭)。等价能力也可通过配置声明选择器:struggleMonitor: {enabled: true,click: {ignoreDeadClickSelectors: ['.js-no-dead-click'],effectiveActionSelectors: ['.js-effective-action'],},}
环境枚举
TDEM.environment.production // 生产环境TDEM.environment.development // 开发环境TDEM.environment.gray // 灰度环境TDEM.environment.pre // 预发布环境TDEM.environment.daily // 日发布环境TDEM.environment.local // 本地环境TDEM.environment.test // 测试环境TDEM.environment.others // 其他环境