@kengic/client-telemetry
v2026.4.13
Published
Platform-neutral, offline-first client log telemetry SDK for Kengic applications
Readme
@kengic/client-telemetry
面向 PDA、Web 和后续客户端的轻量远程日志 SDK。包本身不依赖浏览器、uni-app 或 plus 全局;宿主应用只需配置存储、身份、上下文和传输适配器。
第一版只解决最核心的问题:脱敏日志、离线队列、用户/API 分桶、认证事务隔离、逐事件 ACK、失败重试、坏事件隔离、发送超时和协议/SDK版本标识。它不包含 Metrics、Session Replay、可视化控制台或插件体系。
安装
npm install @kengic/[email protected]包按现有发布配置发布到 npmjs.org(public),业务仓库只依赖版本号,不依赖本机目录。
最小接入
import { createClientTelemetry } from '@kengic/client-telemetry';
export const telemetry = createClientTelemetry({
namespace: 'WMS.PDA.Telemetry',
authStorageKey: 'WMS.PDA.Telemetry.Auth.v1',
context: () => ({
clientType: 'PDA',
appName: 'WMS-PDA',
appVersion: '1.0.0',
environment: 'production',
installationId: 'installation-id',
sessionId: 'session-id',
deviceBrand: 'Kengic',
deviceModel: 'PDA',
osName: 'Android',
osVersion: '13',
}),
storage: {
get: (key) => platformStorage.get(key),
set: (key, value) => platformStorage.set(key, value),
remove: (key) => platformStorage.remove(key),
keys: (prefix) => platformStorage.keys().filter((key) => key.indexOf(prefix) === 0),
},
identity: () => ({
apiOrigin: auth.apiOrigin,
userId: auth.userId,
token: auth.token,
uploadUrl: `${auth.apiOrigin}/clientTelemetry/logs`,
}),
route: () => router.currentRoute,
transport: {
send: (payload, identity, { keepalive, signal }) =>
sendToServer(payload, identity.token, { keepalive, signal }),
},
});
telemetry.initialize();
telemetry.info('APP', 'READY', 'application ready');业务代码只调用 trace/debug/info/warn/error/breadcrumb。SDK 不拦截业务 HTTP,也不会把 Token 写入日志队列。
多命名空间共享预算
Web 多标签页等宿主会创建多个 namespace。SDK 配置中的 maxGlobalBytes 只限制当前 namespace;若它们共用
localStorage 一类有总配额的存储,应在适配器外再配置同源遥测总预算:
import {
createBoundedClientTelemetryStorage,
createClientTelemetry,
} from '@kengic/client-telemetry';
const telemetryStorage = createBoundedClientTelemetryStorage({
storage: rawLocalStorageAdapter,
scopePrefix: 'ClientTelemetry.WEB.',
maxBytes: 2 * 1024 * 1024,
});
const telemetry = createClientTelemetry({
// 其余宿主配置省略
namespace: `ClientTelemetry.WEB.${tabId}`,
storage: telemetryStorage,
options: {
maxBucketBytes: 128 * 1024,
maxGlobalBytes: 128 * 1024,
},
});共享预算只统计并拒绝 scopePrefix 下的遥测写入,不扫描、覆盖或删除业务键。对不具备事务能力的
localStorage,该预算是跨标签页并发下的保护上限而非严格原子配额;宿主仍应在跨上下文互斥下回收已稳定迁移的退役 namespace。更强的一致性可由 IndexedDB readwrite 事务适配器提供。
认证生命周期
Token 与用户缓存通常不是一次写入,必须把登录更新包在同一认证事务中:
const result = await telemetry.auth.run(newToken, async () => {
saveToken(newToken);
const user = await loadCurrentUser();
saveUser(user);
return user;
});遥测存储或提交失败不会改变回调的业务结果,只会冻结遥测上传。并发登录使用事务 epoch 和 Token/API 指纹做 CAS,旧响应不能提交新身份。退出登录在业务缓存清理后调用:
telemetry.auth.clear();clear() 写入持久墓碑,防止旧标签页用残留缓存复活已退出身份;它返回 Promise,退出流程可选择 await 或显式 void。
Web 多标签页接入还应在配置中提供 authCoordinator,使用 Web Locks 或 IndexedDB readwrite 事务等真正跨上下文的互斥能力;不能静默退化为标签页内 Promise 队列。SDK 默认协调器只保证当前实例内串行,PDA 这类单 WebView 场景可直接使用默认值。协调器不可用时应让遥测认证安全失败并冻结上传,不能反向改变业务登录结果。
传输结果
适配器需将响应归类为:
SUCCESS:必须返回明确的acceptedEventIds、duplicateEventIds或rejectedEventIds。AUTH、RATE_LIMITED、TRANSIENT:保留队列并退避。PERMANENT_EVENT:二分定位并隔离单个坏事件。PAYLOAD_TOO_LARGE:缩小批次,最终只隔离无法单独发送的事件。PERMANENT_BATCH:整批配置错误,保留全部事件,不能逐条误删。
核心层有独立发送截止时间,即使平台传输 Promise 永不结束也不会永久锁死队列。适配器必须监听 SDK 提供的 signal 并尽量中止底层 fetch/XHR/原生请求,减少悬挂资源。
隐私与数据归属
- 只对认证凭据脱敏:Authorization(含 Basic/Digest/Bearer)、独立 Bearer、API/Secret Key、Token、密码、完整 Cookie Header、Credential 和 JWT 在入队前替换。
- 邮箱、手机号、长数字业务编号、蓝牙地址、仓储业务编号和路由路径中的业务 ID 原样保留;路由查询参数整体丢弃。
- 每个批次和事件都携带
schemaVersion与sdkVersion,服务端可以兼容识别离线积压的旧协议事件。 jobId、diagnosticId、correlationId、lookupKey原值保留,同时为 1–256 位严格 ASCII 关联号生成 128 位v2-指纹,兼容按完整诊断号查询。v2-指纹只用于跨语言协议关联,不能作为授权。服务端落盘前仍使用仅服务端持有的高熵密钥计算版本化 HMAC 索引;原值和索引都只允许通过受限遥测查询接口访问。- 结构化详情字段不做固定数量裁剪,每个 key/value 仍受长度和事件总字节数限制。
- 对象只记录结构摘要,不递归展开。
- 队列按规范化 API 地址和大小写敏感用户 ID 分桶,只上传当前已确认身份的桶。
- 认证切换期间的匿名事件,只有同一 epoch、installation 和事务开始时 session 的事件会在成功提交后归入新用户;恢复意图持久化,因此应用重启产生新 session 后仍可补偿。
- 运行中 namespace 接管必须调用 SDK 实例的
adoptNamespace():它会先持久化目标内存缓存、拒绝与上传并发、按全局容量校验合并,再刷新目标缓存;不能只在宿主外部改存储。inspectNamespace()可只读比较 generation,避免重复接管未变化的归档队列。 removeTelemetryNamespace()只适用于源 writer 已被持久围栏阻止写入,并且恢复与回收共用跨上下文互斥,或底层存储能原子覆盖“generation 检查到整段删除”的场景。建议在接管完成后保留稳定宽限期,删除前再次校验 generation、围栏和存活租约;存在非空死信或无法确认完整迁移时继续保留。浏览器localStorage不具备多键事务,因此共享预算仍是必要的最后一道容量保护。
验证
npm run verify该命令依次运行行为测试、严格类型检查和构建,再生成唯一 tarball、复核 SHA-512,并在无本地链接或源码依赖的临时目录完成安装与导入冒烟。
发布
版本一经发布不得覆盖。发布账号或 CI 需要公司私有 registry 的 @kengic scope 写权限:
cd frontend/vue3
pnpm run verify
pnpm run version:bump -- --dry-run
pnpm run version:bump仓库内置的 GitLab CI 不绑定固定域名或项目 ID:普通流水线运行 npm run verify,只有受保护的
纯版本标签(例如 2026.3.0)且标签版本与 Maven、package.json、pack metadata 完全一致时,才使用当前项目的 CI_JOB_TOKEN 同时发布后端和前端包到
当前项目 npm registry。首次接入时需要:
- 为本仓库创建 GitLab project 并配置
origin; - 在项目设置中把版本标签规则配置成 Protected tags,只允许发布负责人创建;
- 运行
pnpm run version:bump -- --dry-run预览按年、季度、修订号计算的新版本; - 运行
pnpm run version:bump,由命令更新 Maven 和前端版本、提交、创建同名标签并原子推送; - 发布成功后从消费项目需要使用的 project/group/instance endpoint 做一次无缓存安装冒烟。
流水线通过 CI_API_V4_URL 兼容 GitLab 自定义端口和子路径,只把认证写入 mktemp 创建的临时 npmrc,并在 job 结束时删除;仓库同时忽略 .npmrc 和 npm 缓存目录。verify 阶段把 dist、tarball 与 pack metadata 作为 artifact 交给 publish,发布的是已经验证和洁净安装过的同一 tarball,不会二次打包。
不要将 CI_JOB_TOKEN、个人 Token 或生成后的 npmrc 作为 artifact。
默认流水线使用公司阿里云镜像
registry.cn-shanghai.aliyuncs.com/kengic_k8s/node:24.14.1-slim,不依赖 Docker Hub,并在安装前校验
Node 24.14.1 和 npm 11.11.0。如需覆盖项目变量 CLIENT_TELEMETRY_NODE_IMAGE,也必须指向公司网络可访问的同版本镜像;版本校验会阻止镜像标签被错误复用或静默降级。
年.季度.修订号 是稳定 SemVer,发布到 npm latest。低层工具仍兼容的 alpha.N、beta.N、rc.N 分别发布到 alpha、beta、rc dist-tag,但不作为统一发布命令的正常版本格式。
消费端和 Docker 构建也必须把 @kengic:registry 指向同一私服,并通过 CI secret 注入只读 Token;不要把 Token 写入仓库或镜像层。消费仓库使用 BuildKit 时,将完整的只读配置作为临时文件挂载:
docker build --secret id=npmrc,src=/run/secrets/kengic-npmrc ...Dockerfile 的安装步骤使用 --mount=type=secret,id=npmrc,target=/root/.npmrc,required=true,不能改用包含 Token 的 ARG 或 ENV。发布后应在一个没有本地链接和 pnpm store 缓存的临时目录安装精确版本,再运行 import/build 冒烟验证。
发布物是供 Vite/Webpack 等构建器消费的 ES Module,编译目标为 ES2015;宿主需提供 Promise、Map、Set,或在自己的兼容构建中加入相应 polyfill。第一版不作为可直接插入旧浏览器的独立 <script> 分发。
组件知识库
核验基线:dbade1cf603e4b392044141b770ffeffd31fcce4;日期:2026-10-12。无平台全局依赖的客户端日志 SDK,宿主提供存储、身份、上下文和传输。
修改前按本目录 AGENTS.md 读取相关主题。历史只覆盖记录的 Git 基线;正文依据当前工作区,未提交代码/测试不能当成已发布行为。
