新聞中心
[[429062]]
前言
前段時間在使用 Travis CI 的時候發(fā)現(xiàn)它的部署日志包含了很多帶色彩的日志。

蒸湘網(wǎng)站建設(shè)公司成都創(chuàng)新互聯(lián),蒸湘網(wǎng)站設(shè)計制作,有大型網(wǎng)站制作公司豐富經(jīng)驗。已為蒸湘上千家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\成都外貿(mào)網(wǎng)站制作要多少錢,請找那個售后服務(wù)好的蒸湘做網(wǎng)站的公司定做!
并且我們知道,在使用命令行終端的時候也會出現(xiàn)這些可愛的色彩。
當然我不是為了吹它而吹它,它是有實際的作用的,能夠幫助我們快速定位問題!
對此我就產(chǎn)生了好奇,Travis CI 是怎么把這些彩色日志搬到瀏覽器的?
我猜想肯定不是通過對關(guān)鍵字詞特征識別來做的,因為那樣太 low 了。
進行了查詢后,查到了一個終于查到了關(guān)鍵詞,它就是 ANSI escape sequences。
ANSI轉(zhuǎn)義序列是帶內(nèi)信令的標準,用于控制終端和終端仿真器上的光標位置,顏色和一些其他選項。--維基百科
通俗地講,就是那些在終端輸出彩色的文字中包含了一些轉(zhuǎn)義序列字符,只不過我們看不到,被終端進行了解析。然后終端將這些字符解析成了我們現(xiàn)在看到的形形色色多彩的日志(包括一些顏色、下劃線、粗體等)。
例如,我們在終端進行npm 的安裝,git 分支的切換,包括運行報錯的時候都能看到。
正是有了這些色彩,讓我們的調(diào)試工作效率大大提高,一眼便能看到哪些命令出錯了,以及如何解決的方案。
現(xiàn)在我們要做的就是如何將這些色彩日志輸出到瀏覽器端。而進行這個步驟之前,我們得先知道,這些ANSI轉(zhuǎn)義序列的形態(tài)是什么樣子的?
根據(jù)wiki我們可以知道 ANSI 轉(zhuǎn)義序列可以操作很多功能,例如光標位置、顏色、下劃線和其他選項。下面我們就 顏色部分 來進行講解。
ANSI 轉(zhuǎn)義序列
ANSI 轉(zhuǎn)義序列 也是跟隨著終端的發(fā)展而發(fā)展,顏色的規(guī)范也是隨著設(shè)備的不同有所區(qū)別。例如在早期的設(shè)備只支持 3 / 4 Bit ,支持的顏色分別為 8 / 16 種。
ANSI 轉(zhuǎn)義序列大多數(shù)以 ESC 和'['開頭嵌入到文本中,終端會查找并解釋為命令,而不是字符串。
ESC 的 ANSI 值為 27 ,8進制表示為 \033 ,16進制表示為 \u001B。
3/4 bit
原始規(guī)格只有 8/16 種顏色。
比如ESC[30;47m 它是以 ESC[ 開頭 m 結(jié)束,中間為code碼,以分號進行分割。
color 取值為30-37,background 取值為 40-47。例如 :
- echo -e "\u001B[31m hello"
(如果想要清除顏色就需要使用 ESC [39;49m(某些終端不支持) 或者ESC[0m )
后來的終端增加了直接指定 90-97 和 100-107 的“明亮”顏色的能力。
效果如下:
以下是其色彩對照表:
8-bit
后來由于256色在顯卡上很常見,因此添加了轉(zhuǎn)義序列以從預定義的256種顏色中進行選擇,也就是說在原來的書寫方式上增加了新的一位來代表更多的顏色。
- ESC[ 38;5;
m // 設(shè)置字體顏色 - ESC[ 48;5;
m // 設(shè)置背景顏色 - 0-7: standard colors (as in ESC [ 30–37 m)
- 8-15: high intensity colors (as in ESC [ 90–97 m)
- 16-231: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
- 232-255: grayscale from black to white in 24 steps
在支持更多色彩的終端中,例如:
- echo -e "\u001B[38;5;11m hello"
代表輸出黃色字體。
- echo -e "\u001B[48;5;14;38;5;13m hello"
代表輸出藍色背景,粉紅色字體。
以下是其色彩對照表:
24-bit
再往后發(fā)展就是支持 24 位真彩的顯卡,Xterm, KDE 的Konsole,以及所有基于 libvte 的終端(包括GNOME終端)支持24位前景和背景顏色設(shè)置。
- ESC[ 38;2;
; ;m // 前景色 - ESC[ 48;2;
; ;m // 背景色
例如:
- echo -e "\u001B[38;2;100;228;75m hello"
輸出綠色的字體代表 rgb(100,228,75)。
解析工具
我們知道了轉(zhuǎn)義的規(guī)范后,那么我們需要將 ANSI 字符進行解析。
由于規(guī)范比較多,因此我們先調(diào)研一下在 js 中常用的色彩庫,來進行一個小小的探索。
由于 3 / 4bit 的兼容性更好,大多數(shù)工具(如chalk)會采用這 8 / 16 色來做高亮,因此我們先實現(xiàn)一個 8 / 16 色的解析。
這里參考了 ansiparse 這個解析庫:
核心思路為:
- const ansiparse = require('ansiparse')
- const ansiStr = "\u001B[34mHello \u001B[39m World \u001B[31m! \u001B[39m"
- const json = ansiparse(ansiStr)
- console.log(json)
- // json輸出如下:
- [
- { foreground: 'blue', text: 'Hello ' },
- { text: ' World ' },
- { foreground: 'red', text: '! ' }
- ]
然后我們可以寫一個函數(shù)來遍歷上面解析得到的 JSON數(shù)組,輸出 HTML。
- function createHtml(ansiList, wrap = '') {
- let html = '';
- for (let i = 0; i < ansiList.length; i++) {
- const htmlFrame = ansiList[i];
- const {background = '', text, foreground = ''} = htmlFrame;
- if(background && foreground) {
- if(text.includes('\n')) {
- html += wrap;
- continue;
- }
- html += fontBgCode(text, foreground, background);
- continue;
- }
- if (background || foreground) {
- const color = background ? `bg-${background}` : foreground;
- let textColor = bgCode(text, color);
- textColor = textColor.replace(/\n/g, wrap);
- html += textColor;
- continue;
- }
- if (text.includes('\n')) {
- const textColor = text.replace(/\n/g, wrap);
- html += textColor;
- continue;
- }
- html += singleCode(text);
- }
- html += ''
- return html;
- }
- function fontBgCode(value, color, bgColor) {
- return `${value}`
- }
- function bgCode(value, color) {
- return `${value}`
- }
- function singleCode(value) {
- return `${value}`
- }
使用示例如下:
- const str = "\u001B[34mHello \u001B[39m World \u001B[31m! \u001B[39m";
- console.log(createHtml(parseAnsi(str)));
- // Hello World!
部署實戰(zhàn)
有了上面的部分我們就來用一個簡單的demo實際演示一下部署日志吧!
- // 項目目錄結(jié)構(gòu)
- demo
- |- package.json
- |- index.html
- |- webpack.config.js
- |- /src
- |- index.js
- index.js
- build.sh
我們在 index.js 中啟動一個 build 腳本,來模擬一下我們真實的部署場景。
- const { spawn } = require('child_process');
- const cmd = spawn('sh', ['build.sh']);
- cmd.stdout.on('data', (data) => {
- console.log(`stdout: ${data}`);
- });
- cmd.stderr.on('data', (data) => {
- console.log(`stderr: ${data}`);
- });
- cmd.on('close', (code) => {
- console.log(`child process exited with code ${code}`);
- });
- // build.sh
- cd demo
- npx webpack
我們在終端嘗試一下,控制臺輸入 node index.js
發(fā)現(xiàn)在輸出的日志中,并沒有看到對應(yīng)的色彩。
為什么從 child_process 為什么無法輸出色彩,而我們?nèi)绻诮K端中直接打包項目卻能夠輸出色彩呢?
Why?
第一反應(yīng)就是去查找根源,也就是使用頻率最高的幾個色彩輸出的庫。
以簡單的方式給控制臺的輸出標記顏色。
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
- // colorette/index.js
- import * as tty from "tty"
- const env = process.env || {}
- const argv = process.argv || []
- const isDisabled = "NO_COLOR" in env || argv.includes("--no-color")
- const isForced = "FORCE_COLOR" in env || argv.includes("--color")
- const isWindows = process.platform === "win32"
- const isCompatibleTerminal = tty && tty.isatty && tty.isatty(1) && env.TERM && env.TERM !== "dumb"
- const isCI = "CI" in env && ("GITHUB_ACTIONS" in env || "GITLAB_CI" in env || "CIRCLECI" in env)
- 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: "啞終端"
啞終端指不能執(zhí)行諸如“刪行”、“清屏”或“控制光標位置”的一些特殊ANSI轉(zhuǎn)義序列的計算機終端
參考:https://zh.wikipedia.org/wiki/%E5%93%91%E7%BB%88%E7%AB%AF
也就是說我們的 child_process 的輸出流關(guān)閉了終端模式(TTY),上面的四種情況都不滿足。所以我們得不到帶有 ANSI 的色彩日志。
How?
我們可以顯示傳入環(huán)境變量 FORCE_COLOR=1 或者命令帶上參數(shù) --color 強制啟動顏色來解決這個問題。
這樣我們就拿到了帶有 ANSI 顏色信息的輸出文本,最終解析得到 HTML。
asset main.js 132 bytes [compared for emit] [minimized] (name: main)./src/index.js 289 bytes [built] [code generated]WARNING in configurationThe '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
網(wǎng)站題目:我掌握了少數(shù)人才知道持續(xù)集成系統(tǒng)的日志密碼
網(wǎng)頁鏈接:http://m.fisionsoft.com.cn/article/copeigj.html


咨詢
建站咨詢
