如何使 Jenkins 日志更可读
Jenkins 日志

> 
> Jenkins 的默认日志难以阅读，但日志本不必如此。
> 
> 
> 


![](/data/attachment/album/202105/15/093017vd34foo00wpy2yot.jpg "Person using a laptop")


Jenkins 是一个自由开源的自动化服务器，用于构建、测试和部署代码。它是<ruby> 持续集成 <rt>  Continuous Integration </rt></ruby>（CI）、<ruby> 持续交付 <rt>  Continuous Delivery </rt></ruby>（CD）的基础，可以为开发人员每天节约几小时，并保护他们免受失败的代码上线的影响。一旦代码失效或开发人员需要查看测试输出时，[Jenkins](https://www.jenkins.io/) 提供了日志文件以供检查。


默认的 Jenkins <ruby> 管道 <rt>  Pipeline </rt></ruby>日志可能难以阅读。这篇关于 Jenkins 日志的基础知识的总结文章提供了一些技巧（和代码），说明了如何提升它们的可读性。


### 你获得什么


Jenkins 管道分为 [几个阶段](https://www.jenkins.io/doc/book/pipeline/syntax/#stage)。Jenkins 自动记录每个阶段的开始，记录内容如下：



```
[Pipeline] // stage
[Pipeline] stage (hide)
[Pipeline] { (Apply all openshift resources)
[Pipeline] dir

```

上文显示的内容没有太大区分度，重要的内容（如阶段的开始）未突出显示。在多达数百行的管道日志中，要找到一个阶段的起始和另外一个阶段的终止位置可能会很艰巨。当随意浏览日志寻找一个特定的阶段的时候，这种艰巨尤其明显。


Jenkins 管道是由 [Groovy](https://opensource.com/article/20/12/groovy) 和 Shell 脚本混合编写的。在 Groovy 代码中，日志记录很少。很多时候，日志是由命令中的不起眼的文本组成，没有详细信息。在 Shell 脚本中，打开了调试模式（`set -x`），所以每条命令都会被完全<ruby> 具现化 <rt>  realized </rt></ruby>（变量被解除引用并打印出数值）并详细记录，输出也是如此。


鉴于日志可能有很多内容，通读日志获取相关信息可能很繁琐。由于在管道中被处理，并跟着一个 Shell 脚本的 Groovy 日志可读性差，它们很多时候缺少上下文：



```
[Pipeline] dir
Running in /home/jenkins/agent/workspace/devop-master/devops-server-pipeline/my-repo-dir/src
[Pipeline] { (hide)
[Pipeline] findFiles
[Pipeline] findFiles
[Pipeline] readYaml
[Pipeline] }

```

我可以知道我正在使用的目录，并且知道我正在使用 Jenkins 的步骤搜索文件、读取 YAML 文件。但是我在寻找什么？我找到并读取的内容是什么？


### 能做什么？


我很高兴你这么问，因为这里有一些简单的做法和一些小的代码片段可以提供帮助。首先，代码如下：



```
def echoBanner(def ... msgs) {
   echo createBanner(msgs)
}

def errorBanner(def ... msgs) {
   error(createBanner(msgs))
}

def createBanner(def ... msgs) {
   return """
       ===========================================

       ${msgFlatten(null, msgs).join("\n        ")}

       ===========================================
   """
}

// flatten function hack included in case Jenkins security
// is set to preclude calling Groovy flatten() static method
// NOTE: works well on all nested collections except a Map
def msgFlatten(def list, def msgs) {
   list = list ?: []
   if (!(msgs instanceof String) && !(msgs instanceof GString)) {
       msgs.each { msg ->
           list = msgFlatten(list, msg)
       }
   }
   else {
       list += msgs
   }

   return  list
}

```

将这段代码添加到每个管道的末尾，也可以 [加载一个 Groovy 文件](https://www.jenkins.io/doc/pipeline/steps/workflow-cps/#load-evaluate-a-groovy-source-file-into-the-pipeline-script) 或者使其成为 [Jenkins 共享库](https://www.jenkins.io/doc/book/pipeline/shared-libraries/) 的一部分，这样更有效。


在每个阶段起始处（或者在阶段中的特定位置），只需调用 `echoBanner`：



```
echoBanner("MY STAGE", ["DOING SOMETHING 1", "DOING SOMETHING 2"])

```

你的 Jenkins 日志会展示如下：



```
    ===========================================

    MY STAGE
    DOING SOMETHING 1
    DOING SOMETHING 2

    ===========================================

```

这个横幅很容易从日志中分辨出来。当正确使用它们时，它们还有助于界定管道流，并且可以很好的将日志分解开来进行阅读。


我已经在某些地方专业地使用这些代码一些时间了。在帮助管道日志更易读和流程更易理解方面，反馈是非常积极的。


上述的 `errorBanner` 方法以相同的方式工作，但是它会立即使脚本失效。这有助于突显失败的位置与原因。


### 最佳实践


1. 在你的 Groovy 代码中大量使用 `echo` Jenkins 步骤来通知用户你在做什么。这些也可以帮助记录你的代码。
2. 使用空的日志语句（Groovy 中空的 echo 步骤、`echo ''` 或 Shell 中的 `echo`）来分割输出，提高可读性。你可能在你的代码中为同样的目的使用空行。
3. 避免在脚本中使用 `set +x` 的陷阱，因为它隐藏了日志记录已执行的 Shell 语句。它并没有清理你的日志，而是使你的管道成为一个黑盒子，隐藏了管道正在做的行为以及出现的任何错误。确保管道功能尽可能透明。
4. 如果你的管道创建了<ruby> 中间工件 <rt>  Intermediate Artifacts </rt></ruby>，开发人员和 DevOps 人员可以使用这些工件来帮助调试问题，那么也要记录它的内容。是的，它会加长日志，但这只是文本。在某些时候，这会是有用的信息，而（利用得当的）日志不就是关于发生了什么和为什么发生的大量信息吗？


### Kubernetes 机密信息：无法完全透明的地方


有些事情你不希望出现在日志里暴露出来。如果你在使用 Kubernetes 并引用保存在 Kubernetes <ruby> 机密信息 <rt>  Secrets </rt></ruby>中的数据，那么你绝对不希望在日志中公开该数据，因为这些数据只是被混淆了，而没有被加密。


假如你想获取一些保存在机密信息中的数据，然后将其注入模板化 JSON 文件中。（机密信息和 JSON 模板的完整内容与此例无关。）按照最佳实践，你希望保持透明并记录你的操作，但你不想公开机密信息数据。


将脚本模式从调试（`set -x`）更改为命令记录（`set -v`）。在脚本敏感部分的结尾，将 Shell 重置为调试模式：



```
sh """
   # change script mode from debugging to command logging
   set +x -v

   # capture data from secret in shell variable
   MY_SECRET=\$(kubectl get secret my-secret --no-headers -o 'custom-column=:.data.my-secret-data')

   # replace template placeholder inline
   sed s/%TEMPLATE_PARAM%/${MY_SECRET_DATA}/ my-template-file.json

   # do something with modified template-file.json...

   # reset the shell to debugging mode
   set -x +v
"""

```

这将输出此行到日志：



```
sed s/%TEMPLATE_PARAM%/${MY_SECRET_DATA}/ my-template-file.json

```

与 Shell 调试模式中不同，这不会具现化 Shell 变量 `MY_SECRET_DATA`。显然，如果管道中在这一点出现问题，而你试图找出问题出在哪里，那么这不如调试模式有用。但这是在保持管道执行对开发人员和 DevOps 透明的同时，也保持你的秘密的最佳平衡。




---


via: <https://opensource.com/article/21/5/jenkins-logs>


作者：[Evan "Hippy" Slatis](https://opensource.com/users/hippyod) 选题：[lujun9972](https://github.com/lujun9972) 译者：[DCOLIVERSUN](https://github.com/DCOLIVERSUN) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
Jenkins is a free and open source automation server for building, testing, and deploying code. It's the backbone of continuous integration and continuous delivery (CI/CD) and can save developers hours each day and protect them from having failed code go live. When code does fail, or when a developer needs to see the output of tests, [Jenkins](https://www.jenkins.io/) provides log files for review.

The default Jenkins pipeline logs can be difficult to read. This quick summary of Jenkins logging basics offers some tips (and code) on how to make them more readable.

## What you get

Jenkins pipelines are split into [stages](https://www.jenkins.io/doc/book/pipeline/syntax/#stage). Jenkins automatically logs the beginning of each stage, like this:

```
[Pipeline] // stage
[Pipeline] stage (hide)
[Pipeline] { (Apply all openshift resources)
[Pipeline] dir
```



The text is displayed without much contrast, and important things (like the beginning of a stage) aren't highlighted. In a pipeline log several hundred lines long, finding where one stage starts and another ends, especially if you're casually browsing the logs looking for a particular stage, can be daunting.

Jenkins pipelines are written as a mix of [Groovy](https://opensource.com/article/20/12/groovy) and shell scripting. In the Groovy code, logging is sparse; many times, it consists of grayed-out text in the command without details. In the shell scripts, debugging mode (`set -x`

) is turned on, so every shell command is fully realized (variables are dereferenced and values printed) and logged in detail, as is the output.

It can be tedious to read through the logs to get relevant information, given that there can be so much. Since the Groovy logs that proceed and follow a shell script in a pipeline aren't very expressive, many times they lack context:

```
[Pipeline] dir
Running in /home/jenkins/agent/workspace/devop-master/devops-server-pipeline/my-repo-dir/src
[Pipeline] { (hide)
[Pipeline] findFiles
[Pipeline] findFiles
[Pipeline] readYaml
[Pipeline] }
```


I can see what directory I am working in, and I know I was searching for file(s) and reading a YAML file using Jenkins' steps. But what was I looking for, and what did I find and read?

## What can be done?

I'm glad you asked because there are a few simple practices and some small snippets of code that can help. First, the code:

```
def echoBanner(def ... msgs) {
echo createBanner(msgs)
}
def errorBanner(def ... msgs) {
error(createBanner(msgs))
}
def createBanner(def ... msgs) {
return """
===========================================
${msgFlatten(null, msgs).join("\n ")}
===========================================
"""
}
// flatten function hack included in case Jenkins security
// is set to preclude calling Groovy flatten() static method
// NOTE: works well on all nested collections except a Map
def msgFlatten(def list, def msgs) {
list = list ?: []
if (!(msgs instanceof String) && !(msgs instanceof GString)) {
msgs.each { msg ->
list = msgFlatten(list, msg)
}
}
else {
list += msgs
}
return list
}
```


Add this code to the end of each pipeline or, to be more efficient, [load a Groovy file](https://www.jenkins.io/doc/pipeline/steps/workflow-cps/#load-evaluate-a-groovy-source-file-into-the-pipeline-script) or make it part of a [Jenkins shared library](https://www.jenkins.io/doc/book/pipeline/shared-libraries/).

At the start of each stage (or at particular points within a stage), simply call `echoBanner`

:

`echoBanner("MY STAGE", ["DOING SOMETHING 1", "DOING SOMETHING 2"])`


Your logs in Jenkins will display the following:

```
===========================================
MY STAGE
DOING SOMETHING 1
DOING SOMETHING 2
===========================================
```


The banners are very easy to pick out in the logs. They also help define the pipeline flow when used properly, and they break the logs up nicely for reading.

I have used this for a while now professionally in a few places. The feedback has been very positive regarding helping make pipeline logs more readable and the flow more understandable.

The `errorBanner`

method above works the same way, but it fails the script immediately. This helps highlight where and what caused the failure.

## Best practices

- Use
`echo`

Jenkins steps liberally throughout your Groovy code to inform the user what you're doing. These can also help with documenting your code. - Use empty log statements (an empty echo step in Groovy,
`echo ''`

, or just`echo`

in shell) to break up the output for easier readability. You probably use empty lines in your code for the same purpose. - Avoid the trap of using
`set +x`

in your scripts, which hides logging executed shell statements. It doesn't so much clean up your logs as it makes your pipelines a black box that hides what your pipeline is doing and any errors that appear. Make sure your pipelines' functionality is as transparent as possible. - If your pipeline creates intermediate artifacts that developers and/or DevOps personnel could use to help debug issues, then log their contents, too. Yes, it makes the logs longer, but it's only text. It will be useful information at some point, and what else is a log (if utilized properly) than a wealth of information about what happened and why?

## Kubernetes Secrets: Where full transparency won't work

There are some things that you *don't* want to end up in your logs and be exposed. If you're using Kubernetes and referencing data held in a Kubernetes Secret, then you definitely don't want that data exposed in a log because the data is only obfuscated and not encrypted.

Imagine you want to take some data held in a Secret and inject it into a templated JSON file. (The full contents of the Secret and the JSON template are irrelevant for this example.) You want to be transparent and log what you're doing since that's best practice, but you don't want to expose your Secret data.

Change your script's mode from debugging (`set -x`

) to command logging (`set -v`

). At the end of the sensitive portion of the script, reset the shell to debugging mode:

```
sh """
# change script mode from debugging to command logging
set +x -v
# capture data from secret in shell variable
MY_SECRET=\$(kubectl get secret my-secret --no-headers -o 'custom-column=:.data.my-secret-data')
# replace template placeholder inline
sed s/%TEMPLATE_PARAM%/${MY_SECRET_DATA}/ my-template-file.json
# do something with modified template-file.json...
# reset the shell to debugging mode
set -x +v
"""
```


This will output this line to the logs:

`sed s/%TEMPLATE_PARAM%/${MY_SECRET_DATA}/ my-template-file.json`


This doesn't realize the shell variable `MY_SECRET_DATA`

, unlike in shell debug mode. Obviously, this isn't as helpful as debug mode if a problem occurs at this point in the pipeline and you're trying to figure out what went wrong. But it's the best balance between keeping your pipeline execution transparent for both developers and DevOps while also keeping your Secrets hidden.

## Comments are closed.