前端日志, 追踪行为 @bolt/logger & @bolt/trace

「要多想。」

「想了以后呢?」

「北海, 我只能告诉你在那(初始化项目)以前要多想。」

-- 『三体』

@bolt/logger Api 设计

基本使用

创建一个 logger 实例
TypeScript 复制代码
const { createLoggerFriendly, use } = createInstance(instanceOption);

/**
 * === 通过 use(BaseLogger) 来兼容各平台 ===
 */

/** production 环境禁用 console */
process.env.NODE_ENV === "development" && use(consoleLogger);

use(debugLogger);
use(teaLogger);
use(slardarLogger);
为各 namespace 创建 logger
TypeScript 复制代码
const defaultLogger = createLoggerFriendly("[default]");
export default defaultLogger;

export const apiLogger = createLoggerFriendly('api_fetch', createOption);
export const fgLogger = createLoggerFriendly("[fg]");
约束 logger 调用参数
TypeScript 复制代码
defaultLogger.info(content, data, option);
defaultLogger.debug(content, data, option);
defaultLogger.warn(content, data, option);
defaultLogger.error(content, data, error, option);
  • content 为 string, 描述当前的行为

  • data 为扩展字段, 上报需要记录的数据

  • option 与 createOption, instanceOption 类型一致

增加一个平台的兼容

定义 Baselogger
TypeScript 复制代码
import { defineBaseLogger } from "@bolt/logger";
import Tea from "byted-tea-sdk";

export default defineBaseLogger(async (option) => {
  const { level, content, namespace, data } = option;
  Tea.event("bolt_logger", {
    level,
    content,
    namespace,
    ...data,
  });
});
通过 ts 扩展 option

还记得 @bolt/api 吗, 基于洋葱模型的工程化网络请求库: @bolt/api

自定义配置可追溯的优势我们并没有丢掉

创建 instance 时通过泛型约束 BaseLogger 可以用的所有自定义 Option 强制约束中间件声明所用到的 Option 字段 创建 logger 时提示所有可选 Option
一个文件了解所需内容, 知道自定义配置字段从哪儿来 再复杂的日志平台也不怕 VSCode 可以通过 ts 快速定位到类型定义

设计一款追踪行为的日志工具

基本使用

创建一个 trace 实例
TypeScript 复制代码
export const { createTrace } = createInstance();
针对业务场景定义 trace
TypeScript 复制代码
import { createTrace } from "./instance";

/** jsReady -> 上报 tti */
export const ttiTrace = createTrace<{
  page: "list" | "detail";
}>("ttiTrace");

/** list 页面进入 -> list 页面跳出 */
export const listTrace = createTrace("listTrace");

/** list 卡片点击 -> detail 加载完成 */
export const listEnterDetailTrace = createTrace("listEnterDetailTrace");

/** detail 页面进入 -> detail 页面跳出 */
export const detailTrace = createTrace("detailTrace");
在业务生命周期里调用
TypeScript 复制代码
listTrace.start('准备打开list了', { xxx: '一些自定义数据' })
listTrace.mark('记录一些中间行为日志', { xxx: '一些自定义数据' })
listTrace.mark('记录一些中间行为日志', { xxx: '一些自定义数据' })
listTrace.finish('list已加载完成', { xxx: '一些自定义数据' })

// 如果失败了不会调用 finish, 而是
listTrace.fail('list加载失败', error, { xxx: '一些自定义数据' })

trace 从 start 到 finish 都做了什么

trace 基于 logger 实现, 也就是通过 logger 上报日志到各平台

  • 默认了一些内置上报的数据, 如统计时间间隔
TypeScript 复制代码
/**
 * === trace 统计时间字段说明 ===
 * - t: 当前时间
 * - td: (第一个)开始 -> 结束的时间间隔
 * - tdx: (最后一个)开始 -> 结束的时间间隔
 *    - 只有一个开始时, tdx === td
 *    - 多个开始时, trace 上报为 warn
 * - ti: @bolt/trace 中没有实现, 用户可以在 mark 中传入自定义参数, 自行统计两个 mark 之间的时间差
 */
  • 收集从 start 到 finish(或 fail) 的完整路径, 通过时间戳方便在 tea 或 slardar 定位相关日志 data 数据
TypeScript 复制代码
"start:接口==>开始@1658308526614;mark:请求->开始@1658308526616;mark:请求<-完成@1658308527392;finish:接口<==完成@1658308527392"

// 格式化后好看一点
"start:接口==>开始@1658308526614;
mark:请求->开始@1658308526616;
mark:请求<-完成@1658308527392;
finish:接口<==完成@1658308527392"
  • finish 时会重置 start 和 trace 路径, 方便下次 start 重新记录

接口请求监控: 实现一款 @bolt/api 的中间件

  • 统计接口响应速度

  • 监控接口报错

打算如何使用
TypeScript 复制代码
import { createInstance } from "@bolt/api";

const { createApi, use } = createInstance();

/**
 * 使用 trace 代替 logger, 能够记录接口最终耗时
 * 必须放在其它中间件之前, 用来最接近用户真实感受
 */
use<TraceOption>(trace);
// use(logger);
/**
 * 静默登录&重登中间件
 */
use<LoginNeedOption>(
  createLoginMiddleware({
    checkLoginError,
  })
);
/**
 * 使用 trace.mark 记录真实接口耗时, 不包括登录&重登耗时
 * 必须放在其它中间件之后, 且在<错误码分类中间件>之前
 */
use<TraceOption & LoginNeedOption>(tracePureApi);
/**
 * 对后端错误码分类, 抛出各类错误
 * 中间件必须放在最后一位
 */
use<UnwrapOption>(unwrap);
代码实现
TypeScript 复制代码
export type CustomOption = {
  trace?: TraceObj;
};

export const trace = defineMiddleware<CustomOption>(async (option, next) => {
  const apiTrace = createTrace(`apiTrace:${option.path}`);
  option.trace = apiTrace;
  try {
    apiTrace.start('接口==>开始', { option });
    const res = await next();
    apiTrace.finish('接口<==完成', { option });
    return res;
  } catch (error) {
    apiTrace.fail('接口<xx失败', { option }, error);
    throw error;
  }
});

/** 基于 loginMiddleware 记录 relogin mark */
export const tracePureApi = defineMiddleware<CustomOption & LoginNeedOption>(
  async (option, next) => {
    const apiTrace = option.trace
    const prefix =
      option.needLogin && option.reloginCount !== 0
        ? `重发${option.reloginCount}`
        : "请求";
    try {
      const t = apiTrace?.mark(`${prefix}->开始`, { option }) || Date.now();
      const res = await next();
      const ti = Date.now() - t;
      apiTrace?.mark(`${prefix}<-完成`, { option, ti });
      return res;
    } catch (error) {
      apiTrace?.fail(`${prefix}<x失败`, { option }, error);
      throw error;
    }
  }
);
多实例 trace 介绍
  • 对于业务行为来说, 如打开详情, start 之后, 第二次 start 必定在第一次 finish 之后, 如果存在两次 start, 说明代码有多次 render 风险(React 尤其注意), 因此多次 start 可以直接通过 logger.warn 记录警告风险.

  • 而对于接口来说, 如果创建了一个 apiTrace, 多个请求并发, 必定会先多次 start, 然后多次 finish, 那么对于一个 trace 的时间记录就不准确了

    • 同一个 path 的接口也是会有并发的可能, 因此必须在 api 中间件中针对每次请求单独创建 trace
为何导出了两个中间件

正如注释中提到的, 我们实际调用 api 方法到其返回的时间并不代表是后端接口响应的时间, 因为要考虑中间还有中间件的耗时, 尤其是重登等多次重试直到成功后的返回.

例如重试的 trace 路径为:

TypeScript 复制代码
"start:接口==>开始@1658310509100;mark:请求->开始@1658310509101;fail:请求<x失败@1658310509274;mark:重发1->开始@1658310509275;fail:重发1<x失败@1658310509448;mark:重发2->开始@1658310509449;fail:重发2<x失败@1658310509790;mark:重发3->开始@1658310509791;fail:重发3<x失败@1658310509868;fail:接口<xx失败@1658310509870"

// 格式化后好看一点
"start:接口==>开始@1658310509100;
mark:请求->开始@1658310509101;
fail:请求<x失败@1658310509274;
mark:重发1->开始@1658310509275;
fail:重发1<x失败@1658310509448;
mark:重发2->开始@1658310509449;
fail:重发2<x失败@1658310509790;
mark:重发3->开始@1658310509791;
fail:重发3<x失败@1658310509868;
fail:接口<xx失败@1658310509870"

这里可知:

  • 单次请求后端接口只需173ms(= 1658310509274 - 1658310509101) 就返回请求错误了

  • 而业务 api 调用在 770ms(= 1658310509870 - 1658310509100) 后才真正将报错抛出

声明: 本文说提到的 @bolt/logger @bolt/trace 为虚构, 并未发布到 npm 仓库

相关推荐
jktantan8 小时前
Vue keep-alive 切标签后滚动位置回顶?因为 onDeactivated 里读到的 scrollTop 是 0
前端·javascript·vue.js
总要学点什么9 小时前
第89篇:三维特效-螺旋圆效果——让螺旋臂卷起一圈圈外扩的涟漪
开发语言·javascript·ecmascript·webgl·cesium·空间分析·1024程序员节
前端精髓10 小时前
调用openrouter的免费API模型
java·前端·javascript
螺丝钉tina16 小时前
[Playwright] [02]常用命令与调试:Debug UI、Mode和 Trace Viewer
开发语言·javascript·测试工具·ui·自动化
开开心心就好17 小时前
竖式除法出题工具,整除余数混合模式
运维·服务器·前端·javascript·ocr·perl·1024程序员节
এ慕ོ冬℘゜17 小时前
JS 事件循环、宏任务微任务:单线程底层原理 + 执行顺序
开发语言·javascript·ecmascript
web打印社区17 小时前
Lodop排障总纲-常见报错一张表
开发语言·前端·javascript·chrome·websocket
程序员大阳18 小时前
副队长JavaScript教程(56)-DOM之表单操作
javascript·dom·表单操作
Dxy123931021618 小时前
JS AES加密详解
开发语言·javascript·ecmascript
FRing0319 小时前
JavaScript入门到精通三十三(异常处理、call方法、apply方法、bind方法、防抖、节流)
开发语言·前端·javascript