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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
日志服务 SLS,月写入数据量 50GB 1个月
简介: 通俗地讲,就是那些在终端输出彩色的文字中包含了一些转义序列字符,只不过我们看不到,被终端进行了解析。然后终端将这些字符解析成了我们现在看到的形形色色多彩的日志(包括一些颜色、下划线、粗体等)。

前言



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


image.png

image.png


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


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


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


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


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

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


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


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


image.png


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


现在我们要做的就是如何将这些色彩日志输出到浏览器端。而进行这个步骤之前,我们得先知道,这些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。例如 :


echo -e "\u001B[31m hello"


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


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


效果如下:


image.png


以下是其色彩对照表:


image.png


8-bit


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


ESC[ 38;5;<n> m // 设置字体颜色
ESC[ 48;5;<n> m // 设置背景颜色
    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"


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


image.png


以下是其色彩对照表:


image.png


24-bit


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


ESC[ 38;2;<r>;<g>;<b>m // 前景色
ESC[ 48;2;<r>;<g>;<b>m // 背景色


例如:


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


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


image.png


解析工具



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


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


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


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


核心思路为:


image.png


image.png


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: '! ' }
]


然后我们可以写一个函数来遍历上面解析得到的 JSON数组,输出 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 `<span class="${color} bg-${bgColor}">${value}</span>`
}
function bgCode(value, color) {
    return `<span class="${color}">${value}</span>`
}
function singleCode(value) {
    return `<span>${value}</span>`
}


使用示例如下:


const str = "\u001B[34mHello \u001B[39m World \u001B[31m! \u001B[39m";
console.log(createHtml(parseAnsi(str)));
// <span class="blue">Hello</span><span> World</span><span class="red">!</span>


部署实战



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


// 项目目录结构
demo
 |- package.json
 |- index.html
 |- webpack.config.js
 |- /src
   |- index.js
index.js
build.sh


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


// index.js
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


image.png


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


为什么从 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


// 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: "哑终端"

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

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


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


How?


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


image.png


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


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


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


image.png


参考



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


原创精选


围观

程序员反卷指南 - 打工人也需要生活

热文

体验了一把提高生产力的Mac触控增强神器

热文

上线5个月,我们累计导出了10000+简历| 木及简历的背后

热文

前端游戏巨制!CSS居然可以做3D游戏了

热文

我给鸿星尔克写了一个720°全景看鞋展厅

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
25天前
|
弹性计算 运维 Serverless
项目管理和持续集成系统搭建问题之云效流水线支持阿里云产品的企业用户如何解决
项目管理和持续集成系统搭建问题之云效流水线支持阿里云产品的企业用户如何解决
46 1
项目管理和持续集成系统搭建问题之云效流水线支持阿里云产品的企业用户如何解决
|
25天前
|
安全 前端开发 持续交付
项目管理和持续集成系统搭建问题之云效的缺陷管理如何解决
项目管理和持续集成系统搭建问题之云效的缺陷管理如何解决
47 6
|
20天前
|
分布式计算 DataWorks 关系型数据库
MaxCompute 生态系统中的数据集成工具
【8月更文第31天】在大数据时代,数据集成对于构建高效的数据处理流水线至关重要。阿里云的 MaxCompute 是一个用于处理大规模数据集的服务平台,它提供了强大的计算能力和丰富的生态系统工具来帮助用户管理和处理数据。本文将详细介绍如何使用 DataWorks 这样的工具将 MaxCompute 整合到整个数据处理流程中,以便更有效地管理数据生命周期。
39 0
|
28天前
|
存储 数据采集 数据处理
【Flume拓扑揭秘】掌握Flume的四大常用结构,构建强大的日志收集系统!
【8月更文挑战第24天】Apache Flume是一个强大的工具,专为大规模日志数据的收集、聚合及传输设计。其核心架构包括源(Source)、通道(Channel)与接收器(Sink)。Flume支持多样化的拓扑结构以适应不同需求,包括单层、扇入(Fan-in)、扇出(Fan-out)及复杂多层拓扑。单层拓扑简单直观,适用于单一数据流场景;扇入结构集中处理多源头数据;扇出结构则实现数据多目的地分发;复杂多层拓扑提供高度灵活性,适合多层次数据处理。通过灵活配置,Flume能够高效构建各种规模的数据收集系统。
28 0
|
29天前
|
存储 消息中间件 人工智能
AI大模型独角兽 MiniMax 基于阿里云数据库 SelectDB 版内核 Apache Doris 升级日志系统,PB 数据秒级查询响应
早期 MiniMax 基于 Grafana Loki 构建了日志系统,在资源消耗、写入性能及系统稳定性上都面临巨大的挑战。为此 MiniMax 开始寻找全新的日志系统方案,并基于阿里云数据库 SelectDB 版内核 Apache Doris 升级了日志系统,新系统已接入 MiniMax 内部所有业务线日志数据,数据规模为 PB 级, 整体可用性达到 99.9% 以上,10 亿级日志数据的检索速度可实现秒级响应。
AI大模型独角兽 MiniMax 基于阿里云数据库 SelectDB 版内核 Apache Doris 升级日志系统,PB 数据秒级查询响应
|
27天前
|
缓存 NoSQL Linux
【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据
【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据
【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据
|
17天前
|
并行计算 关系型数据库 分布式数据库
朗坤智慧科技「LiEMS企业管理信息系统」通过PolarDB产品生态集成认证!
近日,朗坤智慧科技股份有限公司「LiEMS企业管理信息系统软件」通过PolarDB产品生态集成认证!
|
16天前
|
JSON 缓存 fastjson
一行日志引发的系统异常
本文记录了一行日志引发的系统异常以及作者解决问题的思路。
|
22天前
|
存储 Prometheus 监控
Grafana 与 Prometheus 集成:打造高效监控系统
【8月更文第29天】在现代软件开发和运维领域,监控系统已成为不可或缺的一部分。Prometheus 和 Grafana 作为两个非常流行且互补的开源工具,可以协同工作来构建强大的实时监控解决方案。Prometheus 负责收集和存储时间序列数据,而 Grafana 则提供直观的数据可视化功能。本文将详细介绍如何集成这两个工具,构建一个高效、灵活的监控系统。
97 1
|
25天前
|
运维 持续交付 项目管理
项目管理和持续集成系统搭建问题之帮助以诺行进行项目管理如何解决
项目管理和持续集成系统搭建问题之帮助以诺行进行项目管理如何解决
30 3