# `@lx-frontend/multi-track`

sls 监控日志上报

## 用法

### 前提

1. 在阿里云 sls 创建好 `project` 和 `store`
2. store 需要开启：`WebTracking`
3. 小程序项目需要再后台 `request` 域名增加上述 `project` 域名
4. 项目需要接入 `@lx-frontend/taro-plugin-monitor`

### 示例配置

1. 在 taro 配置增加 `taro-plugin-monitor` 使用，在 `config/index.js`

```js
plugins: [
      [
        '@lx-frontend/taro-plugin-monitor',
        {
          monitor: {
            /**
             * 允许触发事件的节点名称
             */
            allowNodeNames: ['view', 'button', 'text', 'image'] as string[],

            /**
             * 是否开启节点监控
             */
            enableNodeMonitor: true,

            /**
             * 是否开启 API 监控
             */
            enableApiMonitor: true,

            /**
             * 是否开启生命周期监控
             */
            enableLifecycleMonitor: true,

            /**
             * 是否开启错误监控
             */
            enableErrorMonitor: true
          }
        }
      ]
    ],
```

2. 新建 `monitor/track.ts` 文件

```ts
import { MiniTrack } from '@lx-frontend/multi-track'
import Taro from '@tarojs/taro'

// 用于存储，门店、用户等信息。
const _userInfo: Record<string, any> = {}

/**
 * 在企业微信中可能存在下面 API 不支持情况
 * @returns
 */
const getMiniAppInfo = () => {
  const windowInfo = Taro.getWindowInfo?.() || {}
  const systemSetting = Taro.getSystemSetting?.() || {}
  const deviceInfo = Taro.getDeviceInfo?.() || {}
  const appBaseInfo = Taro.getAppBaseInfo?.() || {}
  const appAuthorizeSetting = Taro.getAppAuthorizeSetting?.() || {}

  return {
    windowInfo,
    systemSetting,
    deviceInfo,
    appBaseInfo,
    appAuthorizeSetting,
  }
}

const getUserInfo = () => {
  return _userInfo || {}
}

export const setUserInfo = (userInfo: Record<string, any> = {}) => {
  Object.assign(_userInfo, userInfo)
}

// 排除掉一些带有敏感数据的接口。
const ignoreRequestUrlList: string[] = ['xxxx/MpLogin', 'xxxx/ChooseStore', 'auth/xxx_wxlogin']

/**
 * 日志上报 track 实例
 *
 * 针对一些异步登录，初始化等场景，需要手动调用 track.setUploadEnabled(true) 来开启上报。
 */
export const track = new MiniTrack({
  slsOptions: {
    host: 'cn-hangzhou.log.aliyuncs.com', // 替换成 sls 提供的
    project: 'xxxx', // 替换成 sls project 名称
    logstore: 'xxxx', // 替换成 sls project 下 store 名称
    topic: process.env.TARO_APP_RUN_ENV || 'test',
    source: 'xxxx-mp-frontend', // 一般为项目仓库名，随便写
    tags: {
      // 用于 区分日志的 tag，对象即可，这里我只加了一个
      version: VERSION,
    },
  },
  trackOptions: {
    /**
     * 上报队列，add 数据达到10条就触发一次上报
     */
    maxQueueSize: 10,

    /**
     * 最后一次 add 后，没任何操作 5s 后上报
     */
    debounceTime: 5000,

    /**
     * 是否持久化
     */
    persistLogs: true,

    /**
     * 持久化 key
     */
    localStorageKey: 'localStorageKey',
  },
  // add 日志都会经过该函数处理
  sendPayload: (log) => {
    // 简单数据校验
    if (!log?.eventType) {
      return {}
    }

    const { userInfo } = getUserInfo()

    // 需要处理掉一些敏感数据，比如请求头 和 登录接口响应的
    // ParkBffService/MpLogin
    if (log.eventType === 'http') {
      log.headers = JSON.stringify({})
    }

    if (log.eventType === 'http' && ignoreRequestUrlList.some((item) => log.requestUrl.includes(item))) {
      log.body = JSON.stringify({})
    }

    return {
      ...log,
      // 增加一些业务
      ...(log.eventType === 'lifecycle' && log.lifecycleStage === 'onLaunch' ? getMiniAppInfo() : {}),
      userId: userInfo?.user?.id || '',
      userName: userInfo?.user?.displayName || '',
      storeId: userInfo?.user?.storeId || '',
    }
  },
})
```

3. 新建 `monitor/index.ts` 文件

```ts
import Taro from '@tarojs/taro'

import { track } from './track'

// 这些高频触发 api 忽略不上报，对定位问题基本没作用。
const apiCallIgnoreList: string[] = [
  'getWindowInfo',
  'getSystemSetting',
  'getDeviceInfo',
  'getAppBaseInfo',
  'getAppAuthorizeSetting',
  'getStorage',
  'getStorageInfo',
  'getStorageSync',
  'getStorageInfoSync',
  'reportEvent',
  'getSystemInfo',
  'getSystemInfoSync',
  'getPrivacySetting',
]

// 注意要忽略本身
const httpIgnoreList: string[] = ['APIVersion=0.6.0']

Taro?.monitorEvent?.on('all', (log) => {
  // 简单数据校验
  if (!log?.eventType) {
    return
  }

  // 过滤一些不需要上报的事件
  if (log?.eventType === 'api' && apiCallIgnoreList.includes(log?.apiName)) {
    return
  }

  if (log?.eventType === 'http' && httpIgnoreList.some((item) => log?.requestUrl?.includes(item))) {
    return
  }

  if (log?.eventType === 'lifecycle' && log?.lifecycleStage === 'onReady') {
    return
  }

  track.add(log)
})
```

4. 在小程序入口处引用

```ts
// 引用监控
import '@/monitor'

import { PropsWithChildren } from 'react'
import { useLaunch } from '@tarojs/taro'

import './app.less'

function App({ children }: PropsWithChildren<any>) {
  useLaunch(() => {
    console.log('App launched.')
    // 检查版本更新
    checkVersion()
  })

  // children 是将要会渲染的页面
  return children
}

export default App
```
