@cxcxcx/client-logger

前端客户端日志 SDK:全局错误监控、UI 库自动识别、行为埋点、IndexedDB 即时持久化、定时批量上报;内置 Electron/HBuilder/Web 设备 ID 识别;支持 Vue 2/3、React、Electron
Install
npm install @cxcxcx/client-logger
Latest Version:1.4.1
Vue:2 · 3
License:MIT
Last Updated:Jul 17, 2026
Links: npm
Maintainer: cxcxcx

@cxcxcx/client-logger

前端客户端日志 SDK,适用于 Vue 2 / Vue 3 / React + axios 的 Web / Electron 项目。

提供全局错误监控、UI 组件库自动识别、行为埋点、IndexedDB 即时持久化、定时批量上报,以及 Electron 本地日志文件写入(可选,不上报文件)。

当前版本:1.4.1


目录


功能特性

能力 说明
全局错误捕获 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;内置机器码 IPC;上报与 Web 相同走批量接口

更新日志

版本 说明
1.4.1 文档:新增「登录前不监控」接入指南;同步 fe-adhd 登录态门禁与延迟激活示例
1.4.0 内置跨端设备 ID / 平台识别(Electron 机器码、HBuilder plus.uuid、Web localStorage);init 自动 resolveDeviceId;Electron 模块新增 log:get-machine-id
1.3.9 支持自定义 getDeviceId / getPlatform(Electron 机器码、HBuilder UUID 等)
1.3.8 Breaking:移除 Electron 日志文件上传;Web / Electron 统一走批量日志上报;fileUploadPath / uploadLogFiles / setLogUploadConfig 已删除
1.3.7 Breaking:移除上报 meta 收集(含 fieldRemarks);批量上报仅发送 logs,文件上传仅发送 file
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-logger

Peer 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',              // 必填:批量上报路径,由项目自行定义
  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. 验证是否生效

  1. 启动项目,切换几个页面
  2. 打开 DevTools → Application → IndexedDB → my_project_client_log
  3. 应能立即看到 page_view(页面访问)、ui_click(点击)等记录(刷新 Object Store 视图)
  4. 故意触发 Vue 报错,应能看到 level: error 的记录
  5. 上报成功后对应记录会从 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',
  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',              // 【必填】批量上报路径,由接入项目定义

  // --- 存储 ---
  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(默认不脱敏)
  getDeviceId: () => '',           // 自定义设备 ID(可选;不传则自动识别)
  getPlatform: () => 'web',        // 自定义平台(可选;不传则自动识别 electron/app/web)
  autoDetectDevice: true,          // 是否在 init 时自动识别设备 ID(默认 true)
  plusReadyTimeout: 1500,          // HBuilder plusready 等待超时(毫秒)
  maskClientUserId: false,         // 是否脱敏 clientUserId(默认 false;userId 始终脱敏)

  // --- HTTP 客户端 ---
  createHttpClient: () => axiosInstance,  // 必须提供,用于 POST 上报

  // --- Electron 桥接(可选)---
  electronBridge: {
    writeClientLog: (entry) => Promise  // 可选:同步写入主进程本地 .log
  },

  // --- 可读化配置(可选)---
  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']   // 额外脱敏字段
})

1.3.2 起:uploadPath 为必填项,SDK 不再提供默认路径。若单独使用 createUploader,同样需要传入该项:

import { createUploader } from '@cxcxcx/client-logger'

const uploader = createUploader({
  uploadPath: '/clientLog/batch',
  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 中的待上报日志(与 logger.flush('diagnostic') 等价)。

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_view
  • router.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 集成

1.3.8 起:不再上传本地 .log 文件。Electron 与 Web 统一通过渲染进程批量上报;主进程仅可选写入本地文件便于排查。 1.4.0 起:内置 log:get-machine-id,配合 preload 暴露后可自动写入稳定 deviceId。

主进程(main.js)

const {
  setupElectronLog,
  registerLoggerIpcHandlers
} = require('@cxcxcx/client-logger/electron')

app.whenReady().then(() => {
  setupElectronLog()
  registerLoggerIpcHandlers()
})

本地日志目录:app.getPath('userData')/logs/client-YYYY-MM-DD.log

Preload(preload.js)

contextBridge.exposeInMainWorld('electronAPI', {
  isElectron: true,
  writeClientLog: (entry) => ipcRenderer.invoke('log:write', entry),
  getLogDirectory: () => ipcRenderer.invoke('log:get-log-dir'),
  getMachineId: () => ipcRenderer.invoke('log:get-machine-id')
})

渲染进程桥接

function createElectronBridge() {
  const api = window.electronAPI
  if (!api?.isElectron) return null

  return {
    writeClientLog: (entry) => api.writeClientLog(entry)
  }
}

const logger = createClientLogger({
  // platform / deviceId 默认自动识别,一般无需手动传
  getAuthToken: () => getToken(),
  electronBridge: createElectronBridge(),
  // ...其他配置(与 Web 相同,走 uploadPath 批量上报)
})

await logger.init({ uploadBaseURL: API_BASE })

渲染进程每条日志会:

  1. 立即写入 IndexedDB → 按 flushInterval 定时批量上报服务端
  2. (可选)通过 IPC 同步写入 Electron 本地 log 文件,仅作本地排查,不上报

设备 ID 识别优先级:Electron 机器码 → HBuilder plus.device.uuid → localStorage 伪 ID


日志结构与字段说明

每条日志上报时的数据结构:

{
  "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

应用版本、环境、平台等上下文写在每条 log 上,上报请求不再附带顶层 meta。


上报接口约定

路径由接入项目配置:SDK 不再内置默认路径。请在 createClientLogger 中传入 uploadPath,并与后端约定一致。以下以 fe-adhd 项目为例。

批量日志上报

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": {} }
  ]
}

响应: HTTP 2xx 即视为成功,SDK 会删除已上报的 IndexedDB 记录。


动态识别(推荐,跨项目零配置)

无需写死 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,首次点击时根据以下条件探测页面使用的组件库:

  1. 全局变量(如 window.ELEMENT、window.antd)
  2. Vue 2 原型(如 $ELEMENT)
  3. 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 !== 'development'

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 isLogEnabled = runEnv === 'production'

多环境白名单

const LOG_ENABLED_ENVS = ['uat', 'production']
const isLogEnabled = LOG_ENABLED_ENVS.includes(runEnv)

运行时临时关闭

logger.init({ enabled: false })

注意:init 只会执行一次。若首次以 enabled: false 初始化,之后无法再通过 init({ enabled: true }) 打开。需要「先关后开」时,请采用下方 登录前不监控 的延迟激活写法。


登录前不监控

SDK 不内置登录态判断。若业务要求「未登录不采集、不上报;登录后再挂载监控」,由接入项目自行门禁。推荐做法(fe-adhd 已采用):

  1. 延迟 init / 挂载适配器:无 token 时不调用 logger.init({ enabled: true }),也不挂 setupVue2ErrorHandler / setupRouterLogger / setupClickTrack
  2. 写入与上报门禁:对 debug / info / track / warn / error / flush / uploadDiagnosticLogs 包一层,无 token 直接 return
  3. 监听 token:store.watch(或登录成功回调)在拿到 token 后再 activateLogger();session 恢复、H5 换票等同理

核心逻辑示例

import store from '@/store'

export function hasAuthToken() {
  return Boolean(store.state.token)
}

/** 写入 / 上报增加登录态门禁 */
function applyAuthGate(loggerInstance) {
  ;['debug', 'info', 'track', 'warn', 'error'].forEach((name) => {
    const original = loggerInstance[name].bind(loggerInstance)
    loggerInstance[name] = (...args) => {
      if (!hasAuthToken()) return
      return original(...args)
    }
  })

  const originalFlush = loggerInstance.flush.bind(loggerInstance)
  loggerInstance.flush = (reason = 'manual') => {
    if (!hasAuthToken()) {
      return Promise.resolve({ ok: true, skipped: true, reason: 'no_auth' })
    }
    return originalFlush(reason)
  }

  return loggerInstance
}

export const logger = applyAuthGate(
  createClientLogger({
    enabled: isLogEnabled,
    getAuthToken: () => store.state.token,
    // ...
  })
)

let loggerInitialized = false
let adaptersInstalled = false

async function activateLogger(Vue, router) {
  if (!isLogEnabled || !hasAuthToken()) return

  if (!loggerInitialized) {
    await logger.init({ uploadBaseURL: baseURL, enabled: true })
    loggerInitialized = true
  }

  if (adaptersInstalled) return
  setupVue2ErrorHandler(logger, Vue)
  setupRouterLogger(router, logger)
  setupClickTrack(router, logger)
  adaptersInstalled = true
}

export async function setupAppLogger(Vue, router) {
  if (!isLogEnabled) {
    await logger.init({ uploadBaseURL: baseURL, enabled: false })
    return
  }

  // 登录成功 / 退出后再登:靠 token 变化激活
  store.watch(
    (state) => state.token,
    (token) => {
      if (token) activateLogger(Vue, router)
    }
  )

  // 已有 token(session 恢复 / H5 换票)则立即开启
  if (hasAuthToken()) {
    await activateLogger(Vue, router)
  }
}

注意点

点 说明
判定条件 一般用 store.state.token(或项目等价登录凭证)
H5 / 扫码 换票成功写入 token 后应同样开启监控
退出登录 门禁会自动让写入/上报变为空操作;定时 flush 若已启动,无 token 时业务侧 flush 包装会跳过
适配器只挂一次 用 adaptersInstalled 标记,避免重复 afterEach / 点击监听
与环境开关组合 先判断 isLogEnabled(环境),再判断 hasAuthToken()(登录)

环境变量配置(可选)

以下变量非 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
electron/preload.js      # writeClientLog IPC 暴露
vue.config.js            # transpileDependencies

index.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 packageJson from '/package.json'
import { getElectronAPI } from '@/utils/electron'
import { buildAdhdBehaviorLog } from './behaviorLogBuilder'

const APP_VERSION = packageJson.version
const runEnv = process.env.VUE_APP_RUN_ENV || process.env.NODE_ENV || ''

/** 客户端日志监控:除开发环境都开启 */
export const isLogEnabled = runEnv !== 'development'

const CLIENT_LOG_UPLOAD_PATH = '/clientLog/batch'

function resolveLogFlushInterval() {
  const fromEnv = Number(process.env.VUE_APP_LOG_FLUSH_INTERVAL)
  if (Number.isFinite(fromEnv) && fromEnv > 0) return fromEnv
  return 60000
}

export function hasAuthToken() {
  return Boolean(store.state.token)
}

function applyAuthGate(loggerInstance) {
  ;['debug', 'info', 'track', 'warn', 'error'].forEach((name) => {
    const original = loggerInstance[name].bind(loggerInstance)
    loggerInstance[name] = (...args) => {
      if (!hasAuthToken()) return
      return original(...args)
    }
  })
  const originalFlush = loggerInstance.flush.bind(loggerInstance)
  loggerInstance.flush = (reason = 'manual') => {
    if (!hasAuthToken()) {
      return Promise.resolve({ ok: true, skipped: true, reason: 'no_auth' })
    }
    return originalFlush(reason)
  }
  return loggerInstance
}

export const logger = applyAuthGate(
  createClientLogger({
    appVersion: APP_VERSION,
    runEnv,
    enabled: isLogEnabled,
    level: process.env.VUE_APP_LOG_LEVEL || 'info',
    flushInterval: resolveLogFlushInterval(),
    getFlushInterval: resolveLogFlushInterval,
    uploadBaseURL: baseURL,
    uploadPath: CLIENT_LOG_UPLOAD_PATH,
    dbName: 'adhd_client_log',
    legacyStorageKey: 'adhd_client_log_pending',
    deviceKey: 'adhd_log_device_id',
    getAuthToken: () => store.state.token,
    getClientUserId: () =>
      store.state.user?.currentUser?.clientUserId || store.state.h5ClientUserId || '',
    createHttpClient: () => axios.create({ baseURL, timeout: 1000 * 20 }),
    behaviorLogBuilder: buildAdhdBehaviorLog
  })
)

let loggerInitialized = false
let adaptersInstalled = false

async function activateLogger(Vue, router) {
  if (!isLogEnabled || !hasAuthToken()) return
  if (!loggerInitialized) {
    await logger.init({ uploadBaseURL: baseURL, enabled: true })
    loggerInitialized = true
  }
  if (adaptersInstalled) return
  setupVue2ErrorHandler(logger, Vue)
  setupRouterLogger(router, logger)
  setupClickTrack(router, logger, {
    skipTarget(target) {
      return Boolean(target?.closest?.('input[type="password"]'))
    }
  })
  adaptersInstalled = true
}

export async function setupAppLogger(Vue, router) {
  if (!isLogEnabled) {
    await logger.init({ uploadBaseURL: baseURL, enabled: false })
    return
  }
  store.watch(
    (state) => state.token,
    (token) => {
      if (token) activateLogger(Vue, router)
    }
  )
  if (hasAuthToken()) {
    await activateLogger(Vue, router)
  }
}

完整说明见 登录前不监控。

businessTracks.js 业务埋点

// Vuex 监听设备状态(建议同时判断 hasAuthToken)
store.watch(
  (state) => state.device?.eeg?.status,
  (next, prev) => {
    if (!hasAuthToken() || next === prev) return
    logger.track('eeg_status_change', { from: prev, to: next })
  }
)

// MQTT 事件
mqttClient.on('connect', () => {
  if (!hasAuthToken()) return
  logger.track('mqtt_connect')
})
mqttClient.on('error', (err) => {
  if (!hasAuthToken()) return
  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 })

init 只执行一次;若需要「未登录关闭、登录后再开」,请看 登录前不监控,不要依赖二次 init({ enabled: true })。

Q: 如何实现登录前不监控?

SDK 不内置登录判断。在业务层:无 token 时不 init、不挂适配器;并对写入/flush 做 token 门禁;store.watch(token) 在登录后延迟激活。详见 登录前不监控。

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: Electron 和 Web 上报方式一样吗?

一样。统一通过渲染进程 uploadPath 批量上报 JSON 日志。Electron 主进程仅可选写入本地 .log 文件便于排查,不再上传文件。

Q: 上报路径在哪里配置?

在接入项目的 createClientLogger 中传入 uploadPath,SDK 包内不再写死默认路径。

不需要单独封装 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

Related Packages

@quick-start/create-electron

An easy way to start an Electron project

115,010 363
mixone

mixone - universal Electron desktop app scaffolding tool (TS-ready)

3,943 87
vuelogger

logger by js

439 1
@laboverwire/stitch

Reactive state synchronization library bridging an in-memory store, IndexedDB pe...

1,156 2

Version History

Version Released Vue License
1.4.1 ^2.6.0 || ^3.0.0 MIT
1.4.0 ^2.6.0 || ^3.0.0 MIT
1.3.9 ^2.6.0 || ^3.0.0 MIT
1.3.8 ^2.6.0 || ^3.0.0 MIT
1.3.6 ^2.6.0 || ^3.0.0 MIT