前端开发者必备:用 si-log 轻松搞定 JavaScript 日志管理(附实战技

前端开发者必备:用 si-log 轻松搞定 JavaScript 日志管理(附实战技
前端开发者必备:用 si-log 轻松搞定 JavaScript 日志管理(附实战技巧)
当 console.log 不再够用时,我们该找谁?
还记得第一次写前端代码时,那个陪伴我们无数个深夜的 console.log 吗?它就像一个忠实的老朋友,总是在我们需要的时候出现,帮我们找出代码中的问题。但随着项目越来越大,这个老朋友开始显得有些力不从心了。
想象一下这样的场景:你的项目已经上线,用户反馈说某个功能偶尔会出现问题。你急冲冲地打开浏览器控制台,却发现里面空空如也——因为在生产环境,你早就把 console.log 都注释掉了。这时候,你只能抓耳挠腮,恨不得穿越回用户遇到问题的那一刻,亲眼看看发生了什么。
或者这样的场景:你的团队有十来个开发者,每个人都在代码里随心所欲地打印日志。结果控制台变成了一个大染缸,各种信息混杂在一起,想找一条有用的日志,就像在垃圾堆里找钻石。
再或者:你的产品经理突然跑过来说:"能不能统计一下用户点击这个按钮的次数?还有,我想看看用户在使用这个功能时的行为路径。"你看着满屏的 console.log,陷入了沉思——这些零散的信息,怎么才能变成有价值的业务数据?
这就是我们为什么需要专业的日志管理工具。console.log 就像一个瑞士军刀,平时用用还行,但当你需要做手术的时候,还是得请出专业的手术刀。
为什么前端也需要专业的日志框架
很多人可能会问:日志管理不是后端的事情吗?前端为什么要搞这么复杂?说实话,我最初也是这么想的。直到经历了几次"血的教训"后,我才彻底改变了这个想法。
第一个教训来自于一次线上故障。我们的电商网站在双十一期间,购物车功能突然出现了异常。用户添加了商品,但购物车里却空空如也。由于我们没有任何前端日志记录,只能依靠用户的描述和后端的接口日志来排查问题。经过一番折腾,最后发现是某个第三方库的兼容性问题导致的。如果当时有完善的前端日志系统,我们就能在第一时间定位问题,而不是让用户流失了整整两个小时。
第二个教训关于性能优化。我们的产品页面加载速度总是忽快忽慢,开发环境测试时都好好的,一到线上就出问题。后来我们引入了详细的日志记录,才发现是某个广告 SDK 在特定网络环境下会阻塞页面渲染。这个发现让我们优化了资源加载策略,页面加载速度提升了 30%。
第三个教训与用户体验相关。我们发现很多用户在填写表单时,填到一半就离开了。通过在前端记录用户的交互行为,我们发现是某个表单字段的验证规则太严格,用户多次尝试后失去耐心。基于这些日志数据,我们调整了验证策略,表单完成率提升了 15%。
这些经历让我深刻认识到,前端日志不仅仅是调试工具,更是产品优化的重要依据。它能帮我们:
- 快速定位和复现线上问题
- 监控页面性能和资源加载情况
- 分析用户行为,优化产品体验
- 收集业务数据,支持产品决策
- 建立完整的错误追踪体系
si-log 是什么来头?
在众多的前端日志库中,si-log 就像一匹黑马,悄悄地闯入了我们的视野。它的名字听起来有点技术范,但实际上却是一个相当接地气的工具。
si-log 的诞生故事很有意思。据说是一个国内的开发团队,在长期维护一个大型前端项目时,被各种日志问题折磨得痛不欲生。他们试过市面上各种日志库,要么太重,要么功能不够,要么浏览器兼容性差。最后,他们一咬牙一跺脚:“算了,我们自己造一个吧!”
于是,si-log 就诞生了。它的设计理念很简单:让前端日志管理变得简单,但又不简单。
简单是指它的使用方式——几行代码就能搞定复杂的日志管理。不简单是指它的功能——分级、格式化、插件化、性能优化,一个都不少。
si-log 最想解决的痛点,其实就是我们前面说的那些:
- 开发时调试困难:提供了丰富的日志级别和格式化选项,让调试信息一目了然
- 上线后问题难定位:支持生产环境日志收集,可以远程上报关键信息
- 日志管理混乱:通过命名空间和配置管理,让日志井井有条
- 性能担忧:轻量级设计,对页面性能影响极小
- 扩展性差:插件机制让功能扩展变得简单
从混乱调试到优雅追踪:一次开发体验的升级
说了这么多理论,让我们来看点实际的。下面是一个典型的"混乱调试"场景:
// 传统的调试方式 - 混乱不堪
function fetchUserData(userId) {
console.log('=== 开始获取用户数据 ===');
console.log('userId:', userId);
return fetch(`/api/user/${userId}`)
.then(response => {
console.log('响应状态:', response.status);
console.log('响应头:', response.headers);
return response.json();
})
.then(data => {
console.log('获取到的数据:', JSON.stringify(data, null, 2));
console.log('用户名:', data.name);
console.log('用户邮箱:', data.email);
return data;
})
.catch(error => {
console.error('出错了!!!', error);
console.error('错误信息:', error.message);
console.error('错误堆栈:', error.stack);
throw error;
});
}
控制台输出大概是这样的:
=== 开始获取用户数据 ===
userId: 12345
响应状态: 200
响应头: Headers {}
获取到的数据: {"id":12345,"name":"张三","email":"zhangsan@example.com"}
用户名: 张三
用户邮箱: zhangsan@example.com
看起来还好?那让我们看看使用 si-log 后的版本:
// 使用 si-log 的优雅方式
import { createLogger } from 'si-log';
// 创建一个带命名空间的 logger
const logger = createLogger('UserService');
// 配置日志格式
logger.configure({
level: 'debug',
formatter: {
template: '[{timestamp}] [{level}] {namespace} - {message}',
timestampFormat: 'HH:mm:ss.SSS'
}
});
function fetchUserData(userId) {
logger.debug('开始获取用户数据', { userId });
return fetch(`/api/user/${userId}`)
.then(response => {
logger.debug('收到响应', {
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries())
});
return response.json();
})
.then(data => {
logger.info('成功获取用户数据', {
userId: data.id,
username: data.name,
hasEmail: !!data.email
});
return data;
})
.catch(error => {
logger.error('获取用户数据失败', {
userId,
error: error.message,
stack: error.stack
});
throw error;
});
}
输出变成了这样:
[14:23:45.123] [DEBUG] UserService - 开始获取用户数据
{"userId": 12345}
[14:23:45.456] [DEBUG] UserService - 收到响应
{"status": 200, "statusText": "OK", "headers": {"content-type": "application/json"}}
[14:23:45.789] [INFO] UserService - 成功获取用户数据
{"userId": 12345, "username": "张三", "hasEmail": true}
看到了吗?信息更加结构化,阅读体验更好,而且还包含了时间戳和命名空间。更重要的是,我们可以很容易地控制哪些信息需要显示,哪些需要隐藏。
初识 si-log:核心功能全景扫描
si-log 的功能设计非常贴心,就像是专门为前端开发者量身定制的一样。让我们逐一看看它的核心功能。
日志分级:让信息井井有条
si-log 提供了六个日志级别,从低到高分别是:trace、debug、info、warn、error、silent。每个级别都有它的特定用途:
import { createLogger } from 'si-log';
const logger = createLogger('DemoApp');
// 最详细的追踪信息,通常用于调试复杂逻辑
logger.trace('进入函数 calculateTotalPrice', {
params: { items: [...], discount: 0.1 }
});
// 调试信息,开发时常用
logger.debug('当前购物车状态', {
itemsCount: 5,
totalPrice: 299.99
});
// 一般信息,记录重要操作
logger.info('用户成功添加商品到购物车', {
userId: 12345,
productId: 67890,
quantity: 2
});
// 警告信息,不会中断程序但需要注意
logger.warn('库存不足,部分商品可能无法购买', {
productId: 67890,
availableStock: 1,
requestedQuantity: 2
});
// 错误信息,程序运行出错
logger.error('支付处理失败', {
orderId: 'ORD-20241223-001',
errorCode: 'PAYMENT_FAILED',
errorMessage: '银行卡余额不足'
});
输出控制:让日志各就各位
si-log 支持多种输出方式,可以同时将日志输出到不同的目的地:
import { createLogger, ConsoleTransport, FileTransport, HttpTransport } from 'si-log';
const logger = createLogger('MultiOutputApp');
// 配置多个输出器
logger.configure({
transports: [
// 控制台输出,开发时用
new ConsoleTransport({
level: 'debug',
formatter: {
template: '{timestamp} [{level}] {message}',
colorize: true // 支持彩色输出
}
}),
// 文件输出,本地调试时保存日志
new FileTransport({
level: 'info',
filename: './logs/app.log',
maxSize: 10 * 1024 * 1024, // 10MB
maxFiles: 5,
format: 'json' // JSON 格式便于后续分析
}),
// HTTP 上报,生产环境收集日志
new HttpTransport({
level: 'error',
url: 'https://log-collector.example.com/api/logs',
headers: {
'Authorization': 'Bearer YOUR-API-KEY',
'Content-Type': 'application/json'
},
batchSize: 50, // 批量发送,减少请求次数
flushInterval: 30000 // 30秒刷新一次
})
]
});
格式化器:让日志颜值爆表
si-log 的格式化功能非常强大,支持各种自定义格式:
// 创建一个美观的格式化器
const customFormatter = {
template: '{timestamp} {levelIcon} {namespace} │ {message}',
timestampFormat: 'MM-DD HH:mm:ss',
levelIcons: {
trace: '🔍',
debug: '🐛',
info: 'ℹ️',
warn: '⚠️',
error: '❌'
},
colors: {
trace: 'gray',
debug: 'blue',
info: 'green',
warn: 'yellow',
error: 'red'
},
// 自定义格式化函数
formatMessage: (level, message, meta) => {
if (meta && meta.duration) {
return `${message} (耗时: ${meta.duration}ms)`;
}
return message;
}
};
// 使用自定义格式化器
const logger = createLogger('PrettyApp');
logger.configure({
formatter: customFormatter
});
// 输出示例
logger.info('页面加载完成', { duration: 1250 });
// 输出: 12-23 14:30:45 ℹ️ PrettyApp │ 页面加载完成 (耗时: 1250ms)
插件系统:让扩展变得简单
si-log 的插件系统设计得很巧妙,可以轻松地扩展功能:
// 创建一个性能监控插件
class PerformancePlugin {
constructor(options = {}) {
this.slowThreshold = options.slowThreshold || 1000; // 1秒
this.logger = null;
}
install(logger) {
this.logger = logger;
// 监听所有日志,记录处理时间
logger.on('log', (level, message, meta) => {
if (meta && meta.duration && meta.duration > this.slowThreshold) {
this.logger.warn('检测到慢操作', {
operation: message,
duration: meta.duration,
threshold: this.slowThreshold
});
}
});
}
}
// 创建一个错误统计插件
class ErrorStatsPlugin {
constructor() {
this.errorCount = 0;
this.errors = [];
}
install(logger) {
logger.on('log', (level, message, meta) => {
if (level === 'error') {
this.errorCount++;
this.errors.push({
message,
meta,
timestamp: new Date().toISOString()
});
// 每10个错误输出一次统计
if (this.errorCount % 10 === 0) {
logger.info('错误统计', {
totalErrors: this.errorCount,
recentErrors: this.errors.slice(-5)
});
}
}
});
}
getStats() {
return {
totalErrors: this.errorCount,
errors: this.errors
};
}
}
// 使用插件
const logger = createLogger('PluginApp');
logger.use(new PerformancePlugin({ slowThreshold: 500 }));
logger.use(new ErrorStatsPlugin());
// 现在日志系统就具备了性能监控和错误统计功能
安装与配置:三步走上专业日志之路
si-log 的安装和配置非常简单,几乎零门槛。让我们一步步来:
第一步:安装
# 使用 npm
npm install si-log
# 使用 yarn
yarn add si-log
# 使用 pnpm
pnpm add si-log
第二步:基础配置
// logger.js - 统一日志配置
import { createLogger, ConsoleTransport } from 'si-log';
// 创建全局 logger
export const logger = createLogger('MyApp');
// 根据环境配置不同的日志级别
const isDevelopment = process.env.NODE_ENV === 'development';
const isProduction = process.env.NODE_ENV === 'production';
// 基础配置
const baseConfig = {
level: isDevelopment ? 'debug' : 'info',
transports: [
new ConsoleTransport({
formatter: {
template: '[{timestamp}] [{level}] {namespace} - {message}',
timestampFormat: 'HH:mm:ss.SSS',
colorize: isDevelopment // 开发环境才彩色输出
}
})
]
};
logger.configure(baseConfig);
// 开发环境额外配置
if (isDevelopment) {
logger.debug('日志系统初始化完成', {
environment: 'development',
level: 'debug'
});
}
// 生产环境配置
if (isProduction) {
// 可以添加远程上报等功能
logger.info('应用启动', {
environment: 'production',
timestamp: new Date().toISOString()
});
}
第三步:在项目中使用
// main.js - 应用入口
import { logger } from './logger';
// 记录应用启动
logger.info('应用启动中...');
// 监听未处理的错误
window.addEventListener('error', (event) => {
logger.error('未处理的错误', {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error?.stack
});
});
// 监听未处理的 Promise 拒绝
window.addEventListener('unhandledrejection', (event) => {
logger.error('未处理的 Promise 拒绝', {
reason: event.reason,
promise: event.promise
});
});
// Vue 项目中的使用示例
import { createApp } from 'vue';
import App from './App.vue';
// 将 logger 挂载到全局
const app = createApp(App);
app.config.globalProperties.$logger = logger;
// 错误处理
app.config.errorHandler = (err, vm, info) => {
logger.error('Vue 错误', {
error: err.message,
component: vm?.$options.name,
info: info,
stack: err.stack
});
};
app.mount('#app');
与其他日志库的对比:si-log 的独特之处
市面上有不少优秀的前端日志库,比如 loglevel、winston-browser、log4javascript 等。si-log 相比它们有什么优势呢?让我们来个横向对比:
与 loglevel 的对比
loglevel 是一个非常轻量的日志库,只有 2KB 大小:
// loglevel 的使用方式
import log from 'loglevel';
log.setLevel('debug');
log.debug('调试信息');
log.info('一般信息');
log.warn('警告信息');
log.error('错误信息');
// si-log 的对比优势:
// 1. 支持更丰富的格式化选项
// 2. 支持多种输出方式
// 3. 支持插件扩展
// 4. 支持命名空间
// 5. 支持元数据传递
与 winston-browser 的对比
winston-browser 是著名日志库 winston 的浏览器版本:
// winston-browser 的使用
import winston from 'winston-browser';
const logger = winston.createLogger({
level: 'info',
transports: [
new winston.transports.Console()
]
});
// si-log 的优势:
// 1. 更小的体积(si-log 只有 8KB,winston-browser 约 20KB)
// 2. 更简单的 API 设计
// 3. 更好的浏览器兼容性
// 4. 更灵活的插件系统
si-log 的独特优势
- 体积与功能的完美平衡:只有 8KB,但功能齐全
- 零配置即用:合理的默认配置,开箱即用
- 渐进式采用:可以从简单的 console.log 替代开始,逐步使用高级功能
- 中文友好:错误提示和文档都有中文版本
- 性能优化:异步日志处理,不影响主线程
- 现代化设计:支持 ES6 模块、TypeScript、Tree Shaking
深入 si-log 的内部机制
要了解一个工具的真正实力,就得深入它的内部。si-log 的设计哲学是"简单但不简陋",让我们揭开它的神秘面纱。
日志级别的工作原理
si-log 的日志级别系统设计得很巧妙:
// si-log 内部的级别判断逻辑(简化版)
class Logger {
constructor(name) {
this.name = name;
this.level = levels.INFO; // 默认级别
this.transports = [];
}
// 核心:级别比较
shouldLog(level) {
return level.value >= this.level.value;
}
// 统一的日志处理方法
log(level, message, meta) {
if (!this.shouldLog(level)) {
return; // 级别不够,直接返回
}
const logEntry = {
level: level.name,
message,
meta,
namespace: this.name,
timestamp: new Date()
};
// 异步处理,避免阻塞
Promise.resolve().then(() => {
this.transports.forEach(transport => {
if (transport.shouldLog(level)) {
transport.log(logEntry);
}
});
});
}
}
// 使用示例
const logger = createLogger('TestApp');
logger.level = 'warn'; // 设置级别为 warn
logger.debug('这个不会显示');
logger.info('这个也不会显示');
logger.warn('这个会显示');
logger.error('这个也会显示');
Transport 的设计哲学
Transport 是 si-log 的核心概念之一,它决定了日志的去向:
// ConsoleTransport 的实现原理
class ConsoleTransport {
constructor(options = {}) {
this.level = options.level || 'debug';
this.formatter = options.formatter || new DefaultFormatter();
this.colorize = options.colorize !== false;
}
log(logEntry) {
const formatted = this.formatter.format(logEntry);
// 根据级别选择不同的 console 方法
const method = this.getConsoleMethod(logEntry.level);
if (this.colorize && typeof formatted === 'string') {
// 添加颜色
const colored = this.addColor(formatted, logEntry.level);
console[method](colored, logEntry.meta);
} else {
console[method](formatted, logEntry.meta);
}
}
getConsoleMethod(level) {
const methodMap = {
trace: 'trace',
debug: 'debug',
info: 'info',
warn: 'warn',
error: 'error'
};
return methodMap[level] || 'log';
}
}
// HttpTransport 的实现 - 支持批量发送
class HttpTransport {
constructor(options) {
this.level = options.level || 'error';
this.url = options.url;
this.headers = options.headers || {};
this.batchSize = options.batchSize || 50;
this.flushInterval = options.flushInterval || 30000;
this.queue = [];
// 定时刷新
this.startFlushTimer();
}
log(logEntry) {
this.queue.push(logEntry);
// 达到批次大小时立即发送
if (this.queue.length >= this.batchSize) {
this.flush();
}
}
async flush() {
if (this.queue.length === 0) return;
const batch = this.queue.splice(0); // 清空队列
const payload = {
logs: batch,
timestamp: new Date().toISOString(),
source: 'si-log'
};
try {
await fetch(this.url, {
method: 'POST',
headers: this.headers,
body: JSON.stringify(payload)
});
} catch (error) {
// 发送失败时,可以重试或保存到本地
console.error('日志发送失败:', error);
}
}
startFlushTimer() {
setInterval(() => {
this.flush();
}, this.flushInterval);
}
}
格式化器的魔法
格式化器是 si-log 最有趣的部分之一:
// 模板格式化器的实现
class TemplateFormatter {
constructor(options = {}) {
this.template = options.template || '{timestamp} [{level}] {message}';
this.timestampFormat = options.timestampFormat || 'ISO';
this.levelIcons = options.levelIcons || {};
this.colors = options.colors || {};
}
format(logEntry) {
let result = this.template;
// 替换模板变量
const variables = {
timestamp: this.formatTimestamp(logEntry.timestamp),
level: logEntry.level.toUpperCase(),
levelIcon: this.levelIcons[logEntry.level] || '',
namespace: logEntry.namespace,
message: logEntry.message
};
Object.keys(variables).forEach(key => {
result = result.replace(new RegExp(`{${key}}`, 'g'), variables[key]);
});
return result;
}
formatTimestamp(date) {
switch (this.timestampFormat) {
case 'ISO':
return date.toISOString();
case 'short':
return date.toLocaleTimeString();
case 'custom':
// 自定义格式,比如:MM-DD HH:mm:ss
return this.customFormat(date);
default:
return date.toString();
}
}
customFormat(date) {
const MM = String(date.getMonth() + 1).padStart(2, '0');
const DD = String(date.getDate()).padStart(2, '0');
const HH = String(date.getHours()).padStart(2, '0');
const mm = String(date.getMinutes()).padStart(2, '0');
const ss = String(date.getSeconds()).padStart(2, '0');
return `${MM}-${DD} ${HH}:${mm}:${ss}`;
}
}
// JSON 格式化器 - 适合机器读取
class JsonFormatter {
format(logEntry) {
return JSON.stringify({
timestamp: logEntry.timestamp.toISOString(),
level: logEntry.level,
namespace: logEntry.namespace,
message: logEntry.message,
meta: logEntry.meta
});
}
}
插件系统的奥秘
si-log 的插件系统采用了一种优雅的设计:
// 插件管理器的实现
class PluginManager {
constructor() {
this.plugins = [];
this.hooks = {
beforeLog: [],
afterLog: [],
error: []
};
}
use(plugin) {
if (typeof plugin.install === 'function') {
plugin.install(this);
}
this.plugins.push(plugin);
}
// 触发钩子
emit(hookName, ...args) {
const hooks = this.hooks[hookName] || [];
hooks.forEach(hook => {
try {
hook(...args);
} catch (error) {
console.error('插件钩子执行失败:', error);
}
});
}
// 注册钩子
on(hookName, callback) {
if (!this.hooks[hookName]) {
this.hooks[hookName] = [];
}
this.hooks[hookName].push(callback);
}
}
// 使用示例 - 创建一个性能监控插件
class PerformanceMonitor {
install(pluginManager) {
const timing = {};
// 监听日志事件
pluginManager.on('beforeLog', (logEntry) => {
timing[logEntry.id] = performance.now();
});
pluginManager.on('afterLog', (logEntry) => {
const duration = performance.now() - timing[logEntry.id];
delete timing[logEntry.id];
if (duration > 10) { // 超过10ms的日志记录
console.warn(`慢日志警告: ${logEntry.message} 花费了 ${duration}ms`);
}
});
}
}
si-log 的优势与踩坑指南
任何工具都有它的闪光点和不完美之处,si-log 也不例外。让我们客观地分析一下。
轻量与功能的完美平衡
si-log 最大的优点就是找到了一个绝佳的平衡点:
// 让我们看看 si-log 的体积对比
console.table({
'si-log': {
size: '8KB',
features: '分级、格式化、插件、多输出',
typescript: '✅',
treeShaking: '✅'
},
'loglevel': {
size: '2KB',
features: '仅分级',
typescript: '✅',
treeShaking: '✅'
},
'winston-browser': {
size: '20KB',
features: '分级、格式化、多输出',
typescript: '✅',
treeShaking: '❌'
},
'log4javascript': {
size: '35KB',
features: '完整日志框架',
typescript: '❌',
treeShaking: '❌'
}
});
浏览器兼容性实测
si-log 在浏览器兼容性方面做得相当不错:
// 兼容性测试代码
function testCompatibility() {
const results = {};
// 测试基本功能
try {
const logger = createLogger('compatibility-test');
logger.info('兼容性测试');
results.basic = '✅ 通过';
} catch (e) {
results.basic = '❌ 失败: ' + e.message;
}
// 测试高级功能
try {
const logger = createLogger('advanced-test');
logger.configure({
transports: [
new ConsoleTransport({
formatter: {
template: '[{timestamp}] {message}',
colorize: true
}
})
]
});
logger.debug('高级功能测试', { data: 'test' });
results.advanced = '✅ 通过';
} catch (e) {
results.advanced = '❌ 失败: ' + e.message;
}
// 测试 Promise 支持
try {
const logger = createLogger('promise-test');
Promise.resolve().then(() => {
logger.info('Promise 中的日志');
});
results.promise = '✅ 通过';
} catch (e) {
results.promise = '❌ 失败: ' + e.message;
}
return results;
}
// 实际测试结果
const compatibilityResults = {
'Chrome 90+': { basic: '✅', advanced: '✅', promise: '✅', async: '✅' },
'Firefox 88+': { basic: '✅', advanced: '✅', promise: '✅', async: '✅' },
'Safari 14+': { basic: '✅', advanced: '✅', promise: '✅', async: '✅' },
'Edge 90+': { basic: '✅', advanced: '✅', promise: '✅', async: '✅' },
'IE 11': { basic: '✅', advanced: '⚠️', promise: '❌', async: '❌' }
};
性能影响评估
很多人担心日志会影响页面性能,让我们用数据说话:
// 性能测试
function performanceTest() {
const iterations = 10000;
console.time('console.log 性能测试');
for (let i = 0; i < iterations; i++) {
console.log('测试日志', i);
}
console.timeEnd('console.log 性能测试');
console.time('si-log 性能测试');
const logger = createLogger('perf-test');
for (let i = 0; i < iterations; i++) {
logger.info('测试日志', i);
}
console.timeEnd('si-log 性能测试');
// 异步日志测试
console.time('si-log 异步性能测试');
const asyncLogger = createLogger('async-perf-test');
const promises = [];
for (let i = 0; i < iterations; i++) {
promises.push(
new Promise(resolve => {
setTimeout(() => {
asyncLogger.info('异步测试日志', i);
resolve();
}, 0);
})
);
}
Promise.all(promises).then(() => {
console.timeEnd('si-log 异步性能测试');
});
}
// 内存占用测试
function memoryTest() {
const logger = createLogger('memory-test');
// 记录初始内存
const initialMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
// 记录大量日志
for (let i = 0; i < 1000; i++) {
logger.info('内存测试', {
index: i,
data: new Array(100).fill('测试数据').join(''),
timestamp: Date.now()
});
}
// 记录结束内存
const finalMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
console.log('内存使用增加:', finalMemory - initialMemory, 'bytes');
}
调试友好度分析
si-log 在调试友好性方面做了很多工作:
// Source Map 支持
const logger = createLogger('DebugTest');
// 配置错误栈追踪
logger.configure({
captureStackTrace: true, // 捕获调用栈
stackTraceDepth: 5, // 栈深度
showSourceLocation: true // 显示源码位置
});
function problematicFunction() {
logger.debug('进入问题函数');
try {
// 模拟一个错误
const result = JSON.parse('invalid json');
} catch (error) {
logger.error('解析JSON失败', {
error: error.message,
stack: error.stack,
sourceLocation: true // 请求显示源码位置
});
}
}
// 输出示例:
// [14:30:45.123] [ERROR] DebugTest - 解析JSON失败
// 错误位置: http://localhost:3000/src/utils/parser.js:45:9
// 调用栈:
// at problematicFunction (parser.js:45:9)
// at initApp (app.js:23:5)
// at HTMLDocument.<anonymous> (main.js:10:3)
实战案例:真实项目中的 si-log 应用
理论说得再多,不如来点实际的。让我们看看在真实项目中,si-log 是如何发挥作用的。
开发环境 vs 生产环境的日志策略
// logger.config.js - 环境特定的日志配置
import { createLogger, ConsoleTransport, HttpTransport } from 'si-log';
const environment = process.env.NODE_ENV || 'development';
// 基础配置
const createAppLogger = (name) => {
const logger = createLogger(name);
if (environment === 'development') {
// 开发环境配置
logger.configure({
level: 'debug',
transports: [
new ConsoleTransport({
formatter: {
template: '🚀 [{timestamp}] [{level}] {namespace} - {message}',
timestampFormat: 'HH:mm:ss.SSS',
colorize: true,
levelIcons: {
trace: '🔍',
debug: '🐛',
info: '💡',
warn: '⚠️',
error: '🔥'
}
}
})
]
});
// 开发环境额外功能
logger.debug('开发模式已启用', {
features: ['详细日志', '彩色输出', '源码映射']
});
} else if (environment === 'production') {
// 生产环境配置
logger.configure({
level: 'info',
transports: [
new ConsoleTransport({
formatter: {
template: '[{timestamp}] [{level}] {message}',
timestampFormat: 'YYYY-MM-DD HH:mm:ss'
}
}),
new HttpTransport({
level: 'error',
url: 'https://log-collector.yourapp.com/api/logs',
headers: {
'Authorization': `Bearer ${process.env.LOG_API_KEY}`,
'X-App-Version': process.env.APP_VERSION
},
batchSize: 20,
flushInterval: 10000
})
]
});
// 生产环境记录启动信息
logger.info('应用启动', {
version: process.env.APP_VERSION,
buildTime: process.env.BUILD_TIME,
environment: 'production'
});
}
return logger;
};
// 使用示例
export const appLogger = createAppLogger('MyApp');
export const apiLogger = createAppLogger('API');
export const uiLogger = createAppLogger('UI');
Vue 项目集成方案
// vue-logger-plugin.js
import { createLogger, ConsoleTransport } from 'si-log';
export default {
install(app, options = {}) {
// 创建 Vue 专用的 logger
const logger = createLogger('VueApp');
// 配置
logger.configure({
level: options.level || 'info',
transports: [
new ConsoleTransport({
formatter: {
template: '🍃 [{timestamp}] [{level}] {namespace}:{component} - {message}',
timestampFormat: 'HH:mm:ss.SSS',
colorize: true
}
})
]
});
// 全局属性
app.config.globalProperties.$logger = logger;
// 提供组合式 API
app.provide('logger', logger);
// 错误处理
app.config.errorHandler = (err, vm, info) => {
logger.error('Vue 运行时错误', {
error: err.message,
component: vm?.$options.name || 'Unknown',
info: info,
stack: err.stack,
url: window.location.href,
userAgent: navigator.userAgent
});
};
// 警告处理
app.config.warnHandler = (msg, vm, trace) => {
logger.warn('Vue 警告', {
message: msg,
component: vm?.$options.name || 'Unknown',
trace: trace
});
};
// 路由错误处理(如果使用 vue-router)
if (app.config.globalProperties.$router) {
app.config.globalProperties.$router.onError((error) => {
logger.error('路由错误', {
error: error.message,
stack: error.stack,
currentRoute: app.config.globalProperties.$route.fullPath
});
});
}
}
};
// 在 Vue 组件中使用
<template>
<div>
<button @click="handleClick">测试日志</button>
</div>
</template>
<script>
import { inject } from 'vue';
export default {
name: 'LoggerDemo',
setup() {
const logger = inject('logger');
const handleClick = () => {
logger.debug('按钮被点击', {
component: 'LoggerDemo',
timestamp: Date.now()
});
// 模拟一个错误
try {
throw new Error('测试错误');
} catch (error) {
logger.error('捕获到测试错误', {
error: error.message,
component: 'LoggerDemo'
});
}
};
return { handleClick };
}
};
</script>
React 项目集成方案
// logger-context.js
import React, { createContext, useContext, useEffect } from 'react';
import { createLogger, ConsoleTransport } from 'si-log';
// 创建 Logger Context
const LoggerContext = createContext();
// Logger Provider
export const LoggerProvider = ({ children, config = {} }) => {
const logger = createLogger('ReactApp');
useEffect(() => {
logger.configure({
level: config.level || 'info',
transports: [
new ConsoleTransport({
formatter: {
template: '⚛️ [{timestamp}] [{level}] {namespace}:{component} - {message}',
timestampFormat: 'HH:mm:ss.SSS',
colorize: true
}
})
]
});
}, []);
// 错误边界
useEffect(() => {
const handleError = (event) => {
logger.error('全局错误', {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error?.stack,
url: window.location.href
});
};
const handleUnhandledRejection = (event) => {
logger.error('未处理的 Promise 拒绝', {
reason: event.reason?.toString(),
url: window.location.href
});
};
window.addEventListener('error', handleError);
window.addEventListener('unhandledrejection', handleUnhandledRejection);
return () => {
window.removeEventListener('error', handleError);
window.removeEventListener('unhandledrejection', handleUnhandledRejection);
};
}, [logger]);
return (
<LoggerContext.Provider value={logger}>
{children}
</LoggerContext.Provider>
);
};
// Hook for using logger
export const useLogger = (componentName) => {
const logger = useContext(LoggerContext);
return {
debug: (message, meta) => logger.debug(message, { ...meta, component: componentName }),
info: (message, meta) => logger.info(message, { ...meta, component: componentName }),
warn: (message, meta) => logger.warn(message, { ...meta, component: componentName }),
error: (message, meta) => logger.error(message, { ...meta, component: componentName })
};
};
// 使用示例
import React from 'react';
import { useLogger } from './logger-context';
function UserProfile({ userId }) {
const logger = useLogger('UserProfile');
useEffect(() => {
logger.debug('组件挂载', { userId });
fetchUserData(userId)
.then(user => {
logger.info('用户数据加载成功', {
userId,
username: user.name
});
})
.catch(error => {
logger.error('用户数据加载失败', {
userId,
error: error.message
});
});
return () => {
logger.debug('组件卸载', { userId });
};
}, [userId]);
return <div>用户资料页面</div>;
}
配合 Sentry 的错误监控方案
// sentry-logger-integration.js
import * as Sentry from '@sentry/browser';
import { createLogger, ConsoleTransport } from 'si-log';
// 创建 Sentry 集成插件
class SentryIntegration {
constructor(options = {}) {
this.options = options;
this.sentry = Sentry;
}
install(logger) {
this.logger = logger;
// 监听错误级别的日志
logger.on('log', (level, message, meta) => {
if (level === 'error' && this.shouldSendToSentry(meta)) {
this.sendToSentry(level, message, meta);
}
});
}
shouldSendToSentry(meta) {
// 可以根据 meta 中的标记决定是否发送到 Sentry
return meta?.sendToSentry !== false;
}
sendToSentry(level, message, meta) {
const eventId = this.sentry.captureException(new Error(message), {
tags: {
logger: true,
level: level
},
extra: {
...meta,
originalMessage: message,
timestamp: new Date().toISOString()
},
fingerprint: [
'{{ default }}',
message,
meta?.errorCode || 'unknown'
]
});
// 记录 Sentry 事件 ID
this.logger.debug('错误已发送到 Sentry', {
sentryEventId: eventId,
message
});
}
}
// 统一的错误处理
export function createErrorHandler() {
const logger = createLogger('ErrorHandler');
// 基础配置
logger.configure({
level: 'info',
transports: [
new ConsoleTransport({
formatter: {
template: '🚨 [{timestamp}] [{level}] {message}',
colorize: true
}
})
]
});
// 集成 Sentry
logger.use(new SentryIntegration());
return {
handleError: (error, context = {}) => {
logger.error('应用错误', {
error: error.message,
stack: error.stack,
url: window.location.href,
userAgent: navigator.userAgent,
...context
});
},
handleApiError: (error, request = {}) => {
logger.error('API 错误', {
message: error.message,
status: error.response?.status,
statusText: error.response?.statusText,
url: request.url,
method: request.method,
timestamp: new Date().toISOString()
});
},
handleBusinessError: (errorCode, message, data = {}) => {
logger.warn('业务错误', {
errorCode,
message,
data,
userId: getCurrentUserId()
});
}
};
}
// 使用示例
const errorHandler = createErrorHandler();
// API 调用
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
errorHandler.handleApiError(error, {
url: `/api/users/${userId}`,
method: 'GET'
});
throw error;
}
}
// 业务逻辑错误
function validateOrder(order) {
if (!order.items || order.items.length === 0) {
errorHandler.handleBusinessError('EMPTY_ORDER', '订单不能为空', {
orderId: order.id
});
return false;
}
if (order.totalAmount <= 0) {
errorHandler.handleBusinessError('INVALID_AMOUNT', '订单金额必须大于0', {
orderId: order.id,
amount: order.totalAmount
});
return false;
}
return true;
}
移动端 H5 的特殊处理
移动端 H5 环境有一些特殊考虑:
// mobile-logger.js
import { createLogger, ConsoleTransport } from 'si-log';
// 移动端检测
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
const isWeChat = /MicroMessenger/i.test(navigator.userAgent);
// 移动端专用 logger
export const mobileLogger = createLogger('MobileApp');
// 移动端配置
mobileLogger.configure({
level: 'info', // 移动端用 info 级别,减少日志量
transports: [
new ConsoleTransport({
formatter: {
template: '📱 [{timestamp}] [{level}] {message}',
timestampFormat: 'HH:mm:ss'
}
})
]
});
// 网络状况检测
function getNetworkStatus() {
if ('connection' in navigator) {
const connection = navigator.connection;
return {
effectiveType: connection.effectiveType, // 'slow-2g', '2g', '3g', '4g'
saveData: connection.saveData // 是否开启省流模式
};
}
return null;
}
// 移动端日志上报策略
class MobileLogStrategy {
constructor(logger) {
this.logger = logger;
this.queue = [];
this.maxQueueSize = 50; // 移动端队列小一些
this.maxBatchSize = 10; // 批次也小一些
}
log(level, message, meta) {
const entry = {
level,
message,
meta: {
...meta,
network: getNetworkStatus(),
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent.substring(0, 100) // 截取前100字符
}
};
this.queue.push(entry);
// 根据网络状况决定是否立即发送
const networkStatus = getNetworkStatus();
if (networkStatus?.effectiveType === '4g' && !networkStatus?.saveData) {
this.flush();
} else if (this.queue.length >= this.maxBatchSize) {
this.flush();
}
}
async flush() {
if (this.queue.length === 0) return;
const batch = this.queue.splice(0, this.maxBatchSize);
// 检查是否 WiFi
const isWifi = navigator.connection?.type === 'wifi';
if (!isWifi && batch.length < 5) {
// 非 WiFi 环境且日志较少,延迟发送
setTimeout(() => this.flush(), 60000); // 1分钟后发送
return;
}
try {
// 压缩数据
const compressed = await this.compress(batch);
await fetch('/api/mobile-logs', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Encoding': 'gzip'
},
body: JSON.stringify(compressed),
keepalive: true // 页面卸载时也能发送
});
} catch (error) {
// 发送失败,重新加入队列
this.queue.unshift(...batch);
this.logger.error('日志发送失败', { error: error.message });
}
}
compress(data) {
// 简单的数据压缩
return {
logs: data,
compressed: true,
count: data.length
};
}
}
// 使用示例
const strategy = new MobileLogStrategy(mobileLogger);
// 监听页面生命周期
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
// 页面隐藏时,立即发送所有日志
strategy.flush();
}
});
// 监听网络变化
if ('connection' in navigator) {
navigator.connection.addEventListener('change', () => {
mobileLogger.info('网络状态变化', getNetworkStatus());
});
}
常见问题与解决方案
使用 si-log 过程中,可能会遇到一些问题。让我们看看最常见的"翻车"现场和自救指南。
日志没输出?检查这些设置
// 问题排查工具函数
function debugLogger(logger) {
console.log('=== Logger 调试信息 ===');
console.log('Logger 实例:', logger);
console.log('当前级别:', logger.level);
console.log('Transport 数量:', logger.transports?.length || 0);
if (logger.transports) {
logger.transports.forEach((transport, index) => {
console.log(`Transport ${index}:`, {
type: transport.constructor.name,
level: transport.level,
enabled: transport.enabled !== false
});
});
}
// 测试各个级别
const levels = ['trace', 'debug', 'info', 'warn', 'error'];
levels.forEach(level => {
const shouldLog = logger.shouldLog(level);
console.log(`级别 ${level}: ${shouldLog ? '✅ 会输出' : '❌ 不会输出'}`);
});
}
// 常见问题和解决方案
const commonIssues = {
'日志完全不输出': {
causes: [
'Logger 级别设置过高',
'Transport 被禁用',
'浏览器控制台过滤设置',
'代码执行顺序问题'
],
solutions: [
`// 检查级别设置
logger.level = 'debug'; // 确保级别足够低
// 逐个级别测试
logger.trace('trace 测试');
logger.debug('debug 测试');
logger.info('info 测试');`,
`// 检查 Transport 状态
logger.transports.forEach(t => {
t.enabled = true; // 确保启用
});`,
`// 确保 logger 已正确初始化
console.log('Logger 实例:', logger);
console.log('Logger 方法:', Object.keys(logger));`
]
},
'部分日志不输出': {
causes: ['级别过滤', 'Transport 级别限制', '异步问题'],
solutions: [
`// 检查具体级别的设置
logger.setLevel('debug', 'specific-namespace');`,
`// 检查 Transport 的级别限制
transport.level = 'trace'; // 确保 Transport 级别足够`
]
}
};
生产环境日志消失之谜
// 生产环境问题排查
function checkProductionIssues() {
// 检查是否是 Tree Shaking 问题
if (process.env.NODE_ENV === 'production') {
console.log('=== 生产环境检查 ===');
// 确保 logger 被正确使用
const logger = createLogger('ProductionCheck');
// 测试日志
logger.info('生产环境日志测试');
// 检查构建配置
console.log('构建配置检查:', {
NODE_ENV: process.env.NODE_ENV,
// 确保没有删除日志代码的配置
});
}
}
// webpack 配置注意事项
const webpackConfig = {
mode: 'production',
optimization: {
usedExports: true, // tree shaking
sideEffects: false, // 确保不会删除日志代码
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
// 不要删除 console.log(虽然我们用 si-log,但这是一个好习惯)
drop_console: false,
// 不要删除 debugger 语句
drop_debugger: false
}
}
})
]
}
};
// 确保 si-log 不会被 tree shaking
// 在 package.json 中
{
"sideEffects": false,
"module": "es/index.js",
"main": "lib/index.js"
}
中文乱码和时间格式问题
// 编码问题解决方案
const encodingFix = {
// 问题:中文显示为乱码
charsetProblem: {
solution: `
// 确保 HTML 文件设置正确的编码
<meta charset="UTF-8">
// 确保服务器响应头正确
Content-Type: text/html; charset=UTF-8
// si-log 配置
logger.configure({
formatter: {
// 使用 UTF-8 编码
encoding: 'utf-8'
}
})`
},
// 时间格式问题
timeFormatProblem: {
solution: `
// 统一时间格式配置
const timeFormatter = {
// 本地时间格式
local: 'YYYY-MM-DD HH:mm:ss',
// ISO 标准时间
iso: 'YYYY-MM-DDTHH:mm:ss.sssZ',
// 时间戳
timestamp: 'x',
// 自定义格式
custom: 'MM月DD日 HH:mm'
};
// 根据用户地区自动选择
const getLocaleFormat = () => {
const locale = navigator.language;
if (locale.startsWith('zh')) {
return timeFormatter.custom; // 中文格式
}
return timeFormatter.iso; // 国际格式
};
logger.configure({
formatter: {
timestampFormat: getLocaleFormat()
}
});`
}
};
插件加载失败的解决方案
// 插件系统问题排查
class PluginDebugger {
constructor(logger) {
this.logger = logger;
this.loadedPlugins = [];
}
async loadPlugin(pluginPath) {
try {
this.logger.debug('正在加载插件', { path: pluginPath });
// 动态导入插件
const module = await import(pluginPath);
const PluginClass = module.default || module;
if (typeof PluginClass !== 'function') {
throw new Error('插件必须是一个类或构造函数');
}
const plugin = new PluginClass();
// 检查插件是否实现了必要的方法
if (typeof plugin.install !== 'function') {
throw new Error('插件必须实现 install 方法');
}
// 安装插件
this.logger.use(plugin);
this.loadedPlugins.push(plugin);
this.logger.info('插件加载成功', {
plugin: plugin.constructor.name,
path: pluginPath
});
} catch (error) {
this.logger.error('插件加载失败', {
path: pluginPath,
error: error.message,
stack: error.stack
});
}
}
// 检查插件依赖
checkDependencies(plugin) {
const dependencies = plugin.dependencies || [];
dependencies.forEach(dep => {
if (!this.isPluginLoaded(dep)) {
this.logger.warn('插件依赖未满足', {
plugin: plugin.constructor.name,
dependency: dep
});
}
});
}
isPluginLoaded(pluginName) {
return this.loadedPlugins.some(p =>
p.constructor.name === pluginName
);
}
}
// 模块化加载示例
const pluginLoader = {
// 核心插件 - 必须加载
core: [
'si-log/plugins/formatter',
'si-log/plugins/transport'
],
// 可选插件
optional: {
analytics: 'si-log/plugins/analytics',
performance: 'si-log/plugins/performance',
sentry: 'si-log/plugins/sentry'
},
// 按需加载
async load(type, name) {
const debugger = new PluginDebugger(logger);
if (type === 'core') {
// 加载所有核心插件
for (const plugin of this.core) {
await debugger.loadPlugin(plugin);
}
} else if (type === 'optional' && this.optional[name]) {
await debugger.loadPlugin(this.optional[name]);
}
}
};
提升效率的小技巧
掌握一些使用技巧,能让 si-log 成为你的得力助手。
命名空间的艺术
// 命名空间最佳实践
const namespacePatterns = {
// 按模块划分
modular: {
'auth': '用户认证相关',
'api': 'API 调用相关',
'ui': 'UI 交互相关',
'store': '状态管理相关'
},
// 按功能划分
functional: {
'performance': '性能监控',
'error': '错误处理',
'analytics': '数据分析',
'debug': '调试信息'
},
// 按层级划分
layered: {
'app': '应用层',
'service': '服务层',
'component': '组件层',
'util': '工具层'
}
};
// 命名空间管理器
class NamespaceManager {
constructor() {
this.loggers = new Map();
this.configs = new Map();
}
// 创建带命名空间的 logger
create(names, config = {}) {
const namespaces = Array.isArray(names) ? names : [names];
const key = namespaces.join(':');
if (this.loggers.has(key)) {
return this.loggers.get(key);
}
const logger = createLogger(key);
logger.configure(config);
this.loggers.set(key, logger);
this.configs.set(key, config);
return logger;
}
// 批量创建
createGroup(groupName, subNamespaces, baseConfig = {}) {
const group = {};
subNamespaces.forEach(subName => {
const fullName = `${groupName}:${subName}`;
group[subName] = this.create(fullName, baseConfig);
});
return group;
}
// 动态调整级别
setLevel(pattern, level) {
this.loggers.forEach((logger, key) => {
if (key.includes(pattern)) {
logger.level = level;
}
});
}
}
// 使用示例
const nsManager = new NamespaceManager();
// 创建模块化的 loggers
const authLoggers = nsManager.createGroup('auth', [
'login',
'logout',
'register',
'password'
]);
// 使用
authLoggers.login.info('用户登录成功', { userId: 12345 });
authLoggers.password.debug('密码验证中', { userId: 12345 });
// 动态开启调试模式
nsManager.setLevel('auth', 'debug'); // 所有 auth 相关的日志都开启 debug
动态日志级别调整
// 动态日志级别控制器
class LogLevelController {
constructor() {
this.loggers = new Map();
this.originalLevels = new Map();
}
register(logger, name) {
this.loggers.set(name, logger);
this.originalLevels.set(name, logger.level);
}
// 临时开启调试模式
enableDebug(pattern, duration = 300000) { // 默认5分钟
this.loggers.forEach((logger, name) => {
if (name.includes(pattern)) {
logger.level = 'debug';
logger.debug('调试模式已开启', { duration: `${duration/1000}秒` });
}
});
// 自动关闭
setTimeout(() => {
this.disableDebug(pattern);
}, duration);
}
disableDebug(pattern) {
this.loggers.forEach((logger, name) => {
if (name.includes(pattern)) {
const originalLevel = this.originalLevels.get(name);
logger.level = originalLevel;
logger.debug('调试模式已关闭');
}
});
}
// 基于 URL 参数控制
handleUrlParams() {
const urlParams = new URLSearchParams(window.location.search);
const debugParam = urlParams.get('debug');
if (debugParam) {
const patterns = debugParam.split(',');
patterns.forEach(pattern => {
this.enableDebug(pattern.trim());
});
}
}
}
// 使用示例
const levelController = new LogLevelController();
// 注册 loggers
levelController.register(apiLogger, 'api');
levelController.register(uiLogger, 'ui');
levelController.register(authLogger, 'auth');
// 通过控制台控制
window.logControl = levelController;
// 在控制台中:
// logControl.enableDebug('api') - 开启 API 调试
// logControl.disableDebug('api') - 关闭 API 调试
// 通过 URL 参数控制
// http://yourapp.com?debug=api,auth - 自动开启 API 和认证调试
levelController.handleUrlParams();
封装统一日志入口
// 统一日志入口 - 避免团队成员各打各的
class UnifiedLogger {
constructor() {
this.loggers = {
app: createLogger('App'),
api: createLogger('API'),
ui: createLogger('UI'),
performance: createLogger('Performance'),
error: createLogger('Error')
};
// 统一配置
Object.values(this.loggers).forEach(logger => {
logger.configure({
level: 'info',
transports: [
new ConsoleTransport({
formatter: {
template: '[{timestamp}] [{level}] {namespace} - {message}',
timestampFormat: 'HH:mm:ss.SSS'
}
})
]
});
});
}
// 应用日志
app(message, meta) {
this.loggers.app.info(message, meta);
}
// API 日志
api(request, response, duration) {
this.loggers.api.info(`${request.method} ${request.url}`, {
status: response.status,
duration: `${duration}ms`,
request: {
headers: request.headers,
body: request.body
},
response: {
headers: response.headers,
size: JSON.stringify(response.data).length
}
});
}
// UI 交互日志
ui(action, element, data = {}) {
this.loggers.ui.info(`用户交互: ${action}`, {
element: element.tagName + (element.id ? `#${element.id}` : ''),
text: element.textContent?.substring(0, 50),
coordinates: data.coordinates,
timestamp: Date.now()
});
}
// 性能日志
performance(metric, value, details = {}) {
this.loggers.performance.info(`性能指标: ${metric}`, {
value,
unit: details.unit || 'ms',
threshold: details.threshold,
exceeded: details.threshold && value > details.threshold,
url: window.location.href
});
}
// 错误日志
error(error, context = {}) {
this.loggers.error.error('应用错误', {
message: error.message,
stack: error.stack,
context,
url: window.location.href,
userAgent: navigator.userAgent.substring(0, 100)
});
}
}
// 创建全局实例
export const unifiedLogger = new UnifiedLogger();
// 使用示例
// 在 API 拦截器中
axios.interceptors.request.use(config => {
config.metadata = { startTime: Date.now() };
return config;
});
axios.interceptors.response.use(
response => {
const duration = Date.now() - response.config.metadata.startTime;
unifiedLogger.api(response.config, response, duration);
return response;
},
error => {
unifiedLogger.error(error, { type: 'API_ERROR' });
return Promise.reject(error);
}
);
// 在 UI 事件中
document.addEventListener('click', (event) => {
const element = event.target;
const action = element.dataset.action || 'click';
unifiedLogger.ui(action, element, {
coordinates: { x: event.clientX, y: event.clientY }
});
});
// 性能监控
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (entry.entryType === 'navigation') {
unifiedLogger.performance('页面加载时间', entry.loadEventEnd - entry.fetchStart);
} else if (entry.entryType === 'paint') {
unifiedLogger.performance(entry.name, entry.startTime);
}
});
});
observer.observe({ entryTypes: ['navigation', 'paint'] });
}
VS Code 集成技巧
// .vscode/settings.json - VS Code 配置
{
"search.exclude": {
"**/node_modules": true,
"**/logs": true
},
"files.associations": {
"*.log": "log"
},
"emmet.includeLanguages": {
"javascript": "javascriptreact"
}
}
// 日志高亮配置
// .vscode/syntaxes/log.tmLanguage.json
{
"scopeName": "source.log",
"patterns": [
{
"match": "\\[ERROR\\]",
"name": "invalid.illegal"
},
{
"match": "\\[WARN\\]",
"name": "invalid.deprecated"
},
{
"match": "\\[INFO\\]",
"name": "keyword.control"
},
{
"match": "\\[DEBUG\\]",
"name": "comment"
}
]
}
// 自定义代码片段
// .vscode/snippets/javascript.json
{
"Logger Import": {
"prefix": "silog-import",
"body": [
"import { createLogger } from 'si-log';",
"const logger = createLogger('${1:moduleName}');",
""
],
"description": "导入 si-log"
},
"Logger Debug": {
"prefix": "silog-debug",
"body": [
"logger.debug('${1:message}', { ${2:meta} });"
],
"description": "Debug 日志"
},
"Logger Error": {
"prefix": "silog-error",
"body": [
"logger.error('${1:error message}', { ",
" error: ${2:error}.message,",
" stack: ${2:error}.stack",
"});"
],
"description": "Error 日志"
}
}
// 日志跳转支持
// 在日志中添加源码位置
class SourceLocationPlugin {
install(logger) {
logger.on('beforeLog', (logEntry) => {
// 获取调用栈
const stack = new Error().stack;
const lines = stack.split('\n');
// 找到调用者的位置(通常是第4行)
const callerLine = lines[4];
const match = callerLine.match(/at .* \((.+):(\d+):(\d+)\)/);
if (match) {
const [, filename, line, column] = match;
logEntry.meta = {
...logEntry.meta,
source: {
file: filename.split('/').pop(),
line: parseInt(line),
column: parseInt(column)
}
};
}
});
}
}
// 使用
const logger = createLogger('VSCodeIntegration');
logger.use(new SourceLocationPlugin());
// 输出示例:
// [14:30:45.123] [INFO] VSCodeIntegration - 用户操作
// {
// action: 'click',
// source: { file: 'UserComponent.js', line: 45, column: 12 }
// }
// 在 VS Code 中 Ctrl+点击文件名即可跳转到对应位置
总结
si-log 就像是前端开发中的瑞士军刀,简单实用,功能丰富。从最初简单的 console.log 替代品,到现在支持复杂的企业级日志管理方案,它证明了"小而美"的工具也能发挥巨大的作用。
通过本文的介绍,我们了解了:
- 为什么前端需要专业的日志管理
- si-log 的核心功能和设计理念
- 在各种项目中的实际应用方案
- 常见问题的解决方案
- 提升开发效率的实用技巧
日志管理不是可有可无的奢侈品,而是现代前端开发的基础设施。选择合适的工具,建立良好的日志规范,能让我们的开发工作事半功倍。
si-log 或许不是最完美的解决方案,但它的简单、实用、灵活,足以应对大多数前端项目的日志需求。从简单的调试输出到复杂的企业级日志收集,si-log 都能胜任。
记住,好的日志系统就像好的保险——平时可能感觉不到它的存在,但在关键时刻,它能帮我们快速定位问题,减少损失,提升用户体验。所以,从今天开始,让我们告别凌乱的 console.log,拥抱更专业的日志管理方案吧!
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!
| 专栏系列(点击解锁) | 学习路线(点击解锁) | 知识定位 |
|---|---|---|
| 《微信小程序相关博客》 | 持续更新中~ | 结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等 |
| 《AIGC相关博客》 | 持续更新中~ | AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结 |
| 《HTML网站开发相关》 | 《前端基础入门三大核心之html相关博客》 | 前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识 |
| 《前端基础入门三大核心之JS相关博客》 | 前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。 通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心 |
|
| 《前端基础入门三大核心之CSS相关博客》 | 介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页 | |
| 《canvas绘图相关博客》 | Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化 | |
| 《Vue实战相关博客》 | 持续更新中~ | 详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅 |
| 《python相关博客》 | 持续更新中~ | Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具 |
| 《sql数据库相关博客》 | 持续更新中~ | SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能 |
| 《算法系列相关博客》 | 持续更新中~ | 算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维 |
| 《IT信息技术相关博客》 | 持续更新中~ | 作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识 |
| 《信息化人员基础技能知识相关博客》 | 无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方 | |
| 《信息化技能面试宝典相关博客》 | 涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面 | |
| 《前端开发习惯与小技巧相关博客》 | 持续更新中~ | 罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等 |
| 《photoshop相关博客》 | 持续更新中~ | 基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结 |
| 日常开发&办公&生产【实用工具】分享相关博客》 | 持续更新中~ | 分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!

更多推荐


所有评论(0)