CI 日志中的 ANSI 转义码 —— GitHub Actions、GitLab、Jenkins、Buildkite
CI runner 把构建输出送到日志文件而非 TTY —— 多数颜色感知工具据此默认剥离 SGR。2018 年后推出的主流 CI 的 Web UI 都能完美渲染 ANSI;唯一缺的环节是说服工具继续输出这些字节。本页覆盖该契约的八个组成 —— 为何 CI 剥离颜色、各大提供商(GitHub Actions、GitLab、Jenkins、Buildkite、CircleCI、Azure)如何解析或注解日志流、如何用 FORCE_COLOR / --color=always / PTY 包装让颜色穿越管道,以及如何在本地查看下载的原始日志并保留彩色渲染。
CI 默认为何剥离颜色 —— isatty(stdout) == 0
在 CI 任务中,进程的 stdout 接的是日志文件或 socket,不是终端。`isatty(STDOUT_FILENO)` 返回 0,大多数规范工具据此剥离 SGR 字节 —— 反正没东西去渲染。覆写手段有三类:FORCE_COLOR(Node 生态:chalk、jest、mocha、npm、pnpm、vite、next;许多非 Node 工具也尊重它 —— 见 /accessibility)、`--color=always`(git、grep、ripgrep、fd、eza、jest、pytest、cargo)、POSIX 下的 `script` / `unbuffer` PTY 包装。让工具知道「自己处于 CI」的事实标记是 `CI=true` —— 每个主流提供商都会设置;chalk 读到它后,即便 isatty 失败也会启用基础 16 色输出。NO_COLOR(no-color.org)仍然优先级最高。
现代 CI 的 Web UI 大多会渲染 SGR —— 字节本身能穿过管道,关键是阻止工具自我剥离。
# Universal CI overrides — works across providers
CI=true # set automatically by every major provider
FORCE_COLOR=3 npm test # 3 = truecolor (chalk depth)
git --color=always log # explicit flag on tools that have one
script -qec 'pytest' /dev/null # wrap in a PTY when no flag existsGitHub Actions —— TERM=dumb,但 Web UI 渲染 SGR
Runner 设置 `TERM=dumb`,会让保守的工具剥离颜色。但 Web UI 自 2018 年起就能渲染 SGR 转义 —— 因此只要强制输出(Node 工具用 `FORCE_COLOR=3`、git/grep/ripgrep 用 `--color=always`),实时日志立刻变彩色。注解通道(`::error::`、`::warning::`、`::notice::`、`::group::name` / `::endgroup::`)独立于 SGR —— 它生成结构化的 PR 注解与可折叠日志段。原始日志下载(齿轮图标 → Download log archive,或通过 API 调 `GET /repos/{owner}/{repo}/actions/runs/{run_id}/logs`)保留 runner 写入的每一字节,含 ANSI —— 把存档喂给 `less -R` 或 `ansi2html` 即可事后重放彩色视图。
::group:: 生成可折叠的 UI 段;FORCE_COLOR=3 让基于 chalk 的输出保留颜色。
# .github/workflows/test.yml — keep colour through chalk-using tools
- name: Run tests
env:
FORCE_COLOR: 3
run: |
echo "::group::Unit tests"
npm test --color=always
echo "::endgroup::"
echo "::notice file=README.md,line=1::done"GitLab CI —— SGR 直接渲染,分段标记用 \r\e[0K 自我隐藏
GitLab 自 8.5(2016)起就在任务日志视图中渲染 SGR。其专有的结构化通道是可折叠分段标记:`section_start:<unix_ts>:<name>\r\x1b[0K<段头文本>` 与 `section_end:<unix_ts>:<name>\r\x1b[0K`。其中 `\r\x1b[0K` 是回车后跟 CSI 0 K(从光标擦至行尾)—— 当 GitLab 解析器**没**消费该标记时(旧版本或本地 cat 查看),尾部的 `\r` + 擦行会在同一行覆盖标记文本本身,所以字面上的 `section_start:…` 不会显示。原始 trace 端点(API 调 `GET /projects/:id/jobs/:job_id/trace`,或 UI 的 'Show complete raw')保留每个 ANSI 字节加这些标记,可以管道送给 `ansi2html` 或 `less -R` 离线审阅。
尾部的 \r\e[0K 会隐藏字面标记 —— GitLab UI 中仅保留渲染后的段头。
# .gitlab-ci.yml — emit a collapsible section
script:
- echo -e "section_start:`date +%s`:tests\r\e[0KUnit tests"
- FORCE_COLOR=3 npm test --color=always
- echo -e "section_end:`date +%s`:tests\r\e[0K"Jenkins —— 必须装 AnsiColor 插件;原始 'Console Output' 仍含字节
Jenkins 开箱即用时,Console Output 页面把转义字节按字面渲染(红色不显示,显示成 `^[[31m` 文本)。解决方法是 AnsiColor 插件:安装后,在 Declarative Pipeline 顶层用 `options { ansiColor('xterm') }`,或在 Scripted Pipeline 中包裹 `wrap([$class: 'AnsiColorBuildWrapper', colorMapName: 'xterm']) { … }`。经典 Freestyle 任务在装好插件后会出现「Color ANSI Console Output」复选框。插件只影响样式化视图 —— 原始的 'Console Output (text)' 链接仍输出未渲染字节,正适合 `wget` 后用 `less -R` 离线考古。colorMapName 选 `xterm`(256 色)是安全默认;`vga` 与 `css` 用于旧主题。
顶层 `options { ansiColor('xterm') }` 让整条流水线的每个阶段都启用渲染视图。
// Jenkinsfile — Declarative Pipeline
pipeline {
agent any
options { ansiColor('xterm') }
stages {
stage('test') {
steps {
sh 'FORCE_COLOR=3 npm test --color=always'
}
}
}
}Buildkite、CircleCI、Azure Pipelines —— 现代 CI 开箱渲染 SGR
Buildkite 日志查看器默认渲染 SGR,并通过 Markdown 风格的 `--- :package: header` 解析可折叠标记(老式 Buildkite 专有标记仍受支持)。CircleCI 2.0+ 在 Web UI 中渲染 ANSI;runner 设置 `TERM=xterm-256color`,所以依赖 TERM 判断的工具也保持彩色。Azure Pipelines 模仿 GitHub Actions 的日志命令语法,把 `::…::` 换成 `##[…]` 前缀 —— `##[group]name` / `##[endgroup]`、`##[error]message`、`##[warning]message`、`##[debug]message`。Travis CI(老旧但仍有部署)在部分默认镜像中设 `TERM=dumb`;与 `CI=true` 结合,chalk 仍会输出基础颜色。Bitbucket Pipelines 自 2018 起在日志 UI 中渲染 SGR。规律:2018 年后推出的 CI 都期望 ANSI;只有 Jenkins 仍需插件。
Azure 的 ##[…] 前缀只比 GitHub 的 ::…:: 多/少两个字符 —— 同时兼容两套 CI 很容易。
# Azure Pipelines — group + annotation
steps:
- script: |
echo "##[group]Unit tests"
FORCE_COLOR=3 npm test --color=always
echo "##[endgroup]"
echo "##[warning]flaky test skipped"isatty() 为假时强制保留颜色 —— 标志、环境变量、PTY 包装
粗暴程度递增的三层手段:(1) **显式 CLI 标志** —— `--color=always` 适用于 git、grep(GNU 与 BSD)、ripgrep、fd、eza、jq、ls(GNU coreutils 支持 `--color=always`)、jest、pytest 的 `--color=yes`、cargo 的 `--color=always`。最便宜也最可预测。(2) **环境变量强制** —— `FORCE_COLOR=3`(Node 真彩色)、`FORCE_COLOR=2`(256)、`FORCE_COLOR=1`(基础 16);所有用 chalk 的工具都尊重,越来越多 Rust 工具也跟进。`CLICOLOR_FORCE=1` 是 BSD 系的对应物(git、BSD ls、eza)。(3) **PTY 包装** —— 当工具既无标志也无环境变量钩子时,把它跑在伪终端里让 `isatty(stdout)` 返回真:`script -qec 'cmd' /dev/null`(util-linux)、`script -q /dev/null cmd`(BSD `script`)、`unbuffer cmd`(expect 包)。慎用:PTY 会略微降低吞吐,并可能让基于行缓冲、靠 close 时批量刷新的工具表现异常。
依次尝试标志、环境变量、PTY 包装 —— 侵入性逐层加重。
# Three escalating workarounds, in order
git --color=always log | tee log.txt # 1. flag
FORCE_COLOR=3 vitest run | tee out.log # 2. env var
script -qec 'apt-get install -y curl' /dev/null # 3. PTY wrap
# Combine for tools that need both
FORCE_COLOR=3 script -qec 'pytest' /dev/null本地查看下载的 CI 日志 —— cat -v、less -R、ansi2html
把原始 CI 日志下载到本地后(GitHub:齿轮 → Download log archive;GitLab:'Show complete raw';Jenkins:'Console Output (text)'),四个工具几乎覆盖所有工作流:`cat -v log.txt` 把控制字节显示为字面 caret 形式(`^[[31mERROR^[[0m`)—— 最适合调解析器或 grep 漏出的转义。`less -R log.txt` 渲染 SGR 颜色、其他控制码原样穿过 —— 适合任意日志文件。`less +F -R log.txt` 加上尾随跟随。`ansi2html < log.txt > log.html`(pip install ansi2html)输出内联 CSS 的自包含 HTML —— 最适合邮件/附件分享彩色日志。`aha < log.txt > log.html` 是 apt 可装的替代品。`ansifilter log.txt` 完全剥离 ANSI(或转为 HTML/LaTeX/RTF)。如果只需剥离,本站 /strip 也提供正则。
less -R 是日常主力 —— cat -v 用于精细调试,ansi2html 用于分享。
# 1. Inspect raw bytes (debug a stray escape)
cat -v build.log | head
# 2. Render in-terminal
less -R build.log
# 3. Convert to shareable HTML
ansi2html < build.log > build.html
aha --black < build.log > build.html # alternative
# 4. Strip entirely
ansifilter build.log > plain.log参见
/accessibility 更深入讲述环境变量契约(NO_COLOR、CLICOLOR、FORCE_COLOR 在非 CI 环境的语义);/strip 提供把日志送往不渲染的聚合器(Datadog / CloudWatch / Splunk)前剥离 ANSI 字节的正则;/pitfalls 覆盖长时间 CI 任务中颜色泄漏的现象层版本。