npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cxcxcx/client-logger

v1.3.6

Published

前端客户端日志 SDK:全局错误监控、UI 库自动识别、行为埋点、IndexedDB 即时持久化、定时批量上报;支持 Vue 2/3、React、Electron

Readme

@cxcxcx/client-logger

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

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

当前版本:1.3.6


目录


功能特性

| 能力 | 说明 | |------|------| | 全局错误捕获 | window.error(含静态资源)、unhandledrejection、Vue errorHandler + warnHandlerconsole.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-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.6vue-router@^3axios@^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. 验证是否生效

  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@^3vue-router@^4axios@^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.$loggersetupVue3ErrorHandler 已挂载到 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_viewui_clickerror 记录。


子模块说明

本包通过 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 起uploadPathfileUploadPath 为必填项,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(按 getFlushIntervalflushInterval 动态取值)
  • 同步 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_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 集成

主进程(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 在 inituploadDiagnosticLogs 时会通过 getAuthToken 自动同步 authToken 到主进程;渲染进程桥接中建议在上传前刷新 token,确保 Electron 本地 .log 文件上传携带 Authorization 头。

渲染进程每条日志会:

  1. 立即写入 IndexedDB → 按 flushInterval 定时批量上报服务端
  2. 通过 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,首次点击时根据以下条件探测页面使用的组件库:

  1. 全局变量(如 window.ELEMENTwindow.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 === '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']
}

脱敏字段扩展

默认脱敏:tokenpasswordauthorizationphoneidcardpayloadeegdata 等。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            # 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 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 → 对应 dbNamepending_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 中传入 uploadPathfileUploadPathSDK 包内不再写死默认路径。Electron 主进程还需在 registerLoggerIpcHandlersuploadPendingLogFiles 中传入相同的 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