Node.js之terminal彩色字体

蛮久以前就对社区里很多用Node.js写的命令行工具(其他语言当然也可以)如行云流水般的terminal输出信息感到很神奇,非常酷。近日对其底层实现做了稍许研究,特此记录。

早前我曾经了解到,terminal的彩色字体是需要加特殊字符前缀的,比如:

1
2
echo '\e[32mHello world!'
echo '\e[33mHello world!'

会打印出一行绿色的Hello world!和一行黄色的Hello world!
这里的\e[32m\e[33m就是特殊的颜色前缀, 更多颜色参考 这里
但如果是在日志文件里,颜色前缀并不长这样,通过vim查看彩色日志文件(即查看文件的原始文本)是这样的:

1
2
^[[32mHello world!
^[[33mHello world!

通过cat, tail, more命令查看文件,即可以显示彩色字体。
行首的^[其实是一个字符,在本文中无法表现出来。其实本质是一样的,只是媒介不同而已。我做了一个小实验:

1
echo '\e[32mHello world!' > colors.log

然后通过vim打开这个colors.log,内容就是:

1
^[[32mHello world!

然后,说说我这次风波。关于日志打印,早期公司的Nodejs项目使用debug,感觉API不满意,然后改用winston,彩色输出并没什么问题,后来觉得winston太重,以及一些小需求没能满足,于是自己造了个小轮子

第一版是基于chalk, 在自己的terminal下测试很正常。然而到了服务器上就出问题了,所有日志都是默认的白色,即相当于没经过任何处理,直接打印日志。

我通过vim打开日志文件,发现里面真是没有任何颜色前缀,都是原始的日志内容。再回过头看别的使用winston的日志文件,都是带颜色前缀的。当时我就懵逼了。
这里又要说一下我们公司项目的部署方式,我们的Node.js服务在docker中通过pm2-docker启动。所以我怀疑:难道是经过pm2从stdout转到文件的过程中,颜色前缀被干掉了?

接着,在本地启动pm2(无docker)经过各种打印调试,研究winston源码,此处省略一万字…
最终找到了这一段代码:
winston/lib/winston/config.js

1
2
3
4
var colors = require('colors/safe');
// Fix colors not appearing in non-tty environments
colors.enabled = true;

注意那句注释,以及下面那句,即强制开启颜色。我把这句注释掉后,winston也无法打印彩色日志了。再翻一下colors文档和源码,发现文档并没有提及这种用法,但从源码看出这样做是可以生效的,像是『黑科技』。另外,colors支持从环境变量读取是否启用颜色日志的开关。再翻翻chalk的源码,与colors功能大体一致,区别就是colors允许从代码级别强制开启彩色字体(虽然文档没提)。然后,我给pm2配置文件中添加FORCE_COLOR: true, 重启,彩色字体就出来了~

所以我把chalk替换为colors, 同样在代码级别强制开启彩色字体,不需要设置环境变量,done!

加餐

之前一直好奇,Nodejs的命令行工具,如何实现对同一行日志替换输出, 典型的比如进度条的展示。虽然已经有很多库已经包装实现了这个功能,比如 progressora。但还是希望知道底层是怎么实现的,其实关键就两个API(代码片段摘自stackoverflow):

1
2
3
4
5
6
7
8
var i = 0; // 『点』的个数的被除数
setInterval(function() {
process.stdout.clearLine(); // 清空当前行
process.stdout.cursorTo(0); // 移动光标到行首
i = (i + 1) % 4;
var dots = new Array(i + 1).join(".");
process.stdout.write("Waiting" + dots); // 输出新的信息
}, 300);

加餐满意否?