2、CI失败日志入门:日志格式、日志级别、常见CI日志输出

说实话,刚接触CI那会儿,我压根没把日志当回事。觉得只要跑出绿色勾就行,红了就往上翻两页,找找红色的Error行。后来有一次半夜被叫起来,说Jenkins构建卡了六个小时,我看日志只看到一堆重复的“Waiting”…嗯,从那以后我才明白:读懂日志格式,是定位问题的第一步。这一节我们就来聊聊日志长什么样、怎么分级,以及三大主流CI系统——Jenkins、GitLab CI、GitHub Actions——它们各自输出的日志有什么特点。

日志的基本骨架

不管什么CI系统,一条典型的日志行通常包含这几个部分:

  • 时间戳 – 精确到毫秒,帮你锁定问题发生的时序。
  • 日志级别 – DEBUG / INFO / WARNING / ERROR / FATAL,表明严重程度。
  • 源标识 – 模块名、线程名、或CI中的Stage/Job名称。
  • 消息体 – 具体描述,可能是编译输出、测试结果或错误信息。
  • 异常堆栈 – 有时会跟着多行traceback,这是归因分析的核心素材。
我的习惯: 拿到一坨日志,先扫时间戳和级别。如果全是INFO没什么好看的,直接过滤出ERROR和WARNING。我个人还会把时间戳对齐到同一时区,不然跨团队的CI日志会看串。

日志级别——别小看了这几个字母

每个级别都有明确的目的,但在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控制台把WARNING标成黄色,ERROR标成红色,但你如果只扫红色,可能会漏掉那些被错误标成WARNING的致命问题。我建议统一用grep或者日志分析工具按严重程度排序。

三大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日志特点对比

特性JenkinsGitLab CIGitHub 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日志结构概览 CI日志结构 时间戳 日志级别 源标识 消息体 异常堆栈 Jenkins [Pipeline] 标记,exit code GitLab CI Section 折叠,相对时间 GitHub Actions ##[error] 标记,ISO时间 归因分析:从结构提取时间轴、级别链、异常堆栈 同一套逻辑可适配三种CI输出

这张图把本章的核心内容串起来了:日志的五个基本要素,以及它们如何映射到三种CI系统的具体输出。你以后看到任何CI日志,都可以先在心里拆成这些组件,然后再去分析。嗯,说实话这个习惯我保持了好几年,每次排查问题都事半功倍。

一个小技巧: 如果你刚开始接触某个CI系统的日志,别急着看全部。先搜索一个关键词,比如"error"(大小写敏感),然后看它周围的5行上下文。大多数时候,错误信息就在那几行里。