npm package: @hai-platform/logger
Description: supports log and download in web environment
Latest version: 0.3.3

# @hai-platform/logger

基于 indexDB 的前端本地日志存储库。

## 接入方式

下载 npm 包

```shell
npm install --save @hai-platform/logger
```

或者

```shell
yarn add @hai-platform/logger
```

## 环境需要

@hai-platform/logger 使用了动态导入 (dynamic imports) 来分割代码，目的是实现按需加载。因此你需要使用 [webpack](https://webpack.docschina.org/) 来打包你的项目。

## 简单上手

### 日志存储

在脚本代码中你可以使用 log() 方法来记录日志内容。日志信息会被 @hai-platform/logger 按序保存在本地浏览器的 IndexedDB 库中。log 方法的调用方式是同步的，其内部会异步执行日志的本地存储，你无需等待日志的存储结果返回。如果你很关心存储过程是否发生异常，可以在 initConfig 方法中配置 errorHandler 来获取存储时异常。

```js
import HFLogger from '@hai-platform/logger'
let logContent = 'Your log content'
let logType = 1

HFLogger.log(logContent, logType)
```

## API

### 📚 initConfig(globalConfig)

该方法为 HFLogger 单例设定全局配置。一般情况下你只需在引入 HFLogger 后执行一次该方法，设定好全局参数即可。该方法每次被调用时，都会覆盖现有所有的 HFLogger 全局配置。**该方法不是必要的，以下配置参数也都是可选的**。

- globalConfig: 全局参数的配置对象。
  - publicKey (可选): 1024 位的 RSA 加密公钥。如果你需要调用 logWithEncryption() 方法对本地日志进行加密操作，那么你必须事先配置该公钥。与该公钥配对的私钥存储于你的服务器上（对于幻方内部应用，建议暂时没有必要配置 RSA 加密，默认会采用 base64）。
  - logTryTimes (可选): HFLogger 在遇到本地存储失败的情况下，会尝试的次数。默认为 3 次。如果 HFLogger 存储失败了 logTryTimes 次数后将不再进行后续日志的存储。
  - dbName (可选): 你可以配置该项来自定义本地 DB 库的名字。默认为 HFLogger_web_db。不同 DB 库之间的数据是隔离而不受影响。
  - errorHandler (可选): 你可以配置该项来接收 log() 和 logWithEncryption() 方法可能产生的异常。HFLogger 的 log 及 logWithEncryption 方法在底层会执行异步存储，因此你无需等待这两个方法的返回。如果你确实想知道 HFLogger 在存储时是否报错了，你可以配置该方法来获取异常。
  - succHandler (可选): 你可以配置该项回调，该方法会在 log() 和 logWithEncryption() 方法内异步存储日志成功后执行。

```typescript
import HFLogger from '@hai-platform/logger'
HFLogger.initConfig({
  publicKey:
    '-----BEGIN PUBLIC KEY-----\n' +
    'MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgG2m5VVtZ4mHml3FB9foDRpDW7Pw\n' +
    'Foa+1eYN777rNmIdnmezQqHWIRVcnTRVjrgGt2ndP2cYT7MgmWpvr8IjgN0PZ6ng\n' +
    'MmKYGpapMqkxsnS/6Q8UZO4PQNlnsK2hSPoIDeJcHxDvo6Nelg+mRHEpD6K+1FIq\n' +
    'zvdwVPCcgK7UbZElAgMBAAE=\n' +
    '-----END PUBLIC KEY-----',
  errorHandler: function (e) {},
  succHandler: function (logItem) {
    var content = logItem.content
    var logType = logItem.logType
    var encrypted = logItem.encrypted
    console.log('Log Succ:' + content)
  },
})
HFLogger.logWithEncryption('confidentialLogContent', 1)
```

### log(content, logType)

- content: 日志内容。

- logType: 日志类型。日志类型用于日志分类，便于你对已上报的日志内容进行分类查看。你可以自己定义需要的日志类型。

### logWithEncryption(content, logType)

使用 log() 方法落地的日志接近于明文存储，任何有办法触达该用户端的人都能够获取到本地日志。如果你期望一些日志内容加密后再落地，你可以调用该方法。HFLogger 使用对称加密结合非对称加密的方式来保障本地日志安全。日志内容会使用 AES 进行加密，同时 AES 加密时使用的对称密钥会使用 RSA 进行非对称加密，加密后的密钥密文会和日志密文一起落地下来。

需要注意的是：虽然使用该方法存储后的日志很难再被破解，但是不能保证你的日志内容在存储之前不被窃听。另外由于在用户端加密以及在服务端解密都更耗费时间且可能引起**性能问题**，所以建议你只在日志内容敏感的必要时候使用该方法。

## genLogBundle(bundleConfig)

将所有的 log 返回成一个 bundle(字符串)，BundleConfig 为可选参数，其定义如下：

```typescript
export interface BundleConfig {
  /**
   * @param {YYYY-MM-DD}
   */
  fromDayString: string
  /**
   * @param {YYYY-MM-DD}
   */
  toDayString: string
  environment?: string // User Agent
  bundle_name?: string // 日志下载的时候可以用于定义文件名
}
```

## triggerSaveBundle(bundleConfig)

本函数实际上和 genLogBundle 比较接近，内部调用了 triggerSaveBundle，区别在于这个函数会直接模拟浏览器的下载功能，下载日志文件到本地。

## 过期和容量策略

- 容量极限：

尽管 IndexedDB 的数据容量较之于其他浏览器存储空间来说是很大了，但它也是有容量限制的。IndexedDB 的容量限制是域隔离的，有些浏览器会在当前域下 IndexedDB 超过 50MB 数据用量时弹出用户授权弹框来引导用户允许更大容量的本地存储使用空间。为了避免影响用户，HFLogger Web 将最多只存储 7 天日志，每天日志量限制在 7M。达到该日志量后，会触发下文的滚动删除策略。过期日志会在下一次 log 时被清除。

- 滚动删除：

对于每一天的日志，我们会对日志进行分页存储（所谓分页，实际上是数据库中的一个索引字段），当当天的日志超出限制后，我们会将较早的页数的日志进行删除，例如：

当日志超过 7M 的时候，我们会将第 1 页进行删除，然后新的日志会被写入第 8 页，之后再次超过之后，就会将第 2 页进行删除，以此类推。

- 单条日志限制：

我们的单条日志最大不能超过 200KB，对于超过 200KB 的单条内容，会存储失败。

## HFLogger Web SDK 的整体架构

我们在开发过程中参考了美团技术团队开源的 Logan(MIT 协议)，可以参考原项目的整体架构：

[Logan WebSDK](https://github.com/Meituan-Dianping/Logan/tree/master/Logan/WebSDK)

## 极端测试

- 测试日志超出限制之后的表现：

```typescript
// 每次写入大约 100KB，循环 70 次之后即超出了大小
let example102KBLog = new Array(10240).fill('123456789').join()

for (let i = 0; i < 100; i += 1) {
  window.HFLogger.log(example102KBLog, 1)
}
```