@cxcxcx/client-logger
v1.3.6
Published
前端客户端日志 SDK:全局错误监控、UI 库自动识别、行为埋点、IndexedDB 即时持久化、定时批量上报;支持 Vue 2/3、React、Electron
Maintainers
Readme
@cxcxcx/client-logger
前端客户端日志 SDK,适用于 Vue 2 / Vue 3 / React + axios 的 Web / Electron 项目。
提供全局错误监控、UI 组件库自动识别、行为埋点、IndexedDB 即时持久化、定时批量上报,以及 Electron 本地日志文件写入与上传能力。
当前版本:1.3.6
目录
- 功能特性
- 安装
- Vue 2 项目接入指南
- Vue 3 项目接入指南
- 子模块说明
- createClientLogger 配置项
- Logger 实例 API
- Vue 2 适配器
- Vue 3 适配器
- React 适配器
- axios 适配器
- vue-router 适配器
- Electron 集成
- 日志结构与字段说明
- 上报接口约定
- 自定义可读化文案
- 按环境开启/关闭
- 环境变量配置(可选)
- 构建注意事项
- 完整接入示例(fe-adhd)
- 常见问题
功能特性
| 能力 | 说明 |
|------|------|
| 全局错误捕获 | window.error(含静态资源)、unhandledrejection、Vue errorHandler + warnHandler、console.error、Worker、CSP、存储配额 |
| API 错误记录 | axios 响应/业务错误适配 |
| 行为埋点 | 页面访问(page_view)、全局点击(ui_click)、自定义 track |
| UI 库自动识别 | 运行时探测 Element UI / Ant Design Vue / Naive UI / Vant 等,动态生成交互选择器 |
| 可读化文案 | 动态识别:路由 meta.title、DOM 文本/aria、可选 data-track-label |
| 脱敏 | token、password 等敏感字段自动处理;clientUserId 默认保留明文,可通过 maskClientUserId: true 开启脱敏 |
| 本地持久化 | 每条日志立即写入 IndexedDB,DevTools 可即时查看;上报成功后才删除 |
| 定时上报 | 按 flushInterval 定时从 IndexedDB 批量上报,支持 getFlushInterval 动态读取间隔 |
| Electron | 主进程写本地 .log 文件,退出时可上传;上传自动携带 Authorization 头 |
更新日志
| 版本 | 说明 |
|------|------|
| 1.3.6 | clientUserId 默认改为不脱敏;maskClientUserId: true 时可手动开启脱敏 |
| 1.3.5 | Electron 日志文件上传支持 Authorization 头(authToken 配置);uploadDiagnosticLogs 上传前自动刷新 token;新增 maskClientUserId 配置 |
| 1.3.4 | 日志写入即落 IndexedDB(移除内存队列);默认上报间隔改为 60s;新增 getFlushInterval / setFlushInterval / refreshFlushInterval;移除 batchSize 满批触发与 error/hidden 提前 flush |
| 1.3.3 | 日志写入即落 IndexedDB(移除内存队列);默认上报间隔改为 60s |
| 1.3.2 | Breaking:移除包内写死的上报路径默认值;uploadPath / fileUploadPath 改由接入项目在 createClientLogger / createUploader / Electron 主进程中自行配置 |
| 1.3.1 | 文档更新;fe-adhd 改为从 npm 安装 |
| 1.3.0 | UI 组件库自动识别(Element UI / Ant Design Vue / Naive UI / Vant 等);扩展全局错误(console / worker / storage / csp) |
| 1.2.2 | 静态资源加载失败(capture error)、Vue Router 导航错误(router.onError) |
| 1.2.x | 基础全局错误、axios 适配、vue-router 页面/点击追踪、Electron 桥接 |
安装
npm install @cxcxcx/client-loggerPeer Dependencies(按需安装)
| 包 | 用途 | 是否必须 |
|----|------|----------|
| vue@^2.6 | Vue 2 错误处理器 | 使用 ./vue2 时需要 |
| vue@^3.0 | Vue 3 错误处理器 | 使用 ./vue3 时需要 |
| react@^16.8 | React Error Boundary | 使用 ./react 时需要 |
| axios@^1.0 | HTTP 上报 | 需要批量上报时需要 |
| dayjs@^1.0 | 时间格式化 | 已作为 dependencies 内置 |
Vue 2 项目接入指南
适用于 Vue 2.6+ + vue-router 3 + axios 的 Web / Electron 项目。
1. 安装
npm install @cxcxcx/client-logger项目需已具备
vue@^2.6、vue-router@^3、axios@^1.0(一般 Vue 2 项目本身就有,无需重复安装)。
2. 新建 Logger 配置
创建 src/utils/logger.js:
/**
* 客户端 Logger 配置
*/
import axios from 'axios'
import { createClientLogger } from '@cxcxcx/client-logger'
import { setupVue2ErrorHandler } from '@cxcxcx/client-logger/vue2'
import { setupRouterLogger, setupClickTrack } from '@cxcxcx/client-logger/vue-router'
const API_BASE = process.env.VUE_APP_API_BASE || 'https://api.example.com'
export const logger = createClientLogger({
appVersion: process.env.VUE_APP_VERSION || '1.0.0',
runEnv: process.env.VUE_APP_RUN_ENV || process.env.NODE_ENV || '',
platform: 'web',
enabled: true,
level: 'info',
flushInterval: 60000,
getFlushInterval: () => Number(process.env.VUE_APP_LOG_FLUSH_INTERVAL) || 60000,
uploadBaseURL: API_BASE,
uploadPath: '/clientLog/batch', // 必填:批量上报路径,由项目自行定义
fileUploadPath: '/clientLog/file/upload', // 必填:文件上传路径(Electron 主进程也会用到)
dbName: 'my_project_client_log',
getAuthToken: () => localStorage.getItem('token') || '',
getClientUserId: () => '',
createHttpClient: () => axios.create({ baseURL: API_BASE, timeout: 20000 })
})
export function setupAppLogger(Vue, router) {
logger.init({ uploadBaseURL: API_BASE })
setupVue2ErrorHandler(logger, Vue)
setupRouterLogger(router, logger)
setupClickTrack(router, logger, {
skipTarget(target) {
return Boolean(target?.closest?.('input[type="password"]'))
}
})
}3. 在入口初始化
src/main.js:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import { setupAppLogger, logger } from '@/utils/logger'
setupAppLogger(Vue, router)
Vue.prototype.$logger = logger
new Vue({
router,
render: (h) => h(App)
}).$mount('#app')4. 路由配置 meta.title(页面名自动识别)
// src/router/index.js
const routes = [
{
path: '/home',
name: 'home',
meta: { title: '首页' },
component: () => import('@/views/home/index.vue')
},
{
path: '/user/:id',
name: 'userDetail',
meta: { title: '用户详情' },
component: () => import('@/views/user/detail.vue')
}
]路由切换后自动记录 page_view,例如:从「首页」进入「用户详情」。
5. axios API 错误监控(可选)
// src/api/request.js
import axios from 'axios'
import { createAxiosLoggerInterceptor } from '@cxcxcx/client-logger/axios'
import { logger } from '@/utils/logger'
const service = axios.create({ baseURL: 'https://api.example.com', timeout: 30000 })
const { onResponseError, onBusinessError } = createAxiosLoggerInterceptor(logger)
service.interceptors.response.use(
(response) => {
onBusinessError(response)
return response
},
onResponseError
)
export default service跳过某次请求的日志:axios.get('/api/heartbeat', { skipLog: true })
6. Vue CLI 构建配置
vue.config.js:
module.exports = {
transpileDependencies: ['@cxcxcx/client-logger']
}7. 组件内手动埋点
// Options API
this.$logger.track('form_submit', { formId: 'login' })
// 或直接 import
import { logger } from '@/utils/logger'
logger.track('order_created', { orderId: '123' })
logger.error('api', { url: '/pay', message: '支付失败' })8. 验证是否生效
- 启动项目,切换几个页面
- 打开 DevTools → Application → IndexedDB →
my_project_client_log - 应能立即看到
page_view(页面访问)、ui_click(点击)等记录(刷新 Object Store 视图) - 故意触发 Vue 报错,应能看到
level: error的记录 - 上报成功后对应记录会从 IndexedDB 删除;失败则保留,等待下次定时上报
Vue 3 项目接入指南
适用于 Vue 3 + vue-router 4 + axios 的 Web / Electron 项目(Vite / Vue CLI 均可)。
1. 安装
npm install @cxcxcx/client-logger项目需已具备
vue@^3、vue-router@^4、axios@^1.0(一般 Vue 3 项目本身就有,无需重复安装)。
2. 新建 Logger 配置
创建 src/utils/logger.js:
/**
* 客户端 Logger 配置
*/
import axios from 'axios'
import { createClientLogger } from '@cxcxcx/client-logger'
import { setupVue3ErrorHandler, provideLogger } from '@cxcxcx/client-logger/vue3'
import { setupRouterLogger, setupClickTrack } from '@cxcxcx/client-logger/vue-router'
const API_BASE = import.meta.env.VITE_API_BASE || process.env.VUE_APP_API_BASE || 'https://api.example.com'
export const logger = createClientLogger({
appVersion: import.meta.env.VITE_APP_VERSION || '1.0.0',
runEnv: import.meta.env.MODE || process.env.NODE_ENV || '',
platform: 'web',
enabled: (import.meta.env.VITE_LOG_ENABLED ?? 'true') !== 'false',
level: import.meta.env.VITE_LOG_LEVEL || 'info',
flushInterval: Number(import.meta.env.VITE_LOG_FLUSH_INTERVAL || 60000),
getFlushInterval: () => Number(import.meta.env.VITE_LOG_FLUSH_INTERVAL || 60000),
uploadBaseURL: API_BASE,
uploadPath: '/clientLog/batch',
fileUploadPath: '/clientLog/file/upload',
dbName: 'my_project_client_log',
getAuthToken: () => localStorage.getItem('token') || '',
getClientUserId: () => '',
createHttpClient: () => axios.create({ baseURL: API_BASE, timeout: 20000 })
})
export function setupAppLogger(app, router) {
logger.init({ uploadBaseURL: API_BASE })
setupVue3ErrorHandler(logger, app)
provideLogger(app, logger, 'logger')
setupRouterLogger(router, logger)
setupClickTrack(router, logger, {
skipTarget(target) {
return Boolean(target?.closest?.('input[type="password"]'))
}
})
}Vite 项目用
import.meta.env.VITE_*;Vue CLI 项目用process.env.VUE_APP_*,按实际构建工具选用即可。
3. 在入口初始化
src/main.js:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { setupAppLogger } from '@/utils/logger'
const app = createApp(App)
setupAppLogger(app, router)
app.use(router)
app.mount('#app')4. 组合式 API 中使用
<script setup>
import { inject } from 'vue'
const logger = inject('logger')
function handleSubmit() {
logger.track('form_submit', { formId: 'register' })
}
</script>Options API 仍可使用 this.$logger(setupVue3ErrorHandler 已挂载到 globalProperties)。
5. 路由、axios、构建配置
与 Vue 2 相同:
- 路由表配置
meta.title→ 自动page_view - 点击按钮自动
ui_click(可见文字 /data-track-label) - axios 使用
@cxcxcx/client-logger/axios拦截器 - Vue CLI 需
transpileDependencies: ['@cxcxcx/client-logger'] - Vite 一般无需额外配置
6. 可选:显式标记难识别的控件
<button data-track-label="导出报表" @click="handleExport">导出</button>7. 验证是否生效
同 Vue 2:IndexedDB 中查看 page_view、ui_click、error 记录。
子模块说明
本包通过 exports 提供多个入口:
| 入口 | 说明 |
|------|------|
| @cxcxcx/client-logger | 核心工厂、detectUiLibraries / UI_LIBRARY_PROFILES 等工具 |
| @cxcxcx/client-logger/vue2 | Vue 2 全局错误处理 |
| @cxcxcx/client-logger/vue3 | Vue 3 全局错误处理 + provide |
| @cxcxcx/client-logger/react | React Error Boundary + 路由/点击追踪 |
| @cxcxcx/client-logger/axios | axios 拦截器工具 |
| @cxcxcx/client-logger/vue-router | vue-router 3/4 页面追踪 + 导航错误 + 全局点击追踪 |
| @cxcxcx/client-logger/electron | Electron 主进程日志模块(CommonJS) |
createClientLogger 配置项
createClientLogger({
// --- 基础信息 ---
appVersion: '1.0.0', // 应用版本号
runEnv: 'production', // 运行环境:development / test / uat / production
platform: 'web', // web | electron
// --- 开关与级别 ---
enabled: true, // 是否启用(也可在 init 时覆盖)
level: 'info', // 最低记录级别:debug / info / track / warn / error
bindGlobalErrors: true, // 是否绑定 window 全局错误(含 capture 阶段资源加载失败,默认 true)
captureConsoleError: true, // 是否收集第三方库 console.error(默认 true)
captureWorkerError: true, // 是否 patch Worker / SharedWorker 错误(默认 true)
captureStorageQuotaError: true,// 是否监听 localStorage / IndexedDB 配额超限(默认 true)
captureCspViolation: true, // 是否监听 CSP 违规(默认 true)
consoleErrorSkipPatterns: [], // 额外忽略的 console.error 正则,如 [/ignore me/i]
// --- 上报策略 ---
flushInterval: 60000, // 自动上报间隔(毫秒),默认 60s
getFlushInterval: () => 60000, // 动态获取上报间隔(优先于 flushInterval,每次重启定时器时重新调用)
uploadBaseURL: '', // 上报 API 根地址(也可在 init 时传入)
uploadPath: '/clientLog/batch', // 【必填】批量上报路径,由接入项目定义
fileUploadPath: '/clientLog/file/upload', // 【必填】文件上传路径,由接入项目定义
// --- 存储 ---
dbName: 'client_logger', // IndexedDB 库名
legacyStorageKey: 'client_log_pending', // 旧版 localStorage 迁移 key
sessionKey: 'client_log_session_id', // sessionStorage 会话 ID key
deviceKey: 'client_log_device_id', // localStorage 设备 ID key
// --- 鉴权与用户 ---
getAuthToken: () => '', // 返回 Authorization 头内容(如 Bearer xxx 或 token 字符串)
getClientUserId: () => '', // 返回当前业务用户 ID(默认不脱敏)
maskClientUserId: false, // 是否脱敏 clientUserId(默认 false;userId 始终脱敏)
// --- HTTP 客户端 ---
createHttpClient: () => axiosInstance, // 必须提供,用于 POST 上报
// --- Electron 桥接(可选)---
electronBridge: {
writeClientLog: (entry) => Promise,
setLogUploadConfig: (config) => Promise,
uploadLogFiles: () => Promise
},
// --- 可读化配置(可选)---
routeTitleMap: { '/home': '首页' },
pathPrefixTitles: [{ prefix: '/train', title: '训练中心' }],
classHintMap: { 'submit-btn': '提交按钮' },
elementTypeMap: { span: '文本' },
autoDetectUiLibraries: true, // 是否自动识别 UI 组件库(默认 true)
uiLibraries: ['element-ui'], // 手动指定库 id,设 [] 则仅用原生 HTML/ARIA 选择器
trackMessages: { custom_action: '自定义动作' },
logTypeMessages: { api: '接口错误' },
behaviorLogBuilder: (action, ctx, helpers) => ({ msg, ctx }), // 自定义行为文案
sensitiveKeys: ['customSecret'], // 额外脱敏字段
fieldRemarks: { root: {}, ctx: {} } // 上报 meta 字段备注扩展
})1.3.2 起:
uploadPath与fileUploadPath为必填项,SDK 不再提供默认路径。若单独使用createUploader,同样需要传入这两项:
import { createUploader } from '@cxcxcx/client-logger'
const uploader = createUploader({
uploadPath: '/clientLog/batch',
fileUploadPath: '/clientLog/file/upload',
createHttpClient: () => axios.create({ baseURL: API_BASE }),
getAuthToken: () => token
})日志级别优先级
debug(0) < info(1) < track(2) < warn(3) < error(4)设置 level: 'track' 时,会记录 track、warn、error,但不记录 debug、info。
存储与上报流程(1.3.4+)
产生日志 → 立即写入 IndexedDB(pending_logs)
↓
定时 flush(默认 60s,可动态配置)
↓
批量 POST 到服务端
↓
成功:按 id 删除已上报记录(防重复)
失败:保留记录,下次定时重试- 不使用内存队列:日志产生后立即可在 DevTools → IndexedDB 中查看
- 防重复上报:仅上报成功后才删除 IndexedDB 记录;
flushing锁防止并发重复提交 - 触发时机:定时器、
init启动时重试历史记录、手动logger.flush()/uploadDiagnosticLogs()
Logger 实例 API
logger.init(options?)
初始化 Logger,必须在应用启动时调用一次。
logger.init({
enabled: true,
level: 'info',
flushInterval: 60000,
uploadBaseURL: 'https://api.example.com'
})初始化时会:
- 打开 IndexedDB 并迁移旧 localStorage 数据
- 尝试上报历史 pending 日志(启动时重试上次失败记录)
- 绑定全局错误(若未禁用)
- 启动定时 flush(按
getFlushInterval或flushInterval动态取值) - 同步 Electron 上传配置
logger.debug(type, message, ctx?)
调试日志。
logger.debug('custom', '调试信息', { detail: 'xxx' })logger.info(type, message, ctx?)
普通信息日志。
logger.info('logger', '客户端 Logger 已初始化', { flushInterval: logger.getFlushInterval() })logger.track(action, ctx?)
行为埋点,level 固定为 track,type 固定为 behavior。
logger.track('user_selected', { clientUserId: '12345' })
logger.track('mqtt_connect')
logger.track('page_view', { msg: '访问页面:首页', fromPath: '/', toPath: '/home' })ctx 中可传入 msg 覆盖自动生成的中文描述。
logger.warn(type, message, ctx?)
警告日志。
logger.warn('network', '网络不稳定', { retry: 3 })logger.error(type, ctx?)
错误日志,与其他级别一样立即写入 IndexedDB,按定时器间隔上报。
// 写法 1:type + ctx 对象
logger.error('api', {
url: '/user/info',
method: 'get',
status: 500,
message: '服务器异常'
})
// 写法 2:仅传 ctx 对象
logger.error({
message: '未知错误',
stack: err.stack
})logger.flush(reason?)
手动触发上报,返回 Promise。
const result = await logger.flush('manual')
// { ok: true, count: 10 } 或 { ok: false, error }logger.getFlushInterval()
获取当前生效的上报间隔(毫秒)。若配置了 getFlushInterval 回调,每次调用都会重新读取。
const interval = logger.getFlushInterval()logger.setFlushInterval(interval)
手动设置上报间隔并重启定时器。返回 true 表示设置成功。
logger.setFlushInterval(30000)logger.refreshFlushInterval()
重新调用 getFlushInterval 回调(若有)并重启定时器,适用于运行时修改 localStorage / 远程配置后刷新间隔。
localStorage.setItem('log_flush_interval', '120000')
logger.refreshFlushInterval()logger.uploadDiagnosticLogs()
诊断场景:先 flush IndexedDB 中的待上报日志,再触发 Electron 本地 log 文件上传(Web 环境仅 flush)。
await logger.uploadDiagnosticLogs()logger.setRoute(routePath)
设置当前路由路径,写入每条日志的 route 字段。
logger.setRoute('/home')vue-router 适配器会在
afterEach中自动调用,一般无需手动设置。
logger.destroy()
停止定时 flush,重置初始化状态(SPA 热重载或测试场景可用)。
辅助方法
| 方法 | 说明 |
|------|------|
| logger.buildPageViewLog(fromRoute, toRoute) | 生成页面访问的可读 msg 和 ctx |
| logger.buildClickLog(target, currentRoute) | 生成点击事件的可读 msg 和 ctx |
| logger.getRouteTitle(route) | 根据路由对象获取中文页面名 |
| logger.getFlushInterval() | 获取当前上报间隔(ms) |
| logger.setFlushInterval(ms) | 设置上报间隔并重启定时器 |
| logger.refreshFlushInterval() | 重新读取动态间隔并重启定时器 |
Vue 2 适配器
import { setupVue2ErrorHandler, createVue2Plugin } from '@cxcxcx/client-logger/vue2'
// 方式 1:直接设置 errorHandler(推荐,默认同时捕获 Vue warn)
setupVue2ErrorHandler(logger, Vue)
// 关闭 Vue warn 捕获
setupVue2ErrorHandler(logger, Vue, { captureWarn: false })
// 仅上报 Logger,不在控制台重复输出
setupVue2ErrorHandler(logger, Vue, { printToConsole: false })
// 方式 2:Vue 插件
Vue.use(createVue2Plugin(logger))捕获 Vue 组件内未处理异常,记录为:
{
"level": "error",
"type": "vue",
"msg": "Vue 组件错误",
"ctx": {
"message": "Cannot read property 'x' of undefined",
"stack": "...",
"info": "render",
"route": "/home"
}
}Vue 开发警告(如模板引用未定义属性)默认不设置 warnHandler,由 Vue 原生 console.error 输出(红色),Logger 侧向监听并以 level: error 收集:
{
"level": "error",
"type": "vue",
"msg": "Property or method \"xxx\" is not defined...",
"ctx": {
"message": "Property or method \"xxx\" is not defined...",
"source": "vue-warn",
"trace": "...",
"route": "/assess"
}
}同时挂载 Vue.prototype.$logger = logger。
Vue 3 适配器
import { createApp } from 'vue'
import { setupVue3ErrorHandler, createVue3Plugin, provideLogger } from '@cxcxcx/client-logger/vue3'
import { setupRouterLogger, setupClickTrack } from '@cxcxcx/client-logger/vue-router'
const app = createApp(App)
// 方式 1:直接设置(推荐)
setupVue3ErrorHandler(logger, app)
// 方式 2:插件
app.use(createVue3Plugin(logger))
// 组合式 API:provide/inject
provideLogger(app, logger, 'logger')
logger.init({ uploadBaseURL: 'https://api.example.com' })
setupRouterLogger(router, logger) // vue-router 4 兼容
setupClickTrack(router, logger)
app.mount('#app')组合式 API 中使用:
import { inject } from 'vue'
const logger = inject('logger')
logger.track('button_click', { buttonId: 'submit' })Options API 中仍可使用 this.$logger。
React 适配器
import React from 'react'
import { useLocation } from 'react-router-dom'
import {
createReactErrorBoundary,
createReactRouterTracker,
setupReactClickTrack
} from '@cxcxcx/client-logger/react'
// 1. 错误边界
const LoggerErrorBoundary = createReactErrorBoundary(logger, { React })
// 2. 路由切换追踪
const ReactRouterTracker = createReactRouterTracker(logger, { React })
function AppTracker() {
const location = useLocation()
return <ReactRouterTracker location={location} />
}
// 3. 全局点击追踪
setupReactClickTrack(logger, () => ({ path: window.location.pathname }))
function App() {
return (
<LoggerErrorBoundary fallback={() => <div>页面出错了</div>}>
<BrowserRouter>
<AppTracker />
<Routes>...</Routes>
</BrowserRouter>
</LoggerErrorBoundary>
)
}React 适配器 API
| 方法 | 说明 |
|------|------|
| createReactErrorBoundary(logger, { React, fallback, onError }) | 创建 Error Boundary 组件 |
| createReactRouterTracker(logger, { React }) | 创建路由追踪组件,需传入 useLocation() 的 location |
| setupReactClickTrack(logger, getCurrentRoute, options) | 全局点击追踪,getCurrentRoute 返回 { path } |
React 错误日志结构:
{
"level": "error",
"type": "react",
"msg": "react",
"ctx": {
"message": "Cannot read properties of undefined",
"stack": "...",
"info": " at App (App.jsx:12:5)"
}
}axios 适配器
import { createAxiosLoggerInterceptor } from '@cxcxcx/client-logger/axios'
const { onResponseError, onBusinessError, logApiError } = createAxiosLoggerInterceptor(logger)
// 响应拦截:HTTP 错误
service.interceptors.response.use(
(response) => {
onBusinessError(response) // 业务 code !== 200
return response
},
onResponseError // 网络/HTTP 状态码错误
)跳过某次请求的日志
在 axios config 上设置:
axios.get('/api/heartbeat', { skipLog: true })手动记录 API 错误
logApiError(config, { status: 401, msg: '未授权' })扩展全局错误捕获
logger.init() 且 bindGlobalErrors !== false 时,除运行时错误外还会自动启用:
| type | 触发场景 | 说明 |
|------|----------|------|
| console | 第三方库 console.error | 自动跳过 ResizeObserver loop 等已知噪音;[Vue warn] 归入 vue |
| worker | Worker / SharedWorker 运行错误 | 通过 patch 构造函数自动挂载监听 |
| storage | localStorage / sessionStorage / IndexedDB 配额超限 | IndexedDB 含 Logger 自身缓冲写入失败 |
| csp | Content-Security-Policy 违规 | securitypolicyviolation 事件 |
可按需关闭单项:
createClientLogger({
captureConsoleError: false,
captureWorkerError: false,
captureStorageQuotaError: false,
captureCspViolation: false,
consoleErrorSkipPatterns: [/custom noise/i]
})vue-router 适配器
页面访问追踪
import { setupRouterLogger } from '@cxcxcx/client-logger/vue-router'
setupRouterLogger(router, logger)setupRouterLogger 会同时注册:
router.afterEach:记录page_viewrouter.onError:记录路由导航失败(如 lazy chunk 加载失败、导航守卫 reject)
路由错误示例:
{
"level": "error",
"type": "router",
"msg": "Loading chunk assess failed.",
"ctx": {
"message": "Loading chunk assess failed.",
"route": "/assess",
"routeName": "assess"
}
}每次路由切换后自动记录 page_view:
{
"level": "track",
"type": "behavior",
"msg": "从「首页」进入「评估中心」",
"ctx": {
"action": "page_view",
"fromPage": "首页",
"toPage": "评估中心",
"fromPath": "/home",
"toPath": "/assess"
}
}全局点击追踪
import { setupClickTrack } from '@cxcxcx/client-logger/vue-router'
setupClickTrack(router, logger, {
// 跳过某些路由下的点击记录
skipRoute(route) {
return route.path.startsWith('/game')
},
// 跳过某些 DOM 目标(如密码框、游戏画布)
skipTarget(target) {
return target.closest('input[type="password"]')
}
})自动识别按钮、链接及已探测到的 UI 组件库控件,生成中文描述:
内置支持:Element UI、Ant Design Vue、Naive UI、Vant、Arco Design、View UI、Vuetify、Quasar、TDesign。
fe-adhd 使用 Element UI,启动后会自动探测.el-*控件,无需手动配置。
{
"level": "track",
"type": "behavior",
"msg": "在「首页」点击「开始评估」",
"ctx": {
"action": "ui_click",
"page": "首页",
"pagePath": "/home",
"target": "开始评估",
"targetType": "按钮",
"tag": "button",
"id": "",
"className": "el-button el-button--primary",
"text": "开始评估"
}
}Electron 集成
主进程(main.js)
const {
setupElectronLog,
registerLoggerIpcHandlers,
uploadPendingLogFiles
} = require('@cxcxcx/client-logger/electron')
// 与渲染进程 createClientLogger 中的 fileUploadPath 保持一致
const CLIENT_LOG_FILE_UPLOAD_PATH = '/clientLog/file/upload'
app.whenReady().then(() => {
setupElectronLog()
registerLoggerIpcHandlers({
fileUploadPath: CLIENT_LOG_FILE_UPLOAD_PATH // 必填
})
})
app.on('before-quit', () => {
uploadPendingLogFiles(CLIENT_LOG_FILE_UPLOAD_PATH).catch(() => {})
})本地日志目录:app.getPath('userData')/logs/client-YYYY-MM-DD.log
Preload(preload.js)
contextBridge.exposeInMainWorld('electronAPI', {
isElectron: true,
writeClientLog: (entry) => ipcRenderer.invoke('log:write', entry),
setLogUploadConfig: (config) => ipcRenderer.invoke('log:set-upload-config', config),
getLogDirectory: () => ipcRenderer.invoke('log:get-log-dir'),
uploadLogFiles: () => ipcRenderer.invoke('log:upload-files')
})渲染进程桥接
function createElectronBridge() {
const api = window.electronAPI
if (!api?.isElectron) return null
function withAuthToken(config = {}) {
const token = getToken() // 从 store / localStorage 获取
return token ? { ...config, authToken: token } : config
}
return {
writeClientLog: (entry) => api.writeClientLog(entry),
setLogUploadConfig: (config) => api.setLogUploadConfig(withAuthToken(config)),
uploadLogFiles: async () => {
await api.setLogUploadConfig(withAuthToken({}))
return api.uploadLogFiles()
}
}
}
const logger = createClientLogger({
platform: 'electron',
getAuthToken: () => getToken(),
electronBridge: createElectronBridge(),
// ...其他配置
})1.3.5 起:SDK 在
init与uploadDiagnosticLogs时会通过getAuthToken自动同步authToken到主进程;渲染进程桥接中建议在上传前刷新 token,确保 Electron 本地.log文件上传携带Authorization头。
渲染进程每条日志会:
- 立即写入 IndexedDB → 按
flushInterval定时批量上报服务端 - 通过 IPC 同步写入 Electron 本地 log 文件
日志结构与字段说明
每条日志上报时的数据结构:
{
"ts": "2026-07-10 13:00:00",
"level": "track",
"type": "behavior",
"msg": "在「首页」点击「开始评估」",
"ctx": {
"action": "ui_click",
"page": "首页",
"target": "开始评估"
},
"sessionId": "1720600000000-abc123",
"deviceId": "dev-1720600000000-xyz789",
"appVersion": "1.0.1",
"runEnv": "production",
"route": "/home",
"clientUserId": "1234567890",
"platform": "electron"
}字段说明
| 字段 | 说明 |
|------|------|
| ts | 本地时间,格式 YYYY-MM-DD HH:mm:ss |
| level | debug / info / track / warn / error |
| type | logger / behavior / api / vue / runtime / resource / router / console / worker / storage / csp / unhandledrejection / mqtt 等 |
| msg | 中文可读描述(每条日志都有) |
| ctx | 事件详细上下文 |
| sessionId | 单次打开应用的会话 ID(sessionStorage) |
| deviceId | 设备持久 ID(localStorage) |
| appVersion | 应用版本 |
| runEnv | 运行环境 |
| route | 当前页面路由 |
| clientUserId | 当前用户 ID(默认不脱敏;maskClientUserId: true 时脱敏) |
| platform | web / electron |
字段备注(
fieldRemarks)只在批量上报的meta中携带一次,不会重复写入每条 log。
上报接口约定
路径由接入项目配置:SDK 不再内置默认路径。请在
createClientLogger中传入uploadPath/fileUploadPath,并与后端约定一致。以下以 fe-adhd 项目为例。
1. 批量日志上报
POST {uploadBaseURL}{uploadPath}
// 示例:POST https://api.example.com/adhd/clientLog/batch
Content-Type: application/json
Authorization: {token}请求体:
{
"logs": [
{ "ts": "...", "level": "error", "type": "api", "msg": "...", "ctx": {} }
],
"meta": {
"appVersion": "1.0.1",
"runEnv": "production",
"platform": "electron",
"fieldRemarks": {
"root": { "ts": "日志时间...", "level": "日志级别..." },
"ctx": { "action": "行为动作编码...", "url": "接口地址..." }
}
}
}响应: HTTP 2xx 即视为成功,SDK 会删除已上报的 IndexedDB 记录。
2. 日志文件上传(Electron)
POST {uploadBaseURL}{fileUploadPath}
// 示例:POST https://api.example.com/adhd/clientLog/file/upload
Content-Type: multipart/form-data
Authorization: {token}| 字段 | 说明 |
|------|------|
| file | .log 文件 |
| meta | JSON 字符串,含 appVersion、platform、fieldRemarks 等 |
Authorization由渲染进程通过setLogUploadConfig({ authToken })同步到主进程,SDK 在init/uploadDiagnosticLogs时也会从getAuthToken自动刷新。
动态识别(推荐,跨项目零配置)
无需写死 routeTitleMap / classHintMap,按以下约定即可在其他项目直接使用。
页面名(路由)
在 vue-router 路由表配置 meta.title(本项目已配置):
{
path: '/assess',
name: 'assess',
meta: { title: '评估中心' },
component: ...
}识别优先级:
route.meta.title → matched 记录 meta.title → route.name 可读化 → path按钮/控件名(点击)
自动从 DOM 读取,优先级:
data-track-label → aria-label / title / alt → 可见文本 → class 语义化 → debugHint任意项目可为特殊控件加属性(可选):
<button data-track-label="提交评估">提交</button>
<div class="scale-card" @click="..."> <!-- 有可见中文时会自动识别 -->自动识别规则还包括:原生 button/a/input、自动探测到的 UI 组件库控件、cursor: pointer 区域。
UI 组件库自动识别
默认 autoDetectUiLibraries: true,首次点击时根据以下条件探测页面使用的组件库:
- 全局变量(如
window.ELEMENT、window.antd) - Vue 2 原型(如
$ELEMENT) - DOM 特征选择器(如
.el-button、.ant-btn、.n-button)
探测成功后,会动态生成对应 class 前缀的交互选择器与控件类型(按钮/复选框/标签页等)。
import { detectUiLibraries, UI_LIBRARY_PROFILES } from '@cxcxcx/client-logger'
// 查看当前页面识别结果
logger.getDetectedUiLibraryNames() // ['Element UI']
// 手动指定(关闭自动探测)
createClientLogger({
autoDetectUiLibraries: false,
uiLibraries: ['element-ui', 'ant-design-vue']
})
// 全部内置库 id
UI_LIBRARY_PROFILES.map((item) => item.id)
// element-ui | ant-design-vue | naive-ui | vant | arco-design | view-ui | vuetify | quasar | tdesign点击日志 ctx 中会附带 uiLibraries 字段,记录本次识别到的库名。
最简接入(任意 Vue 2 项目)
setupRouterLogger(router, logger)
setupClickTrack(router, logger)自定义可读化文案(可选覆盖)
仅在自动识别不满足时使用 routeTitleMap / classHintMap 覆盖。
路由中文名
createClientLogger({
routeTitleMap: {
'/login': '登录',
'/home': '首页'
},
pathPrefixTitles: [
{ prefix: '/assess/scale', title: '量表筛查' }
]
})优先级:route.meta.title > matched.meta.title > routeTitleMap > pathPrefixTitles > route.name > path。
控件 class 映射(可选)
createClientLogger({
classHintMap: {
'train-plan-edit': '编辑训练计划',
'el-button--primary': '主按钮'
}
})点击时若元素文本为空,会尝试用 class 映射生成可读名称。
自定义行为埋点文案
function buildBehaviorLog(action, ctx) {
if (action === 'device_connected') {
return {
msg: `设备已连接:${ctx.deviceName}`,
ctx: { action, deviceName: ctx.deviceName }
}
}
return null // 返回 null 则使用默认文案(action 字符串)
}
createClientLogger({ behaviorLogBuilder: buildBehaviorLog })logger.track('device_connected', { deviceName: '脑电帽' })
// msg: "设备已连接:脑电帽"按环境开启/关闭
SDK 本身不绑定环境变量开关,由业务项目在 createClientLogger / setupAppLogger 中自行判断。
仅生产环境开启(fe-adhd 当前策略)
const runEnv = process.env.VUE_APP_RUN_ENV || process.env.NODE_ENV || ''
const isLogEnabled = runEnv === 'production'
export const logger = createClientLogger({
runEnv,
enabled: isLogEnabled,
// ...
})
export function setupAppLogger(Vue, router) {
logger.init({ uploadBaseURL: baseURL, enabled: isLogEnabled })
if (!isLogEnabled) return // 非生产不挂载监听,零开销
setupVue2ErrorHandler(logger, Vue)
setupRouterLogger(router, logger)
setupClickTrack(router, logger)
}多环境白名单
const LOG_ENABLED_ENVS = ['uat', 'production']
const isLogEnabled = LOG_ENABLED_ENVS.includes(runEnv)运行时临时关闭
logger.init({ enabled: false })环境变量配置(可选)
以下变量非 SDK 内置,需项目在 createClientLogger 中自行读取并传入,用于调节级别与上报策略:
| 变量 | 说明 | 默认值 |
|------|------|--------|
| VUE_APP_LOG_LEVEL | 最低级别 | info |
| VUE_APP_LOG_FLUSH_INTERVAL | 上报间隔(ms) | 60000 |
示例(支持运行时动态读取):
function resolveLogFlushInterval() {
const fromEnv = Number(process.env.VUE_APP_LOG_FLUSH_INTERVAL)
if (Number.isFinite(fromEnv) && fromEnv > 0) return fromEnv
return 60000
}
createClientLogger({
level: process.env.VUE_APP_LOG_LEVEL || 'info',
flushInterval: resolveLogFlushInterval(),
getFlushInterval: resolveLogFlushInterval
})构建注意事项
Vue CLI 转译
本包源码为 ES Module,若目标项目使用 Vue CLI 且未转译 node_modules,需在 vue.config.js 中添加:
module.exports = {
transpileDependencies: ['@cxcxcx/client-logger']
}脱敏字段扩展
默认脱敏:token、password、authorization、phone、idcard、payload、eegdata 等。clientUserId 默认不脱敏,userId 始终脱敏。
createClientLogger({
sensitiveKeys: ['customSecretField', 'biometricData'],
maskClientUserId: true // 需要时对 clientUserId 开启脱敏
})完整接入示例(fe-adhd)
本项目(多模态 ADHD 评估与训练系统)在 Vue 2 标准接入基础上,增加了 Electron 与业务埋点扩展。
目录结构
packages/client-logger/src/
├── createLogger.js # Logger 工厂
├── globalErrors.js # Worker / CSP / console.error / 存储配额
├── uiLibraries.js # UI 组件库自动识别
├── readable.js # 页面名、点击描述可读化
├── storage.js # IndexedDB 即时持久化
├── upload.js # 批量上报
├── adapters/
│ ├── vue2.js # Vue 2 错误处理
│ ├── vue3.js # Vue 3 错误处理
│ ├── vueRouter.js # 路由 + 点击追踪
│ ├── axios.js # API 错误
│ └── react.js # React Error Boundary
src/utils/logger/ # fe-adhd 业务接入
├── index.js # createClientLogger + setupAppLogger
└── businessTracks.js # MQTT / 脑电 / 眼动 / 用户切换
src/main.js # setupAppLogger + initBusinessTracks
src/api/request.js # createAxiosLoggerInterceptor
electron/main.js # setupElectronLog + registerLoggerIpcHandlers(传入 fileUploadPath)
electron/preload.js # electronBridge IPC 暴露
vue.config.js # transpileDependenciesindex.js(标准接入 + 项目扩展)
import axios from 'axios'
import { createClientLogger } from '@cxcxcx/client-logger'
import { setupVue2ErrorHandler } from '@cxcxcx/client-logger/vue2'
import { setupRouterLogger, setupClickTrack } from '@cxcxcx/client-logger/vue-router'
import store from '@/store'
import baseURL from '@/api/config'
import APP_VERSION from '@/utils/appVersion'
import { getElectronAPI } from '@/utils/electron'
const runEnv = process.env.VUE_APP_RUN_ENV || process.env.NODE_ENV || ''
/** 客户端日志监控:仅生产环境开启 */
const isLogEnabled = runEnv === 'production'
const CLIENT_LOG_UPLOAD_PATH = '/clientLog/batch'
const CLIENT_LOG_FILE_UPLOAD_PATH = '/clientLog/file/upload'
const DEFAULT_FLUSH_INTERVAL_MS = 60000
function resolveLogFlushInterval() {
const fromEnv = Number(process.env.VUE_APP_LOG_FLUSH_INTERVAL)
if (Number.isFinite(fromEnv) && fromEnv > 0) return fromEnv
try {
const fromStorage = Number(window.localStorage.getItem('adhd_log_flush_interval'))
if (Number.isFinite(fromStorage) && fromStorage > 0) return fromStorage
} catch (error) {
// ignore
}
return DEFAULT_FLUSH_INTERVAL_MS
}
export const logger = createClientLogger({
appVersion: APP_VERSION,
runEnv,
platform: getElectronAPI()?.isElectron ? 'electron' : 'web',
enabled: isLogEnabled,
level: process.env.VUE_APP_LOG_LEVEL || 'info',
flushInterval: resolveLogFlushInterval(),
getFlushInterval: resolveLogFlushInterval,
uploadBaseURL: baseURL,
uploadPath: CLIENT_LOG_UPLOAD_PATH,
fileUploadPath: CLIENT_LOG_FILE_UPLOAD_PATH,
dbName: 'adhd_client_log',
legacyStorageKey: 'adhd_client_log_pending',
getAuthToken: () => store.state.token,
getClientUserId: () =>
store.state.user?.currentUser?.clientUserId || store.state.h5ClientUserId || '',
electronBridge: createElectronBridge(),
createHttpClient: () => axios.create({ baseURL, timeout: 1000 * 20 }),
behaviorLogBuilder: buildAdhdBehaviorLog
})
export function setupAppLogger(Vue, router) {
logger.init({ uploadBaseURL: baseURL, enabled: isLogEnabled })
if (!isLogEnabled) return
setupVue2ErrorHandler(logger, Vue)
setupRouterLogger(router, logger)
setupClickTrack(router, logger, {
skipTarget(target) {
return Boolean(target?.closest?.('input[type="password"]'))
}
})
}businessTracks.js 业务埋点
// Vuex 监听设备状态
store.watch(
(state) => state.device?.eeg?.status,
(next, prev) => {
if (next !== prev) logger.track('eeg_status_change', { from: prev, to: next })
}
)
// MQTT 事件
mqttClient.on('connect', () => logger.track('mqtt_connect'))
mqttClient.on('error', (err) => logger.error('mqtt', { message: err.message }))常见问题
Q: 日志上报失败会丢失吗?
不会。每条日志产生时已写入 IndexedDB;上报失败时记录保留,下次定时 flush(或启动时、手动 flush())会重试。上报成功后按 id 删除,不会重复上报。IndexedDB 最多保留 500 条,超出会删除最旧的记录。
Q: 为什么 DevTools 里看不到日志?
1.3.4 起每条日志立即写入 IndexedDB,打开 DevTools → Application → IndexedDB → 对应 dbName → pending_logs,刷新 Object Store 即可查看。若上报已成功,记录会被删除,因此可能看不到已上报的日志。
Q: 如何运行时修改上报间隔?
// 方式 1:直接设置
logger.setFlushInterval(120000)
// 方式 2:通过 getFlushInterval 数据源(如 localStorage)+ 刷新
localStorage.setItem('adhd_log_flush_interval', '120000')
logger.refreshFlushInterval()Q: 如何临时关闭日志?
logger.init({ enabled: false })生产包默认仅在 runEnv === 'production' 时开启,开发/UAT 构建不会上报。
Q: 如何排除某些 API 不上报?
axios.get('/api/poll', { skipLog: true })Q: 组件内如何使用?
this.$logger.track('button_clicked', { buttonId: 'submit' })
// 或
import { logger } from '@/utils/logger'
logger.track('button_clicked')Q: 支持 Vue 3 或 React 吗?
支持。核心层与框架无关;1.1.0 起提供官方适配器:
- Vue 3:
@cxcxcx/client-logger/vue3 - React:
@cxcxcx/client-logger/react - vue-router 4 页面/点击追踪:继续使用
@cxcxcx/client-logger/vue-router
Q: Web 端能否上传本地文件?
不能。文件上传仅 Electron 主进程支持。Web 端通过 uploadPath 配置的接口批量上报 JSON 日志。
Q: 上报路径在哪里配置?
在接入项目的 createClientLogger 中传入 uploadPath 和 fileUploadPath,SDK 包内不再写死默认路径。Electron 主进程还需在 registerLoggerIpcHandlers 和 uploadPendingLogFiles 中传入相同的 fileUploadPath。
不需要单独封装 clientLog.js API 文件——createClientLogger 内部已集成上报逻辑。
Q: 为什么需要 createHttpClient?
SDK 不强制绑定 axios 实例,便于复用项目已有的 baseURL、拦截器、超时等配置。
导出清单
主入口 @cxcxcx/client-logger 导出:
| 导出 | 说明 |
|------|------|
| createClientLogger | 创建 Logger 实例 |
| createLogStorage | IndexedDB 存储(高级用法) |
| createUploader | 上报模块(高级用法) |
| createSanitizer | 脱敏模块 |
| createReadableHelpers | 可读化模块 |
| createMessageResolver | 文案解析 |
| formatLogTimestamp | 时间格式化 |
| getUploadFieldRemarks | 获取字段备注 |
| stripLogRemarks | 移除 log 上的 _remarks |
| LOG_LEVELS | 级别常量 |
License
MIT
