前端埋点sdk,开发者利用该sdk可以去搜集设备信息、错误日志和性能数据(开发中)等。
- Chrome >=87
- Firefox >=78
- Safari >=14
- Edge >=88
搜集信息如下
| os | 操作系统( 举例:Windows/Mac/Android/iOS/Linux ) |
| osVersion | 操作系统版本,(举例:11 | 10 | Unknown ...) |
| browserName | 浏览器( 举例:Chrome | Firefox | Edge ... ) |
| browserVersion | 浏览器版本( 举例:Chrome: 104.0.0.0 ... ) |
| screenSizeWidth | 屏幕宽度( 举例:2560 )px |
| screenSizeHeight | 屏幕高度( 举例:1440 )px |
| windowSizeWidth | 窗口宽度( 举例:2495 )px |
| windowSizeHeight | 窗口高度 ( 举例:1440 )px |
| language | 使用语言( 举例:zh-CN ...) |
| isWeChart | 是否在微信环境(true | false) |
| isMobile | 是否在移动端(true | false) |
| userAgent | 浏览器的navigator.userAgent |
| platform | 浏览器的navigator.platform |
| gpu | gpu信息(ANGLE (NVIDIA Corporation, GeForce GTX 1660 Ti/PCIe/SSE2, OpenGL 4.5.0)) |
为了方便确定发生错误的环境,错误信息会包括设备信息,
在此基础上会增加错误相关字段,新增字段见下表
| errorName | 错误名称( 举例:TypeError, ParseError) |
| errorMessage | 错误信息 |
| errorLevel | 错误级别:0-3,一般来说数字越小越严重 |
| errorStack | 错误栈,依赖浏览器兼容性 |
| errorComponentStack | 组件级别错误栈,依赖前端框架实现,已知react支持 |
todo
npm install @easycode/client-detector通过script标签引入cdn上的umd包
copy以下代码放在script标签中,如下示例
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Client detector demo</title>
<!-- 引入client detector,暴露global对象: ClientDetector -->
<script src="/src/build/client-detector.umd.cjs"></script>
<!-- 初始化 client detector -->
<script>
(function(){
const serviceHost = 'https://demo.com/data-bury'; // 必填,服务请求地址
const serviceName = 'test-service'; // 必填且唯一,找管理员查询
ClientDetector.init(serviceHost, { serviceName: serviceName});
})();
</script>
...import { createClientDetector } from '@easycode/client-detector';
const serviceHost = 'https://demo.com/data-bury'; // 必填,服务请求地址
const serviceName = 'test-service'; // 必填且唯一,找管理员查询
const userId = 'visitor'; // 可选,用户id,默认是visitor
const buryId = ''; // 可选,32位uuid,前端生成,不填则由后端生成
const clientDetector = createClientDetector(serviceHost,{
serviceName,
userId,
buryId
});
const Demo: FC<NavigatorDemoProps> = () => {
useEffect(() => {
// 发送客户端设备信息
clientDetector.sendClientInfo();
}, []);
return (
<div className={ styles.navigatorDemo }>
Demo
</div>
);
};在具体业务场景中,我们发现userId并不一定能在初始化ClientDetector阶段获取,
所以提供setUserId方法,开发者可以通过该方法设置userId,设置后发送的所有埋点请求都会带上userId,
注意! 在调用setUserId前的请求不会带有userid,使用默认值visitor
import { createClientDetector } from '@easycode/client-detector';
const serviceHost = 'https://demo.com/data-bury'; // 必填,服务请求地址
const serviceName = 'test-service'; // 必填且唯一,找管理员查询
const userId = 'visitor'; // 用户id,可选
const buryId = ''; // 可选
const clientDetector = createClientDetector(serviceHost,{
serviceName,
userId,
buryId
});
const Demo: FC<NavigatorDemoProps> = () => {
useEffect(() => {
// userId为visitor
clientDetector.sendClientInfo();
setTimeout(() => {
// 设置userId
clientDetector.setUserId('0000000');
// 获取客户端设备信息
clientDetector.sendClientInfo();
}, 1000);
}, []);
return (
<div className={ styles.navigatorDemo }>
Demo
</div>
);
};version >= 1.1.0
网络指纹指通过客户端信息分辨用户的技术,当app没有用户功能去区分使用者时,可以使用网络指纹,它会根据客户端信息生成一个hash值,帮助后台系统做区分。
import { createClientDetector } from '@easycode/client-detector';
const serviceHost = 'https://demo.com/data-bury'; // 必填,服务请求地址
const serviceName = 'test-service'; // 必填且唯一,找管理员查询
const clientDetector = createClientDetector(serviceHost,{
serviceName
});
const Demo: FC<NavigatorDemoProps> = () => {
useEffect(() => {
// 发送客户端设备信息
// setFingerprint是一个异步方法,会在localstorage中缓存生成的网略指纹
detector.setFingerprint().then(() => detector.sendClientInfo());
}, []);
return (
<div className={ styles.navigatorDemo }>
Demo
</div>
);
};version >= 1.1.0
import { getFingerprint } from '@easycode/client-detector';
const Demo: FC<NavigatorDemoProps> = () => {
useEffect(() => {
// getFingerprint是一个异步函数
const print = async () => {
const id = await getFingerprint();
console.log(id); // 输出一串hash: 801baad441a144716cb8e0a6181ca337
}
print();
}, []);
return (
<div className={ styles.navigatorDemo }>
Demo
</div>
);
};version >= 1.2.0
案例目录结构my-app
...
├── detector.tsx
├── error-boundary.tsx
├── error-demo.tsx
├── app.tsx
...
detector.tsximport { createClientDetector } from '@easycode/client-detector';
const serviceHost = 'https://host/burry/api'; // 服务请求地址
const serviceName = 'test-service'; // 必须唯一,找管理员查询
const userId = 'visitor'; // 用户id,可选
const buryId = ''; // 可选
export const clientDetector = createClientDetector(serviceHost,{
serviceName,
userId,
buryId
});error-boundary.tsx 捕获错误,通过client-detector发送错误import { ReactNode, Component, ErrorInfo } from 'react';
import { clientDetector } from './detector';
export interface ErrorBoundaryProps {
children?: ReactNode;
}
export interface ErrorBoundaryState {
hasError: boolean;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error) {
console.log(error);
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// 发送错误信息
// errorInfo是以组件为单位的调用栈
clientDetector.sendError0(error, errorInfo.componentStack || '');
// 等同于 clientDetector.sendError(error, errorInfo.componentStack || '', 0);
}
render() {
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
export default ErrorBoundary;
error-demo.tsximport { FC, ReactNode, useEffect } from 'react';
export interface ErrorDemoProps {
children?: ReactNode;
}
const ErrorDemo: FC<ErrorDemoProps> = () => {
useEffect(() => {
throw new TypeError('error message');
}, []);
return (
<div>
ErrorDemo
</div>
);
};
export default ErrorDemo;
app.tsx 使用上述组件import { FC, ReactNode, useEffect } from 'react';
import { clientDetector } from './detector';
import ErrorBoundary from './error-boundary';
import ErrorDemo from './error-demo'
export interface AppProps {
children?: ReactNode;
}
const App: FC<AppProps> = () => {
useEffect(() => {
// 发送设备信息
clientDetector.sendClientInfo();
}, []);
return (
<ErrorBoundary>
<div>
请求发送测试页面
</div>
<ErrorDemo />
</ErrorBoundary>
);
};
export default App;version >= 1.3.0
为了简化使用,client-detector提供单例模式,并且继续提供工厂模式(
createClientDetector创建实例)的方式。在入口文件
main.ts中初始化import * as ReactDOM from 'react-dom/client';
import { init } from '@easycode/client-detector';
import App from './app';
const serviceHost = 'https://openxlab.org.cn/gw/data-bury'; // 必填,服务请求地址
const serviceName = 'test-service'; // 必填且唯一,找管理员查询
init(serviceHost, {
serviceName,
});
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<App />,
);import { detector } from '@easycode/client-detector';
import { useEffect } from 'react';
const App = () => {
useEffect(() => {
const init = async () => {
detector.sendClientInfo();
};
init();
}, []);
return (
<div>...</div>
);
};
export default App;从 1.4.0 版本开始,SDK 提供了 console 日志搜集功能。在生产环境下,会自动拦截并搜集 console.info 和 console.error 的日志信息,并批量发送到服务器。
- 自动搜集 - 在初始化时自动启用(可选关闭)
- 批量处理 - 日志会被缓存并批量发送,减少请求次数
- 智能队列 - 使用 requestIdleCallback 在浏览器空闲时发送,不影响主线程
- 设备信息 - 每批日志会自动附带设备信息,方便追踪问题
import { init } from '@easycode/client-detector';
// 默认开启 console 功能
init(serviceHost, {
serviceName: 'your-service'
});
// 禁用 console 功能
init(serviceHost, {
serviceName: 'your-service'
}, 'production', false);发送到服务器的日志格式如下:
{
// 日志信息
consoleLogs: [{
level: 'info' | 'error', // 日志级别
message: string, // 日志内容
timestamp: number // 时间戳
}],
// 设备信息(自动附加)
os: string, // 操作系统
browserName: string, // 浏览器名称
browserVersion: string, // 浏览器版本
// ... 其他设备信息
}// 记录普通信息
console.info('用户点击了按钮', {
buttonId: 'submit',
timestamp: Date.now()
});
// 记录错误信息
console.error('API 请求失败', new Error('Network Error'), {
url: '/api/data',
status: 500
});
// 支持多种数据类型
console.info(
'复杂数据',
{ obj: { id: 1 } },
[1, 2, 3],
null,
undefined
);- 只在生产环境(production)下生效
- 开发环境下使用原始的 console 方法
- 日志会在以下情况触发发送:
- 累积到 10 条消息
- 距离上次发送超过 5 秒
- 浏览器空闲时
- 所有原始日志仍然会在控制台显示
- 如果发送失败,日志会重新加入队列等待下次发送
# 开发
npm run dev