花式 console.log

背景简介

入职爱奇艺以来,我发现本地代码的控制台有很多 console 日志打印,用来为我们开发时做提示,非常方便,但是上线上线后就不会再打印了,不影响性能,代码内有判断的,只有开发环境会打印!

于是,我就想研究一下这个功能是怎么实现的,在搜集了一些资料之后,搞个花式 console.log,让代码更优雅,更易读,更易维护,更易调试。

console 简介

console 是一个用于调试和记录信息的内置对象, 提供了多种方法,可以帮助开发者输出各种信息,进行调试和分析。

console.log() :

用于输出一般信息,大家应该在熟悉不过了。

console.info() :

输出信息,与 console.log 类似,但在某些浏览器中可能有不同的样式。

console.warn() :

输出警告信息,通常会以黄色背景或带有警告图标的样式显示。

console.error() :

输出错误信息,通常会以红色背景或带有错误图标的样式显示。

console.table() :

以表格形式输出数据,适用于数组和对象。

例如:

1
2
3
4
5
const users = [
{ name: '石小石', age: 18 },
{ name: '刘亦菲', age: 18 }
]
console.table(users)

通过上述介绍,我们可以看出,原生的文本信息、警告信息、错误信息、数组信息打印出来的效果都很普通,辨识度不高!现在我们通过 console.log 来实现一些花里花哨的样式!

技术方案

console.log()

console.log() 可以接受任何类型的参数,包括字符串、数字、布尔值、对象、数组、函数等。最厉害的是,它支持占位符!

常用的占位符:

  • %s - 字符串
  • %d or %i - 整数
  • %f - 浮点数
  • %o - 对象
  • %c - CSS 样式

格式化字符串

console.log() 支持类似于 C 语言 printf 函数的格式化字符串。我们可以使用占位符来插入变量值。

1
2
3
const name = 'Alice'
const age = 30
console.log('Name: %s, Age: %d', name, age) // Name: Alice, Age: 30

添加样式

可以使用 %c 占位符添加 CSS 样式,使输出内容更加美观。

1
console.log('%c This is a styled message', 'color: red; font-size: 20px;')

自定义样式的实现,其实主要是靠%c 占位符添加 CSS 样式实现的!

实现美化的信息打印

基础信息打印

我们创建一个 prettyLog 方法,用于逻辑编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 美化打印实现方法
const prettyLog = () => {
const isEmpty = (value: any) => {
return value == null || value === undefined || value === ''
}
const prettyPrint = (title: string, text: string, color: string) => {
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: string, content = '') => {
const title = isEmpty(content) ? 'Info' : textOrTitle
const text = isEmpty(content) ? textOrTitle : content
prettyPrint(title, text, '#909399')
}
return {
info
}
}

上述代码定义了一个 prettyLog 函数,用于美化打印信息到控制台。通过自定义样式,输出信息以更易读和美观的格式呈现。

我们使用一下看看效果

1
2
3
4
5
6
7
8
// 创建打印对象
const log = prettyLog()

// 不带标题
log.info('这是基础信息!')

//带标题
log.info('注意看', '这是个男人叫小帅!')

info 方法用于输出信息级别的日志。它接受两个参数:textOrTitle 和 content。如果只提供一个参数,则视为内容并设置默认标题为 Info;如果提供两个参数,则第一个参数为标题,第二个参数为内容。最后调用 prettyPrint 方法进行输出。

错误信息打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const prettyLog = () => {
const isEmpty = (value: any) => {
return value == null || value === undefined || value === ''
}
const prettyPrint = (title: string, text: string, color: string) => {
// ...
}
const info = (textOrTitle: string, content = '') => {
// ...
}
const error = (textOrTitle: string, content = '') => {
const title = isEmpty(content) ? 'Error' : textOrTitle
const text = isEmpty(content) ? textOrTitle : content
prettyPrint(title, text, '#F56C6C')
}
// retu;
return {
info,
error
}
}
// 创建打印对象
const log = prettyLog()

log.error('奥德彪', '出来的时候穷 生活总是让我穷 所以现在还是穷。')

log.error('前方的路看似很危险,实际一点也不安全。')

成功信息与警告信息打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 美化打印实现方法
const prettyLog = () => {
const isEmpty = (value: any) => {
return value == null || value === undefined || value === ''
}
const prettyPrint = (title: string, text: string, color: string) => {
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: string, content = '') => {
const title = isEmpty(content) ? 'Info' : textOrTitle
const text = isEmpty(content) ? textOrTitle : content
prettyPrint(title, text, '#909399')
}
const error = (textOrTitle: string, content = '') => {
const title = isEmpty(content) ? 'Error' : textOrTitle
const text = isEmpty(content) ? textOrTitle : content
prettyPrint(title, text, '#F56C6C')
}
const warning = (textOrTitle: string, content = '') => {
const title = isEmpty(content) ? 'Warning' : textOrTitle
const text = isEmpty(content) ? textOrTitle : content
prettyPrint(title, text, '#E6A23C')
}
const success = (textOrTitle: string, content = '') => {
const title = isEmpty(content) ? 'Success ' : textOrTitle
const text = isEmpty(content) ? textOrTitle : content
prettyPrint(title, text, '#67C23A')
}
// retu;
return {
info,
error,
warning,
success
}
}
// 创建打印对象
const log = prettyLog()

log.warning('奥德彪', '我并非无路可走 我还有死路一条! ')

log.success('奥德彪', '钱没了可以再赚,良心没了便可以赚的更多。 ')

实现图片打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 美化打印实现方法
const prettyLog = () => {
// ....
const picture = (url: string, scale = 1) => {
const img = new Image()
img.crossOrigin = 'anonymous'
img.onload = () => {
const c = document.createElement('canvas')
const ctx = c.getContext('2d')
if (ctx) {
c.width = img.width
c.height = img.height
ctx.fillStyle = 'red'
ctx.fillRect(0, 0, c.width, c.height)
ctx.drawImage(img, 0, 0)
const dataUri = c.toDataURL('image/png')

console.log(
`%c sup?`,
`font-size: 1px;
padding: ${Math.floor((img.height * scale) / 2)}px ${Math.floor((img.width * scale) / 2)}px;
background-image: url(${dataUri});
background-repeat: no-repeat;
background-size: ${img.width * scale}px ${img.height * scale}px;
color: transparent;
`
)
}
}
img.src = url
}

return {
info,
error,
warning,
success,
picture
}
}

// 创建打印对象
const log = prettyLog()

log.picture(
'https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2024%2F0514%2Fd0ea93ebj00sdgx56001xd200u000gtg00hz00a2.jpg&thumbnail=660x2147483647&quality=80&type=jpg'
)

上述代码参考了其他文章:Just a moment…

url 可以传支持 base64,如果是 url 链接,图片链接则必须开启了跨域访问才能打印

实现美化的数组打印

打印对象或者数组,其实用原生的 console.table 比较好

1
2
3
4
5
6
7
const data = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 },
{ id: 3, name: 'Charlie', age: 35 }
]

console.table(data)

当然,我们也可以伪实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const table = () => {
const data = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 },
{ id: 3, name: 'Charlie', age: 35 }
]
console.log(
'%c id%c name%c age',
'color: white; background-color: black; padding: 2px 10px;',
'color: white; background-color: black; padding: 2px 10px;',
'color: white; background-color: black; padding: 2px 10px;'
)

data.forEach((row: any) => {
console.log(
`%c ${row.id} %c ${row.name} %c ${row.age} `,
'color: black; background-color: lightgray; padding: 2px 10px;',
'color: black; background-color: lightgray; padding: 2px 10px;',
'color: black; background-color: lightgray; padding: 2px 10px;'
)
})
}

但是,我们无法控制表格的宽度,因此,这个方法不太好用,不如原生。

仅在开发环境使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 美化打印实现方法
const prettyLog = () => {
//判断是否生产环境
const isProduction = import.meta.env.MODE === 'production'

const isEmpty = (value: any) => {
return value == null || value === undefined || value === ''
}
const prettyPrint = (title: string, text: string, color: string) => {
if (isProduction) return
// ...
}
// ...
const picture = (url: string, scale = 1) => {
if (isProduction) return
// ...
}

// retu;
return {
info,
error,
warning,
success,
picture,
table
}
}

我们可以通过 import.meta.env.MODE 判断当前环境是否为生产环境,在生产环境,我们可以禁用信息打印!

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// 美化打印实现方法
const prettyLog = () => {
const isProduction = import.meta.env.MODE === 'production'

const isEmpty = (value: any) => {
return value == null || value === undefined || value === ''
}
const prettyPrint = (title: string, text: string, color: string) => {
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: string, content = '') => {
const title = isEmpty(content) ? 'Info' : textOrTitle
const text = isEmpty(content) ? textOrTitle : content
prettyPrint(title, text, '#909399')
}
const error = (textOrTitle: string, content = '') => {
const title = isEmpty(content) ? 'Error' : textOrTitle
const text = isEmpty(content) ? textOrTitle : content
prettyPrint(title, text, '#F56C6C')
}
const warning = (textOrTitle: string, content = '') => {
const title = isEmpty(content) ? 'Warning' : textOrTitle
const text = isEmpty(content) ? textOrTitle : content
prettyPrint(title, text, '#E6A23C')
}
const success = (textOrTitle: string, content = '') => {
const title = isEmpty(content) ? 'Success ' : textOrTitle
const text = isEmpty(content) ? textOrTitle : content
prettyPrint(title, text, '#67C23A')
}
const table = () => {
const data = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 },
{ id: 3, name: 'Charlie', age: 35 }
]
console.log(
'%c id%c name%c age',
'color: white; background-color: black; padding: 2px 10px;',
'color: white; background-color: black; padding: 2px 10px;',
'color: white; background-color: black; padding: 2px 10px;'
)

data.forEach((row: any) => {
console.log(
`%c ${row.id} %c ${row.name} %c ${row.age} `,
'color: black; background-color: lightgray; padding: 2px 10px;',
'color: black; background-color: lightgray; padding: 2px 10px;',
'color: black; background-color: lightgray; padding: 2px 10px;'
)
})
}
const picture = (url: string, scale = 1) => {
if (isProduction) return
const img = new Image()
img.crossOrigin = 'anonymous'
img.onload = () => {
const c = document.createElement('canvas')
const ctx = c.getContext('2d')
if (ctx) {
c.width = img.width
c.height = img.height
ctx.fillStyle = 'red'
ctx.fillRect(0, 0, c.width, c.height)
ctx.drawImage(img, 0, 0)
const dataUri = c.toDataURL('image/png')

console.log(
`%c sup?`,
`font-size: 1px;
padding: ${Math.floor((img.height * scale) / 2)}px ${Math.floor((img.width * scale) / 2)}px;
background-image: url(${dataUri});
background-repeat: no-repeat;
background-size: ${img.width * scale}px ${img.height * scale}px;
color: transparent;
`
)
}
}
img.src = url
}

// retu;
return {
info,
error,
warning,
success,
picture,
table
}
}
// 创建打印对象
const log = prettyLog()