sfcom-acquisition
v0.1.4
Published
前端日志采集库,支持捕获网络请求/JS异常/心跳/Vue Router/手动发送
Downloads
75
Maintainers
Readme
sfcom-acquisition
浏览器端前端日志采集库,零运行时依赖。调用一次 init() 即可自动采集环境信息、网络异常、运行时报错、路由跳转与心跳,同时支持手动上报业务日志。
5 分钟上手
npm install sfcom-acquisitionimport { init } from 'sfcom-acquisition'
const acq = init({
url: 'https://log.example.com/collect',
app: 'my-app',
user: getCurrentUserId(),
})
// 手动上报业务日志
acq.send({ event: 'purchase', amount: 99 })
// Vue Router 集成(在 router 实例创建之后调用)
acq.useVueRouter(router)库加载后会自动发送 INIT 日志(附带设备信息);之后网络错误、未捕获异常、路由变化、30 秒一次的心跳均自动上报,无需额外配置。
安装
npm / pnpm / yarn
npm install sfcom-acquisition
# pnpm add sfcom-acquisition
# yarn add sfcom-acquisition| 产物 | 路径 | 格式 |
| :--------- | :------------------------- | :----------------------------- |
| ES Module | dist/acquisition.js | ESM(推荐,支持 Tree-shaking) |
| CommonJS | dist/acquisition.umd.cjs | UMD / require() |
| 浏览器直引 | dist/acquisition.iife.js | IIFE,全局变量 Acquisition |
CDN 直接引入
<script src="https://unpkg.com/sfcom-acquisition/dist/acquisition.iife.js"></script>
<script>
const acq = Acquisition.init({
url: 'https://log.example.com/collect',
app: 'my-site',
user: 'anonymous',
})
</script>初始化
init(options): AcquisitionInstance
const acq = init(options: AcquisitionOptions)AcquisitionOptions
| 字段 | 类型 | 必填 | 说明 |
| :----------- | :--------------- | :--: | :-------------------------------------------------------- |
| url | string | ✓ | 日志接收接口地址,所有日志通过 POST 发送到此地址 |
| app | string | ✓ | 应用标识,会话内不变,会附在每条日志中 |
| user | string | ✓ | 用户标识,会话内不变,会附在每条日志中 |
| sendMethod | SendMethod | | 发送方式,默认 'fetch',详见发送方式 |
| features | FeaturesConfig | | 各采集功能的开关,默认全部开启,详见功能开关 |
实例方法
init() 返回一个 AcquisitionInstance 实例,提供以下方法:
acq.send(data?)
手动发送一条 MANUAL 类型的日志。data 可以是任意可序列化的值。
acq.send({ event: 'click', target: '#submit-btn' })
acq.send('用户完成引导流程')
acq.send() // data 可省略acq.useVueRouter(router)
注册 Vue Router 实例,之后每次路由跳转完成时自动发送 VUE-ROUTER 日志。
- 须在 Vue Router 实例创建完成后调用
- 若初始化时
features.vueRouter为false,此方法为空操作
acq.useVueRouter(router)acq.destroy()
销毁实例,清理所有事件监听器、XHR/Fetch 拦截器和心跳定时器。在 SPA 组件卸载或需要重新初始化时调用。
// Vue 3 示例
onUnmounted(() => {
acq.destroy()
})发送方式
通过 sendMethod 选项设置,所有方式均为 fire-and-forget,不影响主程序。
| 值 | 说明 |
| :--------- | :-------------------------------------------------------------------------- |
| 'fetch' | 使用 Fetch API,开启 keepalive: true(页面卸载时仍可发出)。默认值 |
| 'xhr' | 使用异步 XMLHttpRequest,兼容性最好 |
| 'beacon' | 使用 navigator.sendBeacon,最适合页面卸载场景。发送失败时自动降级为 fetch |
| 'test' | 仅在控制台打印,不发起网络请求。开发调试专用 |
// 开发环境:仅打印到控制台
const acq = init({
url: '...',
app: 'my-app',
user: 'dev',
sendMethod: 'test',
})功能开关
features 选项中所有字段默认为开启(true),可以按需关闭。
| 字段 | 类型 | 默认值 | 说明 |
| :--------------- | :---------------------- | :----: | :----------------------------------------------------------- |
| init | boolean | true | 库加载完成时发送 INIT 日志(附带设备信息) |
| netErr | boolean | true | 拦截 XHR/Fetch 请求失败时发送 NET_ERR 日志 |
| frontException | boolean | true | 捕获未处理的异常和 Promise 拒绝,发送 FRONT_EXCEPTION 日志 |
| beat | boolean \| BeatConfig | true | 定期发送 BEAT 心跳日志,默认间隔 30 秒 |
| vueRouter | boolean | true | 注册 Vue Router 路由变化监听,发送 VUE-ROUTER 日志 |
beat 传入对象时可自定义间隔:
// 关闭心跳
features: {
beat: false
}
// 自定义心跳间隔为 60 秒
features: {
beat: {
interval: 60_000
}
}日志格式
所有发送到后端的日志均为 JSON,通用结构如下:
{
type: string, // 日志类型(见下表)
datetime: number, // 毫秒级时间戳,发送时自动填入
app: string, // 初始化时配置的应用标识
user: string, // 初始化时配置的用户标识
data?: unknown // 各类型特有的详细数据(部分类型无此字段)
}各类型说明
INIT — 库加载完成
在 init() 调用后的首个宏任务中触发,附带当前设备和浏览器信息。
{
"type": "INIT",
"datetime": 1711900000000,
"app": "my-app",
"user": "user-123",
"data": {
"userAgent": "Mozilla/5.0 ...",
"language": "zh-CN",
"languages": ["zh-CN", "zh", "en"],
"platform": "Win32",
"screenWidth": 1920,
"screenHeight": 1080,
"devicePixelRatio": 2,
"timezone": "Asia/Shanghai",
"href": "https://example.com/dashboard",
"referrer": "https://google.com",
"cookieEnabled": true,
"onLine": true
}
}NET_ERR — 网络请求失败
拦截所有 XHR 和 Fetch 请求,在响应状态码为 0(网络层错误)或 ≥ 400 时触发。
{
"type": "NET_ERR",
"datetime": 1711900001000,
"app": "my-app",
"user": "user-123",
"data": {
"method": "POST",
"url": "https://api.example.com/orders",
"status": 500,
"statusText": "Internal Server Error",
"duration": 342,
"requestBody": "{\"itemId\":42}",
"responseText": "{\"error\":\"db connection failed\"}"
}
}
status: 0表示网络层失败(DNS 解析失败、CORS 预检被拒绝、连接中断等)。requestBody仅在请求体为字符串时采集。responseText截断至 1024 字符。
FRONT_EXCEPTION — 未捕获异常
监听 window.error 和 window.unhandledrejection 事件。
{
"type": "FRONT_EXCEPTION",
"datetime": 1711900002000,
"app": "my-app",
"user": "user-123",
"data": {
"type": "error",
"message": "Cannot read properties of undefined (reading 'id')",
"source": "https://example.com/assets/app.js",
"lineno": 42,
"colno": 18,
"stack": "TypeError: Cannot read properties of undefined..."
}
}data.type 有两个值:
"error":同步运行时错误"unhandledrejection":未被.catch()处理的 Promise 拒绝
MANUAL — 手动上报
调用 acq.send(data) 时触发,data 由调用方决定。
{
"type": "MANUAL",
"datetime": 1711900003000,
"app": "my-app",
"user": "user-123",
"data": {
"event": "purchase",
"amount": 99,
"itemId": "SKU-001"
}
}VUE-ROUTER — 路由跳转
调用 acq.useVueRouter(router) 后,每次 router.afterEach 触发时上报。
{
"type": "VUE-ROUTER",
"datetime": 1711900004000,
"app": "my-app",
"user": "user-123",
"data": {
"from": {
"path": "/dashboard",
"fullPath": "/dashboard?tab=overview",
"name": "Dashboard",
"query": { "tab": "overview" },
"params": {},
"hash": ""
},
"to": {
"path": "/orders/42",
"fullPath": "/orders/42",
"name": "OrderDetail",
"query": {},
"params": { "id": "42" },
"hash": ""
}
}
}BEAT — 心跳
按照设定间隔定期发送,无额外 data 字段。
{
"type": "BEAT",
"datetime": 1711900030000,
"app": "my-app",
"user": "user-123"
}场景示例
Vue 3 + Vue Router 4 完整接入
// main.ts
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { init } from 'sfcom-acquisition'
import App from './App.vue'
import routes from './routes'
const router = createRouter({
history: createWebHistory(),
routes,
})
const acq = init({
url: import.meta.env.VITE_LOG_URL,
app: 'my-vue-app',
user: store.getters.userId, // 从状态管理中获取
sendMethod: import.meta.env.PROD ? 'beacon' : 'test',
})
acq.useVueRouter(router)
createApp(App).use(router).mount('#app')关闭部分功能
const acq = init({
url: 'https://log.example.com/collect',
app: 'my-app',
user: 'anonymous',
features: {
beat: false, // 不需要心跳
netErr: false, // 不拦截网络请求
beat: { interval: 60_000 }, // 心跳间隔改为 60 秒
},
})页面卸载前用 beacon 发送
// 在需要可靠上报页面停留时长的场景使用
const acq = init({
url: 'https://log.example.com/collect',
app: 'my-app',
user: userId,
sendMethod: 'beacon',
})
window.addEventListener('beforeunload', () => {
acq.send({ stayDuration: Date.now() - pageEnterTime })
})纯 HTML 页面(CDN)
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<title>My Page</title>
</head>
<body>
<script src="https://unpkg.com/sfcom-acquisition/dist/acquisition.iife.js"></script>
<script>
var acq = Acquisition.init({
url: 'https://log.example.com/collect',
app: 'landing-page',
user: 'visitor',
sendMethod: 'beacon',
features: { beat: false },
})
</script>
</body>
</html>TypeScript 类型参考
// 初始化选项
interface AcquisitionOptions {
url: string
app: string
user: string
sendMethod?: 'fetch' | 'xhr' | 'beacon' | 'test'
features?: FeaturesConfig
}
// 功能开关
interface FeaturesConfig {
init?: boolean
netErr?: boolean
frontException?: boolean
beat?: boolean | BeatConfig
vueRouter?: boolean
}
// 心跳配置
interface BeatConfig {
interval?: number // 毫秒,最小 1000,默认 30000
}
// 实例
interface AcquisitionInstance {
send(data?: unknown): void
useVueRouter(router: RouterLike): void
destroy(): void
}
// 日志类型
type LogType = 'INIT' | 'NET_ERR' | 'FRONT_EXCEPTION' | 'MANUAL' | 'VUE-ROUTER' | 'BEAT'
// 发送的日志结构
interface LogPayload {
type: LogType
datetime: number // 毫秒时间戳
app: string
user: string
data?: unknown
}
// INIT 数据
interface InitData {
userAgent: string
language: string
languages: string[]
platform: string
screenWidth: number
screenHeight: number
devicePixelRatio: number
timezone: string
href: string
referrer: string
cookieEnabled: boolean
onLine: boolean
}
// NET_ERR 数据
interface NetErrData {
method: string
url: string
status: number // 0 表示网络层错误
statusText: string
duration: number // 毫秒
requestBody?: string
responseText?: string
}
// FRONT_EXCEPTION 数据
interface FrontExceptionData {
type: 'error' | 'unhandledrejection'
message: string
source?: string
lineno?: number
colno?: number
stack?: string
}
// VUE-ROUTER 数据
interface VueRouterData {
from: VueRouteInfo
to: VueRouteInfo
}
interface VueRouteInfo {
path: string
fullPath: string
name?: string | number | symbol | null
query: Record<string, unknown>
params: Record<string, unknown>
hash: string
}
// Vue Router 兼容接口(v3 / v4 均可)
interface RouterLike {
afterEach(guard: (to: RouteLocationLike, from: RouteLocationLike) => void): () => void
}注意事项
库绝不抛出异常。 所有内部错误都被静默处理,采集功能的任何故障都不会影响主程序运行。
开发时使用 test 模式。 设置 sendMethod: 'test' 后,日志仅打印到控制台,不发出任何网络请求,方便调试时查看日志内容。
SPA 务必调用 destroy()。 若同一页面需要切换用户或重新初始化,先调用旧实例的 destroy(),再创建新实例。否则旧实例的心跳和监听器不会被清理。
useVueRouter() 须在 router 实例创建后调用。 在调用 createRouter() 之前调用会导致钩子注册失败。
NET_ERR 不采集库自身的请求。 库内部发送日志的请求通过请求头 X-Acquisition-Own 标记,拦截器会自动跳过,不会产生循环上报。
心跳最小间隔为 1 秒。 传入小于 1000 的 interval 值会被自动修正为 1000。
