我掌握了少数人才知道持续集成系统的日志密码

[[429062]]

前言

前段时间在使用 Travis CI 的时候发现它的部署日志包含了很多带色彩的日志。

蒸湘网站建设公司成都创新互联,蒸湘网站设计制作,有大型网站制作公司丰富经验。已为蒸湘上千家提供企业网站建设服务。企业网站搭建\成都外贸网站制作要多少钱,请找那个售后服务好的蒸湘做网站的公司定做!

并且我们知道,在使用命令行终端的时候也会出现这些可爱的色彩。

当然我不是为了吹它而吹它,它是有实际的作用的,能够帮助我们快速定位问题!

对此我就产生了好奇,Travis CI 是怎么把这些彩色日志搬到浏览器的?

我猜想肯定不是通过对关键字词特征识别来做的,因为那样太 low 了。

进行了查询后,查到了一个终于查到了关键词,它就是 ANSI escape sequences。

ANSI转义序列是带内信令的标准,用于控制终端和终端仿真器上的光标位置,颜色和一些其他选项。--维基百科

通俗地讲,就是那些在终端输出彩色的文字中包含了一些转义序列字符,只不过我们看不到,被终端进行了解析。然后终端将这些字符解析成了我们现在看到的形形色色多彩的日志(包括一些颜色、下划线、粗体等)。

例如,我们在终端进行npm 的安装,git 分支的切换,包括运行报错的时候都能看到。

正是有了这些色彩,让我们的调试工作效率大大提高,一眼便能看到哪些命令出错了,以及如何解决的方案。

现在我们要做的就是如何将这些色彩日志输出到浏览器端。而进行这个步骤之前,我们得先知道,这些ANSI转义序列的形态是什么样子的?

根据wiki我们可以知道 ANSI 转义序列可以操作很多功能,例如光标位置、颜色、下划线和其他选项。下面我们就 颜色部分 来进行讲解。

ANSI 转义序列

ANSI 转义序列 也是跟随着终端的发展而发展,颜色的规范也是随着设备的不同有所区别。例如在早期的设备只支持 3 / 4 Bit ,支持的颜色分别为 8 / 16 种。

ANSI 转义序列大多数以 ESC 和'['开头嵌入到文本中,终端会查找并解释为命令,而不是字符串。

ESC 的 ANSI 值为 27 ,8进制表示为 \033 ,16进制表示为 \u001B。

3/4 bit

原始规格只有 8/16 种颜色。

比如ESC[30;47m 它是以 ESC[ 开头 m 结束,中间为code码,以分号进行分割。

color 取值为30-37,background 取值为 40-47。例如 :

 
 
 
  1. echo -e "\u001B[31m hello" 

(如果想要清除颜色就需要使用 ESC [39;49m(某些终端不支持) 或者ESC[0m )

后来的终端增加了直接指定 90-97 和 100-107 的“明亮”颜色的能力。

效果如下:

以下是其色彩对照表:

8-bit

后来由于256色在显卡上很常见,因此添加了转义序列以从预定义的256种颜色中进行选择,也就是说在原来的书写方式上增加了新的一位来代表更多的颜色。

 
 
 
  1. ESC[ 38;5; m // 设置字体颜色 
  2. ESC[ 48;5; m // 设置背景颜色 
  3.     0-7:  standard colors (as in ESC [ 30–37 m) 
  4.     8-15:  high intensity colors (as in ESC [ 90–97 m) 
  5.     16-231:  6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5) 
  6.    232-255:  grayscale from black to white in 24 steps 

在支持更多色彩的终端中,例如:

 
 
 
  1. echo -e "\u001B[38;5;11m hello" 

代表输出黄色字体。

 
 
 
  1. echo -e "\u001B[48;5;14;38;5;13m hello" 

代表输出蓝色背景,粉红色字体。

以下是其色彩对照表:

24-bit

再往后发展就是支持 24 位真彩的显卡,Xterm, KDE 的Konsole,以及所有基于 libvte 的终端(包括GNOME终端)支持24位前景和背景颜色设置。

 
 
 
  1. ESC[ 38;2;;;m // 前景色 
  2. ESC[ 48;2;;;m // 背景色 

例如:

 
 
 
  1. echo -e "\u001B[38;2;100;228;75m hello" 

输出绿色的字体代表 rgb(100,228,75)。

解析工具

我们知道了转义的规范后,那么我们需要将 ANSI 字符进行解析。

由于规范比较多,因此我们先调研一下在 js 中常用的色彩库,来进行一个小小的探索。

由于 3 / 4bit 的兼容性更好,大多数工具(如chalk)会采用这 8 / 16 色来做高亮,因此我们先实现一个 8 / 16 色的解析。

这里参考了 ansiparse 这个解析库:

核心思路为:

 
 
 
  1. const ansiparse = require('ansiparse') 
  2.  
  3. const ansiStr = "\u001B[34mHello \u001B[39m World \u001B[31m! \u001B[39m" 
  4.  
  5. const json = ansiparse(ansiStr) 
  6. console.log(json) 
  7.  
  8. // json输出如下: 
  9.   { foreground: 'blue', text: 'Hello ' }, 
  10.   { text: ' World ' }, 
  11.   { foreground: 'red', text: '! ' } 

然后我们可以写一个函数来遍历上面解析得到的 JSON数组,输出 HTML。

 
 
 
  1. function createHtml(ansiList, wrap = '') { 
  2.     let html = ''; 
  3.     for (let i = 0; i < ansiList.length; i++) { 
  4.         const htmlFrame = ansiList[i]; 
  5.  
  6.         const {background = '', text, foreground = ''} = htmlFrame; 
  7.         if(background && foreground) { 
  8.             if(text.includes('\n')) { 
  9.                 html += wrap; 
  10.                 continue; 
  11.             } 
  12.             html += fontBgCode(text, foreground, background); 
  13.             continue; 
  14.         } 
  15.         if (background || foreground) { 
  16.             const color = background ? `bg-${background}` : foreground; 
  17.             let textColor = bgCode(text, color); 
  18.  
  19.             textColor = textColor.replace(/\n/g, wrap); 
  20.              
  21.             html += textColor; 
  22.             continue; 
  23.         } 
  24.         if (text.includes('\n')) { 
  25.             const textColor = text.replace(/\n/g, wrap); 
  26.             html += textColor; 
  27.             continue; 
  28.         } 
  29.         html += singleCode(text); 
  30.     } 
  31.     html += '' 
  32.     return html; 
  33.  
  34. function fontBgCode(value, color, bgColor) { 
  35.     return `${value}` 
  36.  
  37. function bgCode(value, color) { 
  38.     return `${value}` 
  39.  
  40. function singleCode(value) { 
  41.     return `${value}

使用示例如下:

 
 
 
  1. const str = "\u001B[34mHello \u001B[39m World \u001B[31m! \u001B[39m"; 
  2.  
  3. console.log(createHtml(parseAnsi(str))); 
  4.  
  5. // Hello World

部署实战

有了上面的部分我们就来用一个简单的demo实际演示一下部署日志吧!

 
 
 
  1. // 项目目录结构 
  2. demo 
  3.  |- package.json 
  4.  |- index.html 
  5.  |- webpack.config.js 
  6.  |- /src 
  7.    |- index.js 
  8. index.js 
  9. build.sh 

我们在 index.js 中启动一个 build 脚本,来模拟一下我们真实的部署场景。

 
 
 
  1. const { spawn } = require('child_process'); 
  2. const cmd = spawn('sh', ['build.sh']); 
  3.  
  4. cmd.stdout.on('data', (data) => { 
  5.   console.log(`stdout: ${data}`); 
  6. }); 
  7.  
  8. cmd.stderr.on('data', (data) => { 
  9.   console.log(`stderr: ${data}`); 
  10. }); 
  11.  
  12. cmd.on('close', (code) => { 
  13.   console.log(`child process exited with code ${code}`); 
  14. }); 
  15. // build.sh 
  16.  
  17. cd demo 
  18.  
  19. npx webpack 

我们在终端尝试一下,控制台输入 node index.js

发现在输出的日志中,并没有看到对应的色彩。

为什么从 child_process 为什么无法输出色彩,而我们如果在终端中直接打包项目却能够输出色彩呢?

Why?

第一反应就是去查找根源,也就是使用频率最高的几个色彩输出的库。

以简单的方式给控制台的输出标记颜色。

https://github.com/Marak/colors.js

https://github.com/chalk/chalk

在看了webpack-cli的源码后,查到它是用了colorette作为色彩输出库的。

那么我们就来查看一下colorette的源码一探究竟。

在入口文件的开头就看到一个变量isColorSupported来判断是否支持色彩输出。

https://github.com/jorgebucaran/colorette/blob/main/index.js#L17

 
 
 
  1. // colorette/index.js 
  2. import * as tty from "tty" 
  3.  
  4. const env = process.env || {} 
  5. const argv = process.argv || [] 
  6.  
  7. const isDisabled = "NO_COLOR" in env || argv.includes("--no-color") 
  8.  
  9. const isForced = "FORCE_COLOR" in env || argv.includes("--color") 
  10. const isWindows = process.platform === "win32" 
  11. const isCompatibleTerminal = tty && tty.isatty && tty.isatty(1) && env.TERM && env.TERM !== "dumb" 
  12. const isCI = "CI" in env && ("GITHUB_ACTIONS" in env || "GITLAB_CI" in env || "CIRCLECI" in env) 
  13.  
  14. export const isColorSupported = !isDisabled && (isForced || isWindows || isCompatibleTerminal || isCI) 

可以看到这种工具判断了很多条件,来对我们的输出流进行处理。

在以上条件成立下,才会输出 ANSI 日志。在不满足以上情况的条件下,就会切换输出更容易解析的方式。

const isWindows = process.platform === "win32"

参考:https://stackoverflow.com/questions/8683895/how-do-i-determine-the-current-operating-system-with-node-js

dumb: "哑终端"

哑终端指不能执行诸如“删行”、“清屏”或“控制光标位置”的一些特殊ANSI转义序列的计算机终端

参考:https://zh.wikipedia.org/wiki/%E5%93%91%E7%BB%88%E7%AB%AF

也就是说我们的 child_process 的输出流关闭了终端模式(TTY),上面的四种情况都不满足。所以我们得不到带有 ANSI 的色彩日志。

How?

我们可以显示传入环境变量 FORCE_COLOR=1 或者命令带上参数 --color 强制启动颜色来解决这个问题。

这样我们就拿到了带有 ANSI 颜色信息的输出文本,最终解析得到 HTML。

 
 
 
  1. asset main.js 132 bytes [compared for emit] [minimized] (name: main)
    ./src/index.js 289 bytes [built] [code generated]
    WARNING in configuration
    The 'mode' option has not been set, webpack will fallback to 'production' for this value.
    Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
    You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
    webpack 5.53.0 compiled with 1 warning in 201 ms
     

然后就可以在浏览器中展示我们彩色的输出日志了,与在终端里输出的一致。

参考

https://www.twilio.com/blog/guide-node-js-logging

https://github.com/jorgebucaran/colorette/blob/main/index.js#L17

https://en.wikipedia.org/wiki/ANSI_escape_code#Colors

https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences

https://stackoverflow.com/questions/15011478/ansi-questions-x1b25h-and-x1be

https://bluesock.org/~willg/dev/ansi.html

https://www.cnblogs.com/gamesky/archive/2012/07/28/2613264.html

https://github.com/mmalecki/ansiparse

 

分享文章:我掌握了少数人才知道持续集成系统的日志密码
链接地址:http://www.36103.cn/qtweb/news45/12345.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联