前端监控系统

201 阅读2分钟

前端监控就是通过页面埋点上报,来获取用户信息、异常信息、交互信息等,并对这些信息分析与计算,来提升网站的用户体验。

监控信息

  • 用户信息
  • 环境信息
  • 事件信息

用户信息可以通过登录来获取。

环境信息包含了终端系统、浏览器信息、网络、语言等。

const devInfo = {
  osType: osType(),
  browserType,
  browserVersion,
  language: window.navigator.language,
  netType: window.navigator.connection?.type,
  evn: browserRouter ? 'prod' : 'dev',
  appName: name,
  appVersion: version,
};

事件信息主要是用户交互、路由跳转、页面异常、请求异常等。

页面埋点

用户交互

用户交互可以自定义埋点组件来监控。

例如,对点击事件进行监控。

const ReportButton = props => {
  const {value, label, text, description, onClick, ...rest} = props;
  const handleClick = e => {
    repot({
      text: text || props.children,
      value,
      label,
      description,
      type: 'click',
    });
    onClick?.(e);
  };
  return <Button {...rest} onClick={handleClick} />;
};

当我们需要埋点时,直接使用 ReportButton 即可。

路由跳转

我们可以使用路由钩子来监控路由跳转,可以得到用户在当前页面停留时间等信息。

const routeReport = () => {
  const uuid = uuidv4();
  report({actionType: 'routeChange', uuid, prevUuid});
  prevUuid = uuid;
};

通过 beforeunload 监听页面关闭或刷新:

window.addEventListener('beforeunload', e => {
  const isLogin = e.target.location.pathname.indexOf('/user/') === 0 || e.target.location.hash.indexOf('#/user/') === 0;
  if (!isLogin) {
    report({actionType: 'close', prevUuid});
  }
}, false);

页面异常

通过 ErrorBoundary 来上报页面异常:

const pageError = ({error}) => {
  const errStack = error?.message?.slice(0, 120).split('. ').slice(0, 2).join('. ');
  report({actionType: 'pageError', text: errStack});
};

const ErrorBoundary = props => <HandleError {...props} report={pageError} />;

export default ErrorBoundary;

请求异常

在我们的 fetch 请求函数抛出异常时上报信息:

const fetch = ({method, url, ...opt}) => fetchApi(method)(`${TARGET}${url}`, {...opt, headers: getToken(), credentials: 'omit'})
  .catch(err => {
    report({actionType: 'fetchError', text: err.message, value: url});
    throw err.message;
  });

信息处理

当我们获取了一些基础信息时,我们就可以通过数据分析、归纳,来完善系统稳定性,找出用户关注点等。

简单实现

创建数据表

const schemas = {
  // user info
  uid: {type: String, required: true},
  userName: {type: String},
  userRole: {type: Number},
  projectId: {type: String},
  projectName: {type: String},
  ip: {type: String, required: true},
  // system info
  osType: {type: String, required: true},
  browserType: {type: String, required: true},
  browserVersion: {type: String},
  language: {type: String},
  netType: {type: String},
  evn: {type: String},
  appName: {type: String},
  appVersion: {type: String},
  // route info
  route: {type: String},
  routeName: {type: String},
  // action info
  actionType: {type: String, required: true},
  category: {type: String},
  text: {type: String},
  label: {type: String},
  value: {type: String},
  description: {type: String},
  startTime: {type: Number},
  endTime: {type: Number},
};

创建接口

addApis.png

路由、页面异常以及 nav 导航栏埋点配置

router:

const beforeRender = (input, next) => {
  const {path, prevPath} = input;
  const validPath = path.split('?')[0];
  if (validPath === initPath) {
    return next({path: '/'});
  }
  if (!isAuthed() && !whiteRoutes.includes(validPath)) {
    return next({path: '/user/signin'});
  }
  if (path !== prevPath && demoBackReg.test(prevPath)) {
    // designReg
    return confirmDesignPage(next);
  }
  next();
  routeReport();
};

// ErrorBoundary
const pageError = ({error}) => {
  const errStack = error?.message?.slice(0, 120).split('. ').slice(0, 2).join('. ');
  report({actionType: 'pageError', text: errStack});
};
const errorBoundary = props => <HandleError {...props} report={pageError} />;

export default {
  browserRouter,
  beforeRender,
  basepath,
  errorBoundary,
};

导航栏:

const handleNavClick = (props, item) => {
  report({
    actionType: 'click',
    category: 'navbar',
    text: item.name || item.title || item.type,
    value: item.type,
  });
  const {handle, path, link} = item;
  if (typeof handle === 'function') {
    return handle(item);
  }
  if (link) {
    return window.open(link);
  }
  if (path) {
    return props.router.push(path);
  }
};

展示页面

拿到数据可以通过不同纬度去作分析与展示,比如时间、操作系统、项目名、浏览器等。

report.png