2、CI失败日志入门:日志格式、日志级别、常见CI日志输出
说实话,刚接触CI那会儿,我压根没把日志当回事。觉得只要跑出绿色勾就行,红了就往上翻两页,找找红色的Error行。后来有一次半夜被叫起来,说Jenkins构建卡了六个小时,我看日志只看到一堆重复的“Waiting”…嗯,从那以后我才明白:读懂日志格式,是定位问题的第一步。这一节我们就来聊聊日志长什么样、怎么分级,以及三大主流CI系统——Jenkins、GitLab CI、GitHub Actions——它们各自输出的日志有什么特点。
日志的基本骨架
不管什么CI系统,一条典型的日志行通常包含这几个部分:
- 时间戳 – 精确到毫秒,帮你锁定问题发生的时序。
- 日志级别 – DEBUG / INFO / WARNING / ERROR / FATAL,表明严重程度。
- 源标识 – 模块名、线程名、或CI中的Stage/Job名称。
- 消息体 – 具体描述,可能是编译输出、测试结果或错误信息。
- 异常堆栈 – 有时会跟着多行traceback,这是归因分析的核心素材。
日志级别——别小看了这几个字母
每个级别都有明确的目的,但在CI场景下,我见过不少团队滥用级别。
| 级别 | 含义 | CI中常见场景 |
|---|---|---|
| DEBUG | 调试信息,开发阶段用 | 展示环境变量、临时文件路径等,生产CI几乎不保留 |
| INFO | 正常流程提示 | “Cloning repository…”、“Building stage1…” |
| WARNING | 潜在风险,不影响当前执行 | “Deprecated API used”、“Test skipped due to config” |
| ERROR | 操作失败,任务可能中断 | “Compilation failed”、“Test assertion error” |
| FATAL | 严重错误,CI进程直接退出 | “Cannot connect to Docker daemon”、“Out of memory” |
为什么会单独强调这个?我踩过一个坑:有次GitLab CI里一个Job里面全是WARNING级别的“file not found”,但因为没有ERROR,CI最后标了绿色通过。实际上那个Job根本没跑完测试,只是脚本用echo写了Warning而已。后来我强制要求在脚本里用exit 1配合ERROR级别输出,才把这类问题揪出来。
三大CI系统的日志输出风格
不同CI系统输出的日志格式差异挺大,我给你贴几个真实的片段。
Jenkins
Jenkins的日志最传统,一般是纯文本加上时间戳。它的<pre>块里经常混着环境变量dump和Maven/Gradle的彩色输出(但保存到文件时颜色会被剥离)。
Started by user admin
Running on Jenkins-node-1 in /var/lib/jenkins/workspace/proj
[Pipeline] stage
[Pipeline] { (Build)
[Pipeline] sh
+ make
gcc -c main.c -o main.o
main.c:12:5: error: ‘undefined’ undeclared (first use in this function)
undefined_var = 1;
^~~~~~~~~
make: *** [main.o] Error 1
[Pipeline] }
[Pipeline] // stage
[Pipeline] End of Pipeline
ERROR: script returned exit code 2
Finished: FAILURE
你看,Jenkins用[Pipeline]标注阶段切换,用+显示实际执行的shell命令。它的错误信息分散在中间,最后的ERROR只是笼统的说exit code。我最头疼的是当输出超过几万行时,那个ERROR行往往在最后,但真正的编译错误可能在中间。
GitLab CI
GitLab CI的日志结构相对更干净,每个Job都被分割成多个Section,用Section start/end标记。它还支持Ansi颜色码,日志里会看到\e[0m这种控制符。
Running with gitlab-runner 15.0.0 on docker-runner 1234abcd
Preparing the "docker" executor
00:00
Using Docker executor with image python:3.10 ...
Pulling docker image python:3.10 ...
00:05
$ echo "Running tests..."
Running tests...
$ pytest
=================================== FAILURES ===================================
_________ test_add _________
def test_add():
> assert 1 + 1 == 3
E assert 2 == 3
test_demo.py:5: AssertionError
=================================== short test summary info ====================
FAILED test_demo.py::test_add - assert 2 == 3
Job failed: exit code 1
GitLab的日志有一个好处:它会自动折叠每个命令的输出,展开后你能看到精确到秒的耗时。我经常利用这个特性来定位哪个步骤花了最长时间。
GitHub Actions
Actions的日志是Web风格的,有##[group]和##[error]这样的标记,方便GitHub UI分组展示。你在原始日志里会看到很多带双井号的行。
##[group]Set up job
Current runner version: '2.304.0'
##[endgroup]
##[group]Build
Run echo "Building..."
Building...
##[error]src/app.cpp:42:10: fatal error: missing_header.h: No such file or directory
##[endgroup]
##[group]Test
Run pytest
##[error]test_app.py::test_login -- AssertionError
##[endgroup]
Post job cleanup.
##[error]Process completed with exit code 1.
我会特别关注##[error]行,它直接告诉你错误位置。注意Actions的日志里有时会混入##[warning],但UI上默认只高亮error。我曾经有一次漏看了##[warning]里的“Disk space low”,结果后续构建全部超时——你想想看,低级错误往往就藏在这些细节里。
三种CI日志特点对比
| 特性 | Jenkins | GitLab CI | GitHub Actions |
|---|---|---|---|
| 时间戳格式 | 自然语言 / 毫秒时间戳 | MM:SS 相对时间 | UTC ISO 8601 |
| 阶段标记 | [Pipeline] / 缩进 | Section start/end 块 | ##[group] ... ##[endgroup] |
| 错误定位 | 滚动搜索 "error:" | Job failed 行 + pytest摘要 | ##[error] 直接标记 |
| 级别分类 | 大部分纯文本,靠 exit code | 有颜色但无显式级别字段 | 内置 error/warning 标记 |
| 可读性(原生) | 中等,行数多了很乱 | 较好,折叠后清晰 | 较好,但依赖UI渲染 |
说白了,没有哪个系统天生就让你一眼看出根因。但了解它们的日志结构,你就知道该用什么工具去解析、过滤、聚合。我个人会写一个简单的Python脚本,把三种日志统一转成相同的JSON格式,然后再做归因分析。这个后面会详细讲。
本章知识体系图
这张图把本章的核心内容串起来了:日志的五个基本要素,以及它们如何映射到三种CI系统的具体输出。你以后看到任何CI日志,都可以先在心里拆成这些组件,然后再去分析。嗯,说实话这个习惯我保持了好几年,每次排查问题都事半功倍。