前端日志调试也能专业化?我们这样设计日志系统

我们团队做的系统不是面向 C 端,而是一个带中后台的 B 端 SaaS 平台。用户不多,但业务复杂,页面交互、权限、多层组件嵌套,出了 bug 你靠一句 "你重试一下" 根本没用。

于是我们真的下功夫写了一个"日志系统"出来。

不是埋点,不是上报平台,是"调试用的日志" ,你平时 console.log() 的东西,全收编、格式化、可查、可过滤、可追踪、能自动上报。

这篇文章讲的是我们怎么一步一步把它做到今天这样:


👉背景

项目里日志是这样的:

ts 复制代码
console.log('开始加载')
console.log(res.data)
console.log('执行完毕')

大家也都知道"上线不能打 console",但:

  • 不打看不到数据,出问题无从下手;
  • 打了又怕污染 console 或带上敏感数据;
  • 测试环境 / 线上环境根本无法还原用户路径;
  • 日志没有结构,过滤都做不到;

而且一个页面一旦包含 iframe、micro-frontend、甚至 webview,调试日志就像打进水里,一点回响都没有。

我们痛定思痛,真的重新设计了整个前端日志体系。


🧩 日志系统的设计目标

✅ 所有日志归一管理,不再满屏 console

✅ 每条日志带上下文(页面路径 / 用户 / 请求 ID)

✅ 支持日志分级(debug / info / warn / error)

✅ 开发时输出到控制台,线上时输出到远程服务(带缓冲)

✅ 支持动态开启日志过滤(调试指定用户 / 页面)

✅ 不影响性能,不干扰 UI 渲染


🔧 日志系统模块结构

我们把日志系统分成五个部分:

模块名 作用描述
logger.ts 日志核心:提供 debug/info/warn/error 方法
context.ts 上下文注入:获取当前页面、用户、traceId 等
transport.ts 日志发送器:控制本地输出还是上报服务端
filters.ts 日志过滤器:比如调试指定用户、关键词
proxy-console.ts 可选:接管 console.log() 重定向到 logger

这五个模块都可以独立测试、替换,完全不是"封一层函数"那么简单。


🧱 核心实现结构

1️⃣ 日志结构标准化

ts 复制代码
interface LogItem {
  level: 'debug' | 'info' | 'warn' | 'error'
  message: string
  time: string
  traceId?: string
  userId?: string
  route?: string
  detail?: any
}

所有输出都走这个结构。

2️⃣ 核心输出入口 logger.ts

ts 复制代码
import { getContext } from './context'
import { transport } from './transport'

function baseLog(level: LogItem['level'], msg: string, detail?: any) {
  const ctx = getContext()
  const item: LogItem = {
    level,
    message: msg,
    time: new Date().toISOString(),
    traceId: ctx.traceId,
    userId: ctx.userId,
    route: ctx.route,
    detail,
  }

  transport(item)
}

export const logger = {
  debug: (msg: string, detail?: any) => baseLog('debug', msg, detail),
  info: (msg: string, detail?: any) => baseLog('info', msg, detail),
  warn: (msg: string, detail?: any) => baseLog('warn', msg, detail),
  error: (msg: string, detail?: any) => baseLog('error', msg, detail),
}

🧠 上下文注入模块(context)

这个模块统一获取日志需要的信息,支持你换掉 router、用户管理、TraceID 模式而不影响 logger。

ts 复制代码
export function getContext() {
  const route = window.location.pathname
  const userId = localStorage.getItem('uid') || ''
  const traceId = sessionStorage.getItem('trace_id') || generateTraceId()
  return { route, userId, traceId }
}

每次刷新都会自动生成 traceId,调试的时候一查就知道是哪一批日志。


🚀 日志发送模块(transport)

ts 复制代码
export function transport(item: LogItem) {
  const isDev = import.meta.env.MODE === 'development'
  
  if (isDev) {
    console[item.level === 'error' ? 'error' : 'log']('[log]', item)
    return
  }

  // 缓存后批量发送
  logBuffer.push(item)
  if (logBuffer.length >= 10) flushLogs()
}

function flushLogs() {
  const payload = logBuffer.splice(0, logBuffer.length)
  navigator.sendBeacon('/api/log', JSON.stringify(payload))
}

🔍 动态开启调试过滤(filters)

我们为了方便调试指定用户或页面,会加一个 window.__debug_filter__

ts 复制代码
window.__debug_filter__ = {
  userId: 'u12345',
  route: '/dashboard',
  includeLevel: ['warn', 'error']
}

在 transport 中判断,如果命中条件就输出,否则静默处理,调试体验超强。

📦 日志系统上线 2 个月后的真实收益

  • 线上报错排查时间从半小时缩短到 5 分钟;
  • 日志数据也作为我们用户使用路径分析的补充;
  • 后端反馈请求 ID 后,我们能顺藤摸瓜还原整个日志链;
  • 项目中的 console.log 基本为 0,日志系统真正变成了"工具链";

✍️ 感想

一个简单可控的日志系统,不仅能帮你调试 bug,更能撑起一个中型前端项目的可观测性、可靠性、可追溯性

📌 你可以继续看我的系列文章

相关推荐
excel16 分钟前
微信小程序鉴权登录详解 —— 基于 wx.login 与后端 openid 换取流程
前端
Gazer_S18 分钟前
【前端隐蔽 Bug 深度剖析:SVG 组件复用中的 ID 冲突陷阱】
前端·bug
蓝婷儿1 小时前
每天一个前端小知识 Day 7 - 现代前端工程化与构建工具体系
前端
mfxcyh2 小时前
npm下载离线依赖包
前端·npm·node.js
waterHBO2 小时前
01 ( chrome 浏览器插件, 立马翻译), 设计
前端·chrome
江城开朗的豌豆2 小时前
Vue组件CSS防污染指南:让你的样式乖乖“宅”在自家地盘!
前端·javascript·vue.js
江城开朗的豌豆3 小时前
Vue组件花式传值:祖孙组件如何愉快地聊天?
前端·javascript·vue.js
浩男孩3 小时前
【🍀新鲜出炉 】十个 “如何”从零搭建 Nuxt3 项目
前端·vue.js·nuxt.js
拉不动的猪4 小时前
pc和移动页面切换的两种基本方案对比
前端·javascript·vue.js