在这里插入图片描述

前端开发者必备:用 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 最想解决的痛点,其实就是我们前面说的那些:

  1. 开发时调试困难:提供了丰富的日志级别和格式化选项,让调试信息一目了然
  2. 上线后问题难定位:支持生产环境日志收集,可以远程上报关键信息
  3. 日志管理混乱:通过命名空间和配置管理,让日志井井有条
  4. 性能担忧:轻量级设计,对页面性能影响极小
  5. 扩展性差:插件机制让功能扩展变得简单

从混乱调试到优雅追踪:一次开发体验的升级

说了这么多理论,让我们来看点实际的。下面是一个典型的"混乱调试"场景:

// 传统的调试方式 - 混乱不堪
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 提供了六个日志级别,从低到高分别是:tracedebuginfowarnerrorsilent。每个级别都有它的特定用途:

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 的独特优势

  1. 体积与功能的完美平衡:只有 8KB,但功能齐全
  2. 零配置即用:合理的默认配置,开箱即用
  3. 渐进式采用:可以从简单的 console.log 替代开始,逐步使用高级功能
  4. 中文友好:错误提示和文档都有中文版本
  5. 性能优化:异步日志处理,不影响主线程
  6. 现代化设计:支持 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等工具

吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤

非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!

在这里插入图片描述

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐