日志等级
通常,日志等级从低到高可以分为以下几类:
DEBUG:详细的开发时信息,用于调试应用。
INFO:重要事件的简要信息,如系统启动、配置等。
WARN:系统能正常运行,但有潜在错误的情况。
ERROR:由于严重的问题,某些功能无法正常运行。
FATAL:非常严重的问题,可能导致系统崩溃。
我日常的开发可能就只考虑了自己设计的INFO(相当于分级中的DEBUG和INFO)和系统的ERROR,很多其余的是没有考虑到的,这里的分级更加规范。
日志内容和格式
一个完整的日志消息通常包括:
时间戳:精确到毫秒的事件发生时间。
日志等级:当前日志消息的等级。
消息内容:描述事件的详细信息。
错误堆栈:如果是错误,提供错误堆栈信息。
格式:
# [时间戳] [日志等级] [消息内容] [错误堆栈]
[2024-04-01T12:00:00.000Z] [ERROR] Failed to load user data. {stack}日志输出
在前端项目中,我们通常使用console对象进行日志输出。不同的日志等级可以使用不同的console方法:
console.debug用于DEBUG级别。
console.info用于INFO级别。
console.warn用于WARN级别。
console.error用于ERROR和FATAL级别。
日志收集
在生产环境中,我们可能需要将日志发送到后端服务器进行收集和分析(放到前端分析是不现实的)。这可以通过AJAX请求或专门的日志服务来实现。
class Logger {
  // ...其他方法
 // 根据环境变量判断是否发送日志到后端
if (process.env.NODE_ENV === 'production') {
  this.sendLog(formattedMessage);
}
  static sendLog(message) {
    // 假设我们有一个日志收集的API
    const logEndpoint = '/api/logs';
 fetch(logEndpoint, {
  method: 'POST', 
  headers: {
   'Content-Type': 'application/json', 
  }, body: JSON.stringify({ message }), }).catch((error) => {
  console.error('Failed to send log', error); 
 });
}日志等级控制
在开发环境中,我们可能希望看到尽可能多的日志输出,以便更好地调试应用。但在生产环境中,为了避免性能损耗和过多的日志信息,我们可能只希望输出WARN和以上等级的日志。我们可以在Logger中添加一个等级控制(这也是一个非常好的思路):
class Logger {
  static level = 'DEBUG'; // 默认为DEBUG级别
  static setLevel(newLevel) {
    this.level = newLevel;
  }
  static shouldLog(level) {
    const levels = ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'];
    return levels.indexOf(level) >= levels.indexOf(this.level);
  }
  static log(level, message, error) {
    if (!this.shouldLog(level)) {
      return;
    }
    // ...日志输出逻辑
  }
  // ...其他方法
}
// 生产环境中设置日志等级
if (process.env.NODE_ENV === 'production') {
  Logger.setLevel('WARN');
}
// 使用示例
Logger.debug('This will not be logged in production');
Logger.warn('This will be logged in production');日志格式化输出
为了进一步提高日志的可读性,我们可以添加格式化功能,比如为不同等级的日志添加颜色,或者为错误堆栈提供更好的格式化。
class Logger {
  // ...其他方法
  static formatStack(stack) {
    if (!stack) return '';
    // 格式化错误堆栈的逻辑
    return stack.split('\n').map(line => `    at ${line}`).join('\n');
  }
  static log(level, message, error) {
    // ...日志输出逻辑
    // 格式化错误堆栈
    if (error) {
      formattedMessage += `\n${this.formatStack(error.stack)}`;
    }
    // ...输出逻辑
  }
  // ...其他方法
}这里我之前看过另外一篇文章,也是讲日志输出格式的(利用console.log()):
console.log() 可以接受任何类型的参数,包括字符串、数字、布尔值、对象、数组、函数等。最厉害的是,它支持占位符!常用的占位符:
%s - 字符串
%d or %i - 整数
%f - 浮点数
%o - 对象
%c - CSS 样式
我觉得可以跟这里混合起来,我整合了一下这俩文章的内容,总体的代码如下。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Log Demo</title>
    <style>
        #logOutput {
            border: 1px solid #ccc;
            padding: 10px;
            margin-top: 20px;
            height: 200px;
            overflow-y: scroll;
            background: #f9f9f9;
        }
        button {
            padding: 10px;
            margin: 5px;
            cursor: pointer;
        }
    </style>
</head>
<body>
<button id="infoButton">Info Log</button>
<button id="errorButton">Error Log</button>
<button id="warningButton">Warning Log</button>
<button id="successButton">Success Log</button>
<div id="logOutput"></div>
<script type="module">
    // Set a global variable to determine the environment
    window.ENV = 'development'; // Change this to 'production' as needed
    class Logger {
        static level = 'DEBUG'; // 默认为DEBUG级别
        static setLevel(newLevel) {
            this.level = newLevel;
        }
        static shouldLog(level) {
            const levels = ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'];
            return levels.indexOf(level) >= levels.indexOf(this.level);
        }
        static formatStack(stack) {
            if (!stack) return '';
            // 格式化错误堆栈的逻辑
            return stack.split('\n').map(line => `    at ${line}`).join('\n');
        }
        static log(level, message, error = null) {
            if (!this.shouldLog(level)) {
                return;
            }
            const timestamp = new Date().toISOString();
            const stack = error ? this.formatStack(error.stack) : '';
            let formattedMessage = `[${timestamp}] [${level}] ${message}`;
            if (stack) {
                formattedMessage += `\n${stack}`;
            }
            // 使用 prettyLog 方法输出日志
            switch (level) {
                case 'DEBUG':
                    Logger.prettyLog.info('DEBUG', formattedMessage);
                    break;
                case 'INFO':
                    Logger.prettyLog.info('INFO', formattedMessage);
                    break;
                case 'WARN':
                    Logger.prettyLog.warning('WARN', formattedMessage);
                    break;
                case 'ERROR':
                case 'FATAL':
                    Logger.prettyLog.error(level, formattedMessage);
                    break;
                default:
                    Logger.prettyLog.info('LOG', formattedMessage);
            }
            // 根据环境变量判断是否发送日志到后端
            if (window.ENV === 'production') {
                this.sendLog(formattedMessage);
            }
        }
        static debug(message) {
            Logger.log('DEBUG', message);
        }
        static info(message) {
            Logger.log('INFO', message);
        }
        static warn(message) {
            Logger.log('WARN', message);
        }
        static error(message, error) {
            Logger.log('ERROR', message, error);
        }
        static fatal(message, error) {
            Logger.log('FATAL', message, error);
        }
        static sendLog(message) {
            // 假设我们有一个日志收集的API
            const logEndpoint = '/api/logs';
            fetch(logEndpoint, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ message }),
            }).catch((error) => {
                console.error('Failed to send log', error);
            });
        }
        static prettyLog = (() => {
            const isProduction = false;  // 假设当前总是开发环境
            const isEmpty = (value) => {
                return value == null || value === '';
            };
            const prettyPrint = (title, text, color) => {
                if (isProduction) return;
                console.log(
                    `%c ${title} %c ${text} %c`,
                    `background:${color};border:1px solid ${color}; padding: 1px; border-radius: 2px 0 0 2px; color: #fff;`,
                    `border:1px solid ${color}; padding: 1px; border-radius: 0 2px 2px 0; color: ${color};`,
                    'background:transparent'
                );
            };
            const info = (textOrTitle, content = '') => {
                const title = isEmpty(content) ? 'Info' : textOrTitle;
                const text = isEmpty(content) ? textOrTitle : content;
                prettyPrint(title, text, '#909399');
            };
            const error = (textOrTitle, content = '') => {
                const title = isEmpty(content) ? 'Error' : textOrTitle;
                const text = isEmpty(content) ? textOrTitle : content;
                prettyPrint(title, text, '#F56C6C');
            };
            const warning = (textOrTitle, content = '') => {
                const title = isEmpty(content) ? 'Warning' : textOrTitle;
                const text = isEmpty(content) ? textOrTitle : content;
                prettyPrint(title, text, '#E6A23C');
            };
            const success = (textOrTitle, content = '') => {
                const title = isEmpty(content) ? 'Success' : textOrTitle;
                const text = isEmpty(content) ? textOrTitle : content;
                prettyPrint(title, text, '#67C23A');
            };
            return {
                info,
                error,
                warning,
                success
            };
        })();
    }
    // 生产环境中设置日志等级
    if (window.ENV === 'production') {
        Logger.setLevel('WARN');
    }
    // 使用示例
    Logger.debug('This will not be logged in production');
    Logger.warn('This will be logged in production');
    Logger.error('An error occurred', new Error('Test Error'));
    document.addEventListener("DOMContentLoaded", function() {
        const logOutput = document.getElementById('logOutput');
        const log = Logger;
        document.getElementById('infoButton').addEventListener('click', function() {
            log.prettyLog.info("This is an info message");
            logOutput.innerHTML += '<p style="color: #909399;">Info: This is an info message</p>';
        });
        document.getElementById('errorButton').addEventListener('click', function() {
            log.prettyLog.error("This is an error message");
            logOutput.innerHTML += '<p style="color: #F56C6C;">Error: This is an error message</p>';
        });
        document.getElementById('warningButton').addEventListener('click', function() {
            log.prettyLog.warning("This is a warning message");
            logOutput.innerHTML += '<p style="color: #E6A23C;">Warning: This is a warning message</p>';
        });
        document.getElementById('successButton').addEventListener('click', function() {
            log.prettyLog.success("success","This is a success message");
            logOutput.innerHTML += '<p style="color: #67C23A;">Success: This is a success message</p>';
        });
    });
</script>
</body>
</html>
效果图:

