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

我们团队做的系统不是面向 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,更能撑起一个中型前端项目的可观测性、可靠性、可追溯性

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

相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax