# 采集多行容器日志
在采集容器日志时，有些程序打印的日志存在一条完整日志数据跨占多行的情况（例如Java程序日志），而日志采集系统默认是按行采集，可能会出现采集到的日志每行显示不全的问题。
您可以开启多行文本采集，根据正则表达式匹配日志，从而采集到完整的日志数据。本文介绍如何配置多行日志采集策略，以及不同日志类型的注意事项。
您可以选择以下两种方式配置多行日志采集：
| 多行日志采集方式                                                                        | 说明                                    | 优点                           | 缺点                                          |
|:---|:---|:---|:---|
| [通过多行文本模式采集日志] | 通过行首正则表达式匹配一条日志的行首，未匹配部分则作为该条日志的一部分。 | 配置较为简单。                    | 在不同的日志采集场景下需要注意不同的事项，且部分场景可能存在一定的性能瓶颈，约束较多。 |
| [使用多行解析模式采集日志]  | 先对日志进行解析，再对日志进行多行合并。                 | 在处理标准输出时，流程更简单，性能更好，约束更少。 | 配置相对复杂。                                    |
   
#### 前提条件
集群中需要安装云原生日志采集插件，并启用日志中心功能。
 #### 通过多行文本模式采集日志
在多行文本模式下，云原生日志采集插件将通过行首正则表达式去匹配一条日志的行首，未匹配部分则作为该条日志的一部分。云原生日志采集插件会将日志内容存放在*content*字段中，不会对日志中的字段进行提取。
**不同场景下多行文本配置注意事项如下：**
#### 日志类型为容器文件日志
此类日志因为原始文件就是采集文件，所以只要正则能在首行匹配上内容，绝大多数情况下均能正常采集。
- **注意事项1：**若正则表达式匹配到的首行日志是从中间开始匹配，那么首行会缺少前面未匹配到的部分，如仅填写"\\d+-\\d+-\\d+ \\d+:\\d+:\\d+.\*"，则匹配到的日志内容中将缺少"time="。
- **注意事项2：** 若正则表达式过于简单，且在非首行也存在能匹配到的内容，则可能造成日志被截断。例如正则表达式中仅包含匹配时间的表达式"**\\d+-\\d+-\\d+ \\d+:\\d+:\\d+.\*** "，而日志中在第四行存在相同格式的时间：
  ```
  time=2025-04-01 16:33:06.254 level=info msg=Exception in thread "main" java.lang.RuntimeException: Something has gone wrong, aborting! at com.myproject.module.MyProject.badMethod(MyProject.java:22) at com.myproject.module.MyProject.oneMoreMethod(MyProject.java:18) at com.myproject.module.MyProject.anotherMethod(MyProject.java:14) at com.myproject.module.MyProject.someMethod(MyProject.java:10) 2025-04-01 15:24:30.199 at com.myproject.module.MyProject.someMethod(MyProject.java:10)(MyProject.java:10).java:10) at com.myproject.module.MyProject.main(MyProject.java:6) func=main.writeLog file=D:/cia-tools/cmd/benchmark/log-tool/log.go:96 inputNumber=1
  ```
  那么上报的日志最终结果将会被截断，且第5行的日志前面部分丢失：
  ![](https://support.huaweicloud.com/usermanual-cce-autopilot/zh-cn_image_0000002516176201.png "点击放大")
  
 
#### 日志类型为容器标准输出
标准输出日志会在用户输出日志的基础上增加日志内容，同样需要经过特殊处理，因此部分正则表达式需要注意是否可以生效。由于处理相对复杂，因此containerd下的多行标准输出性能较其他日志有所下降。若当前性能无法满足需求，可以参考[使用多行解析模式采集日志]进行配置。
在本文的日志示例中，实际采集的原始文件日志为：
```
2025-03-28T17:22:44.052300591+08:00 stdout F time=2025-03-28 17:22:44.052 level=info msg=Exception in thread "main" java.lang.RuntimeException: Something has gone wrong, aborting!
2025-03-28T17:22:44.052327792+08:00 stdout F at com.myproject.module.MyProject.badMethod(MyProject.java:22)
2025-03-28T17:22:44.052330808+08:00 stdout F at com.myproject.module.MyProject.oneMoreMethod(MyProject.java:18)
2025-03-28T17:22:44.052332771+08:00 stdout F at com.myproject.module.MyProject.anotherMethod(MyProject.java:14)
2025-03-28T17:22:44.052334906+08:00 stdout F at com.myproject.module.MyProject.someMethod(MyProject.java:10)
2025-03-28T17:22:44.05233719+08:00 stdout F at com.myproject.module.MyProject.someMethod(MyProject.java:10)(MyProject.java:10).java:10)
2025-03-28T17:22:44.052339194+08:00 stdout F at com.myproject.module.MyProject.main(MyProject.java:6) func=main.writeLog file=D:/cia-tools/cmd/benchmark/log-tool/log.go:96 inputNumber=3
```
- **注意事项1：**正则表达式不可以匹配上containerd自带的时间，如当前正则表达式为"\\d+-\\d+\\d+T\\d+:\\d+:\\d+.\*"，则每条日志均可以被该正则表达式匹配上，则多行采集功能失效。
- **注意事项2：**不可以使用"\^"字符，使用后会导致无法正常匹配首行日志。
- **注意事项3：** 若正则表达式匹配到的首行日志是从中间开始匹配，那么首行会缺少前面未匹配到的部分，如仅填写"**\\d+-\\d+-\\d+ \\d+:\\d+:\\d+.\*** "，则匹配到的日志内容中将缺少"time="。
  ![](https://support.huaweicloud.com/usermanual-cce-autopilot/zh-cn_image_0000002516056197.png "点击放大")
  
 
通过多行文本模式采集日志的操作步骤如下：
1. 登录[CCE控制台](https://console.huaweicloud.com/cce2.0/?#/cce/cluster/list)，单击集群名称进入集群，选择左侧导航栏的"日志中心"。
2. 右上角单击"日志采集策略"，单击"创建日志采集策略"。
3. 选择"**自定义策略** "，其中"策略名称"、"日志类型"、"日志源"等信息请根据实际需求填写。
   
   图1自定义策略   
   ![](https://support.huaweicloud.com/usermanual-cce-autopilot/zh-cn_image_0000002485983068.png "点击放大")
   
   
4. 在"日志格式"配置中选择"多行文本"，并输入能匹配首行规则的正则表达式。 
   例如，以下日志首行格式固定为 **time={时间} {日志内容}** 开头，则正则规则可填写为 **time=\\d+-\\d+-\\d+ \\d+:\\d+:\\d+.\***。
   ```
   time=2025-04-01 15:24:30.199 level=info msg=Exception in thread "main" java.lang.RuntimeException: Something has gone wrong, aborting!
   at com.myproject.module.MyProject.badMethod(MyProject.java:22)
   at com.myproject.module.MyProject.oneMoreMethod(MyProject.java:18)
   at com.myproject.module.MyProject.anotherMethod(MyProject.java:14)
   at com.myproject.module.MyProject.someMethod(MyProject.java:10)
   at com.myproject.module.MyProject.someMethod(MyProject.java:10)(MyProject.java:10).java:10)
   at com.myproject.module.MyProject.main(MyProject.java:6) func=main.writeLog file=D:/cia-tools/cmd/benchmark/log-tool/log.go:96 inputNumber=56505
   ```
   其中，"time=" 为固定内容，"\\d+-\\d+-\\d+ \\d+:\\d+:\\d+"匹配时间，如 "2025-04-01 15:24:30"。".\*"匹配的是时间后面的任意字符。
   
   
5. 选择上报到LTS的日志组/日志流后，单击"确定"。 
   在LTS中查看采集到的日志如下：
   ![](https://support.huaweicloud.com/usermanual-cce-autopilot/zh-cn_image_0000002483936250.png "点击放大")
   
   
 #### 使用多行解析模式采集日志
![](https://support.huaweicloud.com/usermanual-cce-autopilot/public_sys-resources/caution_3.0-zh-cn.png)
该功能仅支持云原生日志采集插件1.7.3及以上版本。
多行解析模式下，多行日志采集的配置相对复杂，但由于该模式先对日志进行解析再对日志进行多行合并，使得该模式在处理标准输出时，流程更简单，性能更好，约束更少。
#### 基本原理
云原生日志采集插件是基于fluent-bit实现日志的采集，从开源1.8.0版本开始，fluent-bit提供了新的多行日志采集能力，采集配置示例如下：
```
[MULTILINE_PARSER]
        name          multiline-regex-test
        type          regex
        flush_timeout 1000
        #
        # Regex rules for multiline parsing
        # ---------------------------------
        #
        # configuration hints:
        #
        #  - first state always has the name: start_state
        #  - every field in the rule must be inside double quotes
        #
        # rules |   state name  | regex pattern                  | next state
        # ---|---|--------------------------------------------
        rule      "start_state"   "/time=\d+-\d+-\d+ \d+:\d+:\d+.*/"  "cont"
        rule      "cont"          "/^(?!time=\d+-\d+-\d+ \d+:\d+:\d+.*).*$/" "cont"
```
相比起多行文本模式只需要首行日志的正则表达式，多行解析配置需要每行日志的正则表达式，并将其列出，通过state_name和next_state将正则表达式串联起来，实现多行日志合并的功能。
针对标准输出日志，多行解析提供parser的能力，可以先对日志进行解析，再对日志进行多行合并，这样就不会因为标准输出自带的格式对日志内容的正则表达式造成影响。以下是标准输出最终生成的配置。
```
[MULTILINE_PARSER]
        name          multiline-regex-test
        type          regex
        parser        cri
        key_content   log
        flush_timeout 5000
        #
        # Regex rules for multiline parsing
        # ---------------------------------
        #
        # configuration hints:
        #
        #  - first state always has the name: start_state
        #  - every field in the rule must be inside double quotes
        #
        # rules |   state name  | regex pattern                  | next state
        # ---|---|--------------------------------------------
        rule      "start_state"   "/time=\d+-\d+-\d+ \d+:\d+:\d+.*/"  "cont"
        rule      "cont"          "/^(?!time=\d+-\d+-\d+ \d+:\d+:\d+.*).*$/" "cont"
    [PARSER]
        Name        cri
        Format      regex
        Regex       ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<log>.*)$
        Time_Key    time
        Time_Format %Y-%m-%dT%H:%M:%S.%L%z
```
通过多行解析模式采集日志的操作步骤如下：
1. 请参见[CCE控制台](https://console.huaweicloud.com/cce2.0/?#/cce/cluster/list)，使用kubectl连接集群。
2. 创建名为"**log-config.yaml** "的YAML文件，此处文件名可自定义。
   
   ```
   vi log-config.yaml
   ```
   以采集指定工作负载的标准输出为例，YAML配置示例如下：
   ```
   apiVersion: logging.openvessel.io/v1
   kind: LogConfig
   metadata:
     name: test-log-02  # 规则名称按需修改
     namespace: kube-system  # 采集规则命名空间，固定为kube-system
   spec:
     inputDetail:  # 输入配置
       type: container_file   # 输入类型，container_file表示容器内日志文件
       containerFile:    # 容器内文件日志相关配置，仅当type为container_file时生效，其他type无需该字段
         workloads:        # 工作负载信息需要按照实际情况修改
         - namespace: monitoring  # 工作负载所属命名空间
           kind: Deployment  # 工作负载类型
           name: prometheus-lightweight  # 工作负载名称
           container: prometheus  # 容器名称
           files:
           - logPath: "/var/log"  # 日志目录，绝对路径
             filePattern: "*.log"  # 日志文件名，支持通配
       processors:    # 多行日志定义
         type: multiline_parser  # 多行类型，固定值，multiline_parser
         multilineParsers: 
         - type: regex               # 固定值，regex
           flushTimeout: 5000   # 刷新多行缓冲区的超时时间，单位为毫秒，默认为5000毫秒
           rules:
           - stateName: start_state # 首个多行规则的名称，必须为 start_state
             regex: /time=\d+-\d+-\d+ \d+:\d+:\d+.*/  # 首行正则表达式，以斜杠开头，以斜杠结尾
             nextState: cont                # 下一行正则表达式的名称，可自定义，但必须下面有这个stateName
           - stateName: cont
             regex: /^(?!time=\d+-\d+-\d+ \d+:\d+:\d+.*).*$/  # 非首行的正则表达式
             nextState: cont
     outputDetail:  # 输出配置
       type: LTS    # 输出类型，固定LTS
       LTS:
         ltsGroupID: abf5f0ad-627e-41cc-8d3f-61c9e1f57f5a      # LTS日志组ID，指定的ID必须存在
         ltsStreamID: f7ed71e9-6b9d-4ba3-86e4-b1b9d22ef4fb     # LTS日志流ID，指定的ID必须存在
   ```
   若非首行日志比较复杂，不知道正则，可使用"**/\^(?!{首行正则表达式}).\*$/**"，该正则可满足大部分匹配非首行日志场景，若出现不匹配情况，请自行调整。
   
   
3. 创建LogConfig。 
   ```
   kubectl create -f log-config.yaml
   ```
   回显如下，表示LogConfig已创建。
   ```
   logconfig.logging.openvessel.io/test-log-xx created
   ```
   
   
4. 查看已创建的LogConfig。 
   ```
   kubectl get LogConfig -n kube-system
   ```
   回显如下，表示日志采集策略创建成功。
   ```
   NAME                AGE
   test-log-xx         30s
   ```
   
   
 
