# ARMS BROSWER LOGGER

```
    ____                                    __                               
   / __ )_________ _      __________  _____/ /   ____  ____ _____ ____  _____
  / __  / ___/ __ \ | /| / / ___/ _ \/ ___/ /   / __ \/ __ `/ __ `/ _ \/ ___/
 / /_/ / /  / /_/ / |/ |/ (__  )  __/ /  / /___/ /_/ / /_/ / /_/ /  __/ /    
/_____/_/   \____/|__/|__/____/\___/_/  /_____/\____/\__, /\__, /\___/_/     
                                                    /____//____/             
```

## 开始使用

### 申请阿里云前端监控PID

阿里云业务实时监控服务(ARMS)前端监控平台专注于 Web 端体验数据监控，从页面打开速度（测速）、页面稳定性（JS Error）和外部服务调用成功率（API）这三个方面监测 Web 页面的健康度。

您可以在[这里](https://help.aliyun.com/document_detail/58652.html)了解我们的应用.

在接入之前请确保您已经申请待接入**站点唯一PID**

如果需要申请PID，可以参看[帮助文档](https://help.aliyun.com/document_detail/58663.html)



### cdn引入
在页面头部或 body 第一行引入脚本：

```html
<script>
!(function(c,b,d,a){c[a]||(c[a]={});c[a].config={pid:"站点唯一ID"};
with(b)with(body)with(insertBefore(createElement("script"),firstChild))setAttribute("crossorigin","",src=d)
})(window,document,"https://retcode.alicdn.com/retcode/bl.js","__bl");
</script>
```

### npm包引入

```javascript
const BrowerLogger = require('alife-logger');
// BrowserLogger.singleton(conf) conf传入config配置
const __bl = BrowerLogger.singleton({
    pid: 'your-project-id',
    imgUrl: 'https://arms-retcode.aliyuncs.com/r.png?', // 设定日志上传地址,新加坡部署可选`https://arms-retcode-sg.aliyuncs.com/r.png?`
    // 其他config配置
});
```

- 默认会开启 `fetch` 和 `XMLHttpRequest` 的请求上报，以及页面 JS Error 的上报；
- 站点 ID 会在 ARMS 后台创建的时候自动生成，是当前站点的唯一标识

### config配置项

| 参数名        | 类型       | 描述                                                              | 是否必须 | 默认值            |
|:--------------|:-----------|:------------------------------------------------------------------|:---------|:------------------|
| pid           | `String`   | 项目唯一 ID，由 ARMS 在创建站点时自动生成                         | 是       | 无                |
| page          | `String`   | 页面名称，默认取当前页面 URL 的关键部分                           | 否       | `host + pathname` |
| sample        | `Integer`  | 日志采样配置，值`1/10/100`，性能和成功API日志按照`1/sample`采样，即`1`表示`100%采样`,`10`表示`10%采样`，`100`表示`1%`采样 | 否 | `1` | 
| enableSPA     | `Boolean`  | 是否监听页面的 hashchange 事件并重新上报 PV，适用于单页面应用场景 | 否       | `false`           |
| parseHash     | `Function` | 配合 enableSPA 使用，详情见下文                                   | 否       | 见下文            |
| disableHook   | `Boolean`  | 是否禁用 AJAX 请求监听，默认会监听并用于 API 调用成功率上报       | 否       | `false`           |
| autoSendPv    | `Boolean`  | 是否初始化后自动发送 PV，默认会自动发送                           | 否       | `true`            |
| sendResource  | `Boolean`  | 是否上报资源数据，默认不上报                                    | 否       | `false`            |
| ignoreUrlCase | `Boolean`  | 是否忽略page url大小写，默认忽略                                 | 否      | `true`            |
| urlHelper | *          | URL 规整规则，详情见下文                                          | 否       | 见下文            |
| apiHelper | *          | API 规整规则，详情见下文                                           | 否       | 见下文            |

**部分设置项详细说明**

#### 1. `parseHash` 将URL hash 解析为page的方法

此参数用于单页面应用场景中，在设置了 `enableSPA` 为 `true` 的前提下，页面触发 hashchange 事件时，将 URL hash 解析为 page 字段的方法。

默认值是一个简单的字符串处理方法：

```js
function (hash) {
    var page = hash ? hash.replace(/^#/, '').replace(/\?.*$/, '') : '';
    return page || '[index]';
}
```

**此项一般情况下不需要修改**，不过如果需要在上报时使用自定义的页面名，或者 URL 的 hash 比较复杂（ _例如 /aaa/bbb/:id?t=xxx_ ），则需要修改此配置项。

示例：

```js
// 定义页面 hash 和 page 的映射关系
var PAGE_MAP = {
    '/': '首页',
    '/contact': '联系我们',
    '/list': '数据列表',
    // ...
};

// 页面 onload 后调用 SDK 方法
window.addEventListener('load', function (e) {
    // 调用 setConfig 方法修改 SDK 配置项
    __bl.setConfig({
        parseHash: function (hash) {
            key = hash.replace(/\?.*$/, '');
            return PAGE_MAP[key] || '未知页面';
        }
    });
});
```

#### 2. `urlHelper` URL规整规则，代替原`ignoreUrlPath`

在页面 URL 类似于 `http://xxx.com/projects/123456` 这样的场景中（projects 后面紧跟的是项目 id），
如果将 `xxx.com/projects/123456` 作为 page 上报，会导致在数据查看时页面无法聚成一类，所以需要过滤掉这些非关键字符。

此设置项只在自动获取页面URL作为page时才会生效，如果手动调用 `setPage` 或 `setConfig` 方法修改过 page，或者设置了 `enableSPA` 的值为 `true`，则此设置项无效。

默认值是一个数组，**一般情况下不需要修改**：

```js
[
    // 将所有 path 中的数字变成 *
    {rule: /\/([a-z\-_]+)?\d{2,20}/g, target: '/$1**'},
    // 去掉 url 末尾的'/'
    /\/$/
]
```

此设置项的默认值会过滤掉类似 `xxxx/123456` 后面的数字，比如 `xxxx/00001` 和 `xxxx/00002` 都会变成 `xxxx/**`。

`urlHelper` 的值可以是多种类型，用法分别为：

- `String` 或 `RegExp`: 将匹配到的字符串去掉；
- `Object<rule, target>`: 对象包含两个 key，分别是 rule 和 target，作为 JS 字符串的 `String::replace` 方法的入参；
- `Function`: 将原字符串作为入参执行方法，将执行结果作为 page；
- `Array`: 用于设置多条规则，每条子规则的都可以是上述类型之一。

#### 3. `apiHelper` api规整规则，代替原`ignoreApiPath`

用于在自动上报 API 的时候过滤掉接口 URL 中的非关键字符，用法及含义同 `urlHelper`

默认值是一个对象，**一般情况下不需要修改**：

```js
{rule: /(\w+)\/\d{2,}/g, target: '$1'}
```

此设置项的默认值会过滤掉接口 URL 中类似 `xxxx/123456` 后面的数字。

#### 4. `config.sendResource` 资源上报, 用于慢会话追踪

是否上报资源数据，默认不上报. 如需启用慢会话追踪，需要开启此项(注: 目前新加坡region暂不支持此功能).

开启上报后，会依据[apdex](https://www.apdex.org/)选择性上报资源数据
    
- Satisfied(0-2s): 不上报 
- Tolerating(2-8s): 50%上报
- Frustrated(>8s): 100%上报

## API 接口

### 通用 API


#### 1. @static singleton() 获取单例对象

***该方法只适用于npm引入**

调用参数说明：`BrowerLogger.singleton(config,prePipe)`

静态方法，返回一个单例对象，只在第一次调用时传入的config,prePipe生效，之后调用只返回已经生成的实例：


| 参数 | 类型     | 描述              | 是否必须 | 默认值 |
|:-----|:---------|:------------------|:---------|:-------|
| config  | `Object` | 站点配置, 其他配置查看 #config配置项 | 是       | -      |
| prePipe | `Array` | 预上报内容| 否       | -      |

此方法可以用于在应用入口初始化 SDK，也可以在每次调用时获取实例。


#### 2. `setConfig()` 修改配置项

用于在 SDK 初始完成后重新修改部分配置项，具体配置请参照 SDK 配置项。

调用参数说明：`__bl.setConfig(next)`

| 参数 | 类型     | 描述                   | 是否必须 | 默认值 |
|:-----|:---------|:-----------------------|:---------|:-------|
| next | `Object` | 需要修改的配置项以及值 | 是       | -      |

示例：修改 disableHook 禁用 API 自动上报

```js
__bl.setConfig({
    disableHook: true
});
```

#### 3. setPage() 设置当前页面的page name

用于重新设置页面的 page（默认会触发重新上报 PV），此接口一般在单页面应用中会用到。

调用参数说明：`__bl.setPage(next, sendPv)`

| 参数   | 类型      | 描述                   | 是否必须 | 默认值 |
|:-------|:----------|:-----------------------|:---------|:-------|
| page   | `String`  | 新的page name          | 是       | -      |
| sendPv | `Boolean` | 是否上报PV，默认会上报 | 否       | `true` |

示例：

```javascript
// 设置当前页面的 page name 为当前的 URL hash，并重新上报 PV
__bl.setPage(location.hash);

// 仅设置当前页面的 page 为'homepage'，但不触发 PV 上报
__bl.setPage('homepage', false);
```

#### 4. `removeHook()` 移除当前实例上的自动 API 上报

> 如果需要手动上报 API 监听数据，则可以调用此方法移除自动上报监听（推荐在初始化配置中直接设置 `disableHook` 为 true）

```javascript
__bl.removeHook();
```

#### 5. `addHook(isForce)`  在当前实例上挂载 API 监听的 hook

> 用于多实例的场景，将 API 监听的 hook 从一个实例上移除后，挂载到另一个实例上，如果 `isForce` 为 true，则强制将挂载实例指向自己

```javascript
__bo.addHook(true);
```

#### 6. `createInstance(config)` 创建一个新的 BrowserLogger 实例

> 创建一个新的实例，新的实例默认继承当前实例的 pid，用于单页面应用需要分区块上报的场景

```javascript
// 创建一个新的实例
var sdk2 = __bl.createInstance({
    tag: 'sdk2',
    page: 'another_page' // 如果设置了 page，则会自动上报一次 PV
    // ...
});
```

### 数据上报接口

#### 1. api() 接口调用成功率上报

此接口用于上报页面的 API 调用成功率，SDK 默认会监听页面的 AJAX 请求并调用此接口上报；
如果页面的数据请求方式是 JSONP 或者其它自定义方法（比如客户端 SDK 等），可以在数据请求方法中调用 `api()` 方法手动上报。

另外，如果要调用此接口，建议在 SDK 配置项中将 `disabledHook` 设置为 true，具体配置请参照 SDK 配置项。

调用参数说明：`__bl.api(api, success, time, code, msg)`

| 参数    | 类型            | 描述         | 是否必须 | 默认值 |
|:--------|:----------------|:-------------|:---------|:-------|
| api     | `String`        | 接口名       | 是       | -      |
| success | `Boolean`       | 是否调用成功 | 是       | -      |
| time    | `Number`        | 接口耗时     | 是       | -      |
| code    | `String/Number` | 返回码       | 否       | ''     |
| msg     | `String`        | 返回信息     | 否       | ''     |

示例： 

```javascript
var begin = Date.now(),
    url = '/data/getTodoList.json';

ajax(url, {id: 123456}).then(function (result) {
    var time = Date.now() - begin;
    // 上报接口调用成功
    window.__bl && __bl.api(url, true, time, result.code, result.msg);
    // do something ....
}).catch(function (error) {
    var time = Date.now() - begin;
    // 上报接口调用失败
    window.__bl && __bl.api(url, false, time, 'ERROR', error.message);
    // do something ...
});
```

#### 2. error() 错误信息上报

此接口用于上报页面中的 JS 错误或者使用者想要关注的异常；

一般情况下，SDK 会监听页面全局的 Error 并调用此接口上报异常信息，但由于浏览器的同源策略往往获取不到错误的具体信息，这时就需要使用者手动上报。

调用参数说明：`__bl.error(error, pos)`

| 参数         | 类型     | 描述                              | 是否必须 | 默认值 |
|:-------------|:---------|:----------------------------------|:---------|:-------|
| error        | `Error`  | JS 的 Error 对象                  | 是       | -      |
| pos          | `Object` | 错误发生的位置，包含以下 3 个属性 | 否       | -      |
| pos.filename | `String` | 错误发生的文件名                  | 否       | -      |
| pos.lineno   | `Number` | 错误发生的行数                    | 否       | -      |
| pos.colno    | `Number` | 错误发生的列数                    | 否       | -      |

示例：监听页面的 JS Error 并上报

```js
window.addEventListener('error', function (ex) {
    // 一般事件的参数中会包含 pos 信息
    window.__bl && __bl.error(ex.error, ex);
});
```

示例2： 上报一个自定义的错误信息

```js
window.__bl && __bl.error(new Error('发生了一个自定义的错误'), {
    filename: 'app.js', 
    lineno: 10, 
    colno: 15
});
```

### 3. speed() 自定义测速上报

此接口用于上报页面中的一些自定义的关键时间节点；

调用参数说明：`__bl.speed(point, time)`

| 参数     | 类型      | 描述                                            | 是否必须 | 默认值                   |
|:---------|:----------|:------------------------------------------------|:---------|:-------------------------|
| point    | `Enum`    | 测速关键字，必须是 s0 ~ s10                     | 是       | -                        |
| time     | `Number`  | 耗时(毫秒)，默认是当前时间 - 页面起始时间       | 否       | `Date.now() - startTime` |
| forceSPA | `Boolean` | 只在 SPA 中有效，上报数据时 page 是否使用子页面 | 否       | false                    |

示例：
```js
__bl.speed('s0');

__bl.speed('s1', 1024);
```

### 4. sum() 求和统计

此接口用于统计业务中的某些事情发生的次数

调用参数说明：`__bl.sum(key, value)`

| 参数  | 类型     | 描述                   | 是否必须 | 默认值 |
|:------|:---------|:-----------------------|:---------|:-------|
| key   | `String` | 事件名                 | 是       | -      |
| value | `Number` | 单次累加上报量，默认 1 | 否       | 1      |

示例：
```js
__bl.sum('event-a');

__bl.sum('event-b', 3);

__bl.sum('group-x::event-c', 2);
```

### 4. avg() 求平均统计

此接口用于统计业务场景中某些事情发生的平均次数或平均值

调用参数说明：`__bl.avg(key, value)`

| 参数  | 类型     | 描述               | 是否必须 | 默认值 |
|:------|:---------|:-------------------|:---------|:-------|
| key   | `String` | 事件名             | 是       | -      |
| value | `Number` | 统计上报量，默认 0 | 否       | 0      |

示例：
```js
__bl.avg('event-a', 1);
__bl.avg('event-b', 3);

__bl.avg('events::event-c', 10);
__bl.avg('speed::event-d', 142.42);
```

### 5. percent() 百分比统计

此接口用于统计业务场景中某一类事件下的各自占比

调用参数说明：`__bl.percent(key, subkey, value)`

| 参数   | 类型     | 描述                   | 是否必须 | 默认值 |
|:-------|:---------|:-----------------------|:---------|:-------|
| key    | `String` | 事件名                 | 是       | -      |
| subkey | `String` | 事件成员               | 是       | -      |
| value  | `Number` | 单次累加上报量，默认 0 | 否       | 0      |

示例：
```js
__bl.percent('gender', 'm', 1);
__bl.percent('gender', 'f', 3);
```

## FAQ

### 1. 单页面应用 (SPA) 如何分开统计 PV?

在 SPA（ _Single Page Application_ ）中，页面只会刷新一次；传统的方式只会在页面加载完成后上报一次 PV，而无法统计到各个子页面的 PV，也无法让其它类型的日志聚类到对应的子页面。

SDK 提供了两种 SPA 页面的处理方式：

#### 1. 开启 SPA 自动解析

此方法适用于大部分以 URL hash 作为路由的单页面应用场景。

在初始化的配置项中，设置 `enableSPA` 为 `true`，即会开启页面的 hashchange 事件监听（触发重新上报 PV），并将 URL hash 作为其它数据上报中的 page 字段；

另外，与 `enableSPA` 相配套的还有 `<Function>parseHash`，参见 SDK 配置项

#### 2. 完全手动上报

此方法可用于所有的单页面应用场景，如果方法一无法满足，可用此方法。

SDK 提供了 `setPage` 方法来手动更新数据上报时的 page name，调用此方法时，默认会重新上报页面 PV。

示例：

```js
// 监听应用路由变更事件
app.on('routeChange', function (next) {
    __bl.setPage(next.name);
});
```

另外，对于页面初始化完成后的第一次 PV 上报，如果也想要手动控制，可在配置项中设置 `autoSendPv` 为 `false`，然后在应用初始化完成后，调用 `setPage`


### 2. 解决 JS Error 跨域获取不到的问题

由于浏览器的安全策略，SDK 无法获取到其它 host 下的 JS Error 的错误信息，解决办法：

在 script 标签上添加 `crossorigin` 属性：
```html
<script src="xxx" crossorigin></script>
```
以上只能解决部分问题，实际情况下还是无法获取具体的错误信息，另一种办法就是用户在自己的 JS 中监听 JS Error，然后上报：
```js
window.addEventListener('error', function (e) {
    window.__bl && __bl.errorHandler(e);
});
window.addEventListener('unhandledrejection', function (e) {
    window.__bl && __bl.errorHandler(e);
});
```

### 3. 如何在 SDK 初始化前预上报数据？

- cdn引入

场景一：

在页面刚刚加载时，有一些数据需要上报，此时 SDK 可能还未初始化完成（或者不确定是否初始化完成）。

场景二：

在应用的初始化逻辑中调用 `setConfig` 方法，但由于 SDK 是异步加载的，此时可能还未加载完成。

解决办法：

SDK 在 `__bl` 对象上增加了一个 `pipe` 属性，用于将预调用的信息缓存到此变量中，例如：

```js
__bl.pipe = [
    // 将当前页面的 html 也作为一个 API 上报
    ['api', '/index.html', true, performance.now, 'SUCCESS'],
    
    // SDK 初始化完成后即开启 SPA 自动解析
    ['setConfig', {enableSPA: true}]
];
```

如果只上报单条数据，也可以直接写成：

```js
__bl.pipe = ['msg', '我是另一个普通的消息'];
```

其中数组的第 0 个表示方法名，后面依次是入参。
SDK 初始化完成后，就会将预先挂载到 `window.__bl.pipe` 上的方法及参数依次调用。

_注意：在 SDK 初始化完成前，如果多次设置 `__bl.pipe` 的值，只会以最后一次为准。_

另外，`pipe` 也可以用于 SDK 初始化完成后调用（支持 IE9 及以上），如果不能确定 SDK 是否初始化完成，又不想添加太多的判断逻辑，可以使用此方式

场景：单页面应用中，设置 `autoSend: false` 后，在应用初始化后上报第一次 PV，此时并不确定 SDK 是否初始化完成

```js
// 设置页面 name 为'homepage'，并且上报 PV
__bl.pipe = ['setPage', 'homepage'];
```

- npm 引入

场景：

有部分逻辑在调用`BrowserLogger.singleton()`之前执行，有一些数据想上报

```js

const BrowerLogger = require('alife-logger');

// 与cdn的pipe结构一致
const pipe = [
    // 将当前页面的 html 也作为一个 API 上报
    ['api', '/index.html', true, performance.now, 'SUCCESS'],
    
    // SDK 初始化完成后即开启 SPA 自动解析
    ['setConfig', {enableSPA: true}]
];

const __bl = BrowserLogger.singleton({pid:'站点唯一ID'},pipe);

```
