Skip to main content

前端日志打印规范

在前端开发中,随着项目迭代升级,日志打印逐渐风格不一,合理的日志输出是监控应用状态、调试代码和跟踪用户行为的重要手段。

一个好的日志系统能够帮助开发者快速定位问题,提高开发效率。

日志等级

首先,我们需要定义不同的日志等级,以便根据消息的重要性进行分类。通常,日志等级从低到高可以分为以下几类:

  • DEBUG: 详细的开发时信息,用于调试应用
  • INFO: 重要事件的简要信息,如系统启动、配置等
  • WARN: 系统能正常运行,但有潜在错误的情况
  • ERROR: 由于严重的问题,某些功能无法正常运行
  • FATAL: 非常严重的问题,可能导致系统崩溃

日志内容

日志内容应该包含足够的信息,以便于开发者理解发生了什么。一个完整的日志消息通常包括:

  • 时间戳:精确到毫秒的事件发生时间
  • 日志等级:当前日志消息的等级
  • 消息内容:描述事件的详细信息
  • 错误堆栈 (可选):如果是错误,提供错误堆栈信息

例如:

[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 级别

日志封装

为了更好地控制日志输出,我们可以封装一个日志工具,来统一管理日志输出。

简单实现

class Logger {
static log(level, message, error) {
const timestamp = new Date().toISOString()
const stack = error ? error.stack : ''
const formattedMessage = `[${timestamp}] [${level}] ${message} ${stack}`

switch (level) {
case 'DEBUG':
// 浏览器默认不显示该等级日志
console.debug(formattedMessage)
break
case 'INFO':
console.info(formattedMessage)
break
case 'WARN':
console.warn(formattedMessage)
break
case 'ERROR':
case 'FATAL':
console.error(formattedMessage)
break
default:
console.log(formattedMessage)
}
}

static debug(message) {
this.log('DEBUG', message)
}

static info(message) {
this.log('INFO', message)
}

static warn(message) {
this.log('WARN', message)
}

static error(message, error) {
this.log('ERROR', message, error)
}

static fatal(message, error) {
this.log('FATAL', message, error)
}
}
Logger.info('Application is starting...')
Logger.error('Failed to load user data', new Error('Network Error'))

添加日志上报

在生产环境中,我们可能需要将日志发送到后端服务器进行收集和分析。这可以通过 AJAX 请求或专门的日志服务来实现。

我们可以修改 Logger 工具,添加一个 sendLog 方法来发送日志:

class Logger {
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 log(level, message, error) {
const timestamp = new Date().toISOString()
const stack = error ? error.stack : ''
const formattedMessage = `[${timestamp}] [${level}] ${message} ${stack}`

// 根据环境变量判断是否发送日志到后端
if (process.env.NODE_ENV === 'production') {
this.sendLog(formattedMessage)
}

// ...输出逻辑
}

// ...其他方法
}

过滤开发日志

在生产环境中,为了避免性能损耗和过多的日志信息,我们可能只希望输出 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')
}

日志输出美化

为了进一步提高日志的可读性,我们可以添加格式化功能,比如为不同等级的日志添加颜色,或者为错误堆栈提供更好的格式化。

class Logger {
static colors = {
// 不同日志等级不同颜色
DEBUG: '#5cb87a',
INFO: '#8896b3',
WARN: '#e6a23c',
ERROR: '#ff4d4f',
FATAL: '#ff4d4f'
}

static log(level, message, error) {
// ...其他逻辑

const color = this.colors[level]
const beautifiedMessage = [
`%c [${timestamp}] [${level}] %c ${message} %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'
]

switch (level) {
case 'DEBUG':
console.debug(...beautifiedMessage)
break
case 'INFO':
console.info(...beautifiedMessage)
break
case 'WARN':
console.warn(...beautifiedMessage)
break
case 'ERROR':
case 'FATAL':
console.error(...beautifiedMessage, `\n${stack}`)
break
default:
console.log(...beautifiedMessage)
}
}

// ...其他方法
}

完整封装

class Logger {
static level = 'DEBUG' // 默认为 DEBUG 级别
static colors = {
// 不同日志等级不同颜色
DEBUG: '#5cb87a',
INFO: '#8896b3',
WARN: '#e6a23c',
ERROR: '#ff4d4f',
FATAL: '#ff4d4f'
}

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 sendLog(message) {
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 log(level, message, error) {
if (!this.shouldLog(level)) {
return
}

const timestamp = new Date().toISOString()
const stack = error ? error.stack : ''
const formattedMessage = `[${timestamp}] [${level}] ${message} ${stack}`

// 根据环境变量判断是否发送日志到后端
if (process.env.NODE_ENV === 'production') {
this.sendLog(formattedMessage)
}

const color = this.colors[level]
const beautifiedMessage = [
`%c [${timestamp}] [${level}] %c ${message} %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'
]

switch (level) {
case 'DEBUG':
console.debug(...beautifiedMessage)
break
case 'INFO':
console.info(...beautifiedMessage)
break
case 'WARN':
console.warn(...beautifiedMessage)
break
case 'ERROR':
case 'FATAL':
console.error(...beautifiedMessage, `\n${stack}`)
break
default:
console.log(...beautifiedMessage)
}
}

static debug(message) {
this.log('DEBUG', message)
}

static info(message) {
this.log('INFO', message)
}

static warn(message) {
this.log('WARN', message)
}

static error(message, error) {
this.log('ERROR', message, error)
}

static fatal(message, error) {
this.log('FATAL', message, error)
}
}

// 生产环境中设置日志等级
if (process.env.NODE_ENV === 'production') {
Logger.setLevel('WARN')
}