日志等级
通常,日志等级从低到高可以分为以下几类:
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>
效果图:
参与讨论
(Participate in the discussion)
参与讨论