

# 连接多行或堆栈跟踪 Amazon ECS 日志消息
<a name="firelens-concatanate-multiline"></a>

从 AWS for Fluent Bit 版本 2.22.0 开始，包含多行筛选条件。多行筛选条件有助于连接原属于一个上下文但被分割为多个记录或日志行的日志消息。有关多行筛选条件的更多信息，请参阅 [Fluent Bit 文档](https://docs.fluentbit.io/manual/pipeline/filters/multiline-stacktrace)。

拆分日志消息的常见示例有：
+ 堆栈跟踪。
+ 在多行上打印日志的应用程序。
+ 因为比指定的运行时间最大缓冲区大小长而拆分的日志消息。您可以按照 GitHub 中的以下示例来连接被容器运行时拆分的日志消息：[FireLens Example: Concatenate Partial/Split Container Logs](https://github.com/aws-samples/amazon-ecs-firelens-examples/tree/mainline/examples/fluent-bit/filter-multiline-partial-message-mode)（FireLens 示例：连接部分/拆分的容器日志）。

## 所需的 IAM 权限
<a name="iam-permissions"></a>

您拥有使用容器代理从 Amazon ECR 中提取容器映像以及使用容器将日志路由到 CloudWatch Logs 的所需 IAM 权限。

对于这些权限，您还必须具有以下角色：
+ 任务 IAM 角色。
+ 任务执行 IAM 角色。

您需要以下权限：
+ `logs:CreateLogStream`
+ `logs:CreateLogGroup`
+ `logs:PutLogEvents`

## 确定何时使用多行日志设置
<a name="determine-filter"></a>

以下是您将在使用默认日志设置的 CloudWatch Logs 控制台中看到的示例日志片段。您可以查看以 `log` 开头的行，以确定是否需要多行筛选条件。当上下文相同时，您可以使用多行日志设置，在本例中，上下文为“com.myproject.model.MyProject”。

```
2022-09-20T15:47:56:595-05-00                           {"container_id": "82ba37cada1d44d389b03e78caf74faa-EXAMPLE", "container_name": "example-app", "source=": "stdout", "log": ": "     at com.myproject.modele.(MyProject.badMethod.java:22)",
    {
      "container_id":  "82ba37cada1d44d389b03e78caf74faa-EXAMPLE",
      "container_name: ": "example-app",
      "source": "stdout",
      "log": ": "     at com.myproject.model.MyProject.badMethod(MyProject.java:22)",
      "ecs_cluster": "default",
      "ecs_task_arn": "arn:aws:region:123456789012:task/default/b23c940d29ed4714971cba72cEXAMPLE",
      "ecs_task_definition": "firelense-example-multiline:3"
     }
```

```
2022-09-20T15:47:56:595-05-00                           {"container_id": "82ba37cada1d44d389b03e78caf74faa-EXAMPLE", "container_name": "example-app", "stdout", "log": ": "     at com.myproject.modele.(MyProject.oneMoreMethod.java:18)",
    {
      "container_id":  "82ba37cada1d44d389b03e78caf74faa-EXAMPLE",
      "container_name: ": "example-app",
      "source": "stdout",
      "log": ": "     at com.myproject.model.MyProject.oneMoreMethod(MyProject.java:18)",
      "ecs_cluster": "default",
      "ecs_task_arn": "arn:aws:region:123456789012:task/default/b23c940d29ed4714971cba72cEXAMPLE,
      "ecs_task_definition": "firelense-example-multiline:3"
     }
```

使用多行日志设置后，输出将与以下示例类似。

```
2022-09-20T15:47:56:595-05-00                           {"container_id": "82ba37cada1d44d389b03e78caf74faa-EXAMPLE", "container_name": "example-app", "stdout",...
    {
      "container_id":  "82ba37cada1d44d389b03e78caf74faa-EXAMPLE",
      "container_name: ": "example-app",
      "source": "stdout",
      "log:    "September 20, 2022 06:41:48 Exception in thread \"main\" java.lang.RuntimeException: Something has gone wrong, aborting!\n    
    at com.myproject.module.MyProject.badMethod(MyProject.java:22)\n    at   
    at com.myproject.model.MyProject.oneMoreMethod(MyProject.java:18) com.myproject.module.MyProject.main(MyProject.java:6)",
      "ecs_cluster": "default",
      "ecs_task_arn": "arn:aws:region:123456789012:task/default/b23c940d29ed4714971cba72cEXAMPLE",
      "ecs_task_definition": "firelense-example-multiline:2"
     }
```

## 解析和连接选项
<a name="parse-multiline-log"></a>

要解析日志并连接因换行符而被拆分的行，您可以使用这两个选项之一。
+ 使用自己的解析器文件，该文件中包含用于解析和连接属于同一消息的行的规则。
+ 使用 Fluent Bit 内置解析器。有关 Fluent Bit 内置解析器支持的语言的列表，请参阅 [Fluent Bit 文档。](https://docs.fluentbit.io/manual/pipeline/filters/multiline-stacktrace)

以下教程将引导您完成每个使用案例的步骤。这些步骤向您展示了如何连接多行并将日志发送到 Amazon CloudWatch。您可以为日志指定其他目标。

### 示例：使用您创建的解析器
<a name="customer-parser"></a>

在本示例中，您将完成以下步骤：

1. 为 Fluent Bit 容器生成并上传映像。

1. 为运行、失败和生成多行堆栈跟踪的演示多行应用程序生成并上传映像。

1. 创建任务定义并运行任务。

1. 查看日志以验证跨越多行的消息是否连接起来。

**为 Fluent Bit 容器生成并上传映像**

此映像将包括您在其中指定正则表达式的解析器文件和引用解析器文件的配置文件。

1. 使用名称 `FluentBitDockerImage` 创建文件夹。

1. 在文件夹内，创建解析器文件，其中包含用于解析日志和连接属于同一消息的行的规则。

   1. 将以下内容粘贴到解析器文件中：

      ```
      [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"   "/(Dec \d+ \d+\:\d+\:\d+)(.*)/"  "cont"
          rule      "cont"          "/^\s+at.*/"                     "cont"
      ```

      自定义正则表达式模式时，我们建议您使用正则表达式编辑器来测试表达式。

   1. 将该文件保存为 `parsers_multiline.conf`。

1. 在 `FluentBitDockerImage` 文件夹中，创建引用您在上一步中创建的解析器文件的自定义配置文件。

   有关自定义配置文件的更多信息，请参阅《Amazon Elastic Container Service 开发人员指南》**中的[指定自定义配置文件](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/firelens-taskdef.html#firelens-taskdef-customconfig) 

   1. 将以下内容粘贴到该文件中：

      ```
      [SERVICE]
          flush                 1
          log_level             info
          parsers_file          /parsers_multiline.conf
          
      [FILTER]
          name                  multiline
          match                 *
          multiline.key_content log
          multiline.parser      multiline-regex-test
      ```
**注意**  
您必须使用解析器的绝对路径。

   1. 将该文件保存为 `extra.conf`。

1. 在 `FluentBitDockerImage` 文件夹中，使用 Fluent Bit 映像以及您创建的解析器和配置文件创建 Dockerfile。

   1. 将以下内容粘贴到该文件中：

      ```
      FROM public.ecr.aws/aws-observability/aws-for-fluent-bit:latest
      
      ADD parsers_multiline.conf /parsers_multiline.conf
      ADD extra.conf /extra.conf
      ```

   1. 将该文件保存为 `Dockerfile`。

1. 使用 Dockerfile，构建一个包含解析器和自定义配置文件的自定义 Fluent Bit 映像。
**注意**  
您可以将解析器文件和配置文件放置在 Docker 映像中的任何位置，但 `/fluent-bit/etc/fluent-bit.conf` 除外，因为 FireLens 使用这个文件路径。

   1. 生成镜像：`docker build -t fluent-bit-multiline-image.`

      其中：`fluent-bit-multiline-image` 是此示例中映像的名称。

   1. 验证是否已正确创建映像：`docker images —filter reference=fluent-bit-multiline-image`

      如果成功，输出将显示该映像和 `latest` 标签。

1. 将自定义 Fluent Bit 映像上传到 Amazon Elastic Container Registry。

   1. 创建用于存储映像的 Amazon ECR 存储库：`aws ecr create-repository --repository-name fluent-bit-multiline-repo --region us-east-1`

      其中：`fluent-bit-multiline-repo` 是存储库的名称，`us-east-1` 是此示例中的区域。

      输出为您提供了新存储库的详细信息。

   1. 使用上一个输出中的 `repositoryUri` 值标记您的映像：`docker tag fluent-bit-multiline-image repositoryUri`

      示例：`docker tag fluent-bit-multiline-image xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com/fluent-bit-multiline-repo`

   1. 运行 Docker 镜像以验证它是否正确运行：`docker images —filter reference=repositoryUri`

      在输出中，存储库名称从 fluent-bit-multiline-repo 更改为 `repositoryUri`。

   1. 通过运行 `aws ecr get-login-password` 命令并指定您要对其进行身份验证的注册表 ID 对 Amazon ECR 进行身份验证：`aws ecr get-login-password | docker login --username AWS --password-stdin registry ID.dkr.ecr.region.amazonaws.com`

      示例：`ecr get-login-password | docker login --username AWS --password-stdin xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com`

      此时会显示成功登录消息。

   1. 将映像推送到 Amazon ECR：`docker push registry ID.dkr.ecr.region.amazonaws.com/repository name`

      示例：`docker push xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com/fluent-bit-multiline-repo`

**为演示多行应用程序生成并上传映像**

此映像将包括运行应用程序的 Python 脚本文件和示例日志文件。

运行任务时，应用程序会模拟运行，然后失败并创建堆栈跟踪。

1. 创建名为 `multiline-app` 的文件夹：`mkdir multiline-app`

1. 创建 Python 脚本文件。

   1. 在 `multiline-app` 文件夹中，创建一个文件并将其命名为 `main.py`。

   1. 将以下内容粘贴到该文件中：

      ```
      import os
      import time
      file1 = open('/test.log', 'r')
      Lines = file1.readlines()
       
      count = 0
      
      for i in range(10):
          print("app running normally...")
          time.sleep(1)
      
      # Strips the newline character
      for line in Lines:
          count += 1
          print(line.rstrip())
      print(count)
      print("app terminated.")
      ```

   1. 保存 `main.py` 文件。

1. 创建示例日志文件。

   1. 在 `multiline-app` 文件夹中，创建一个文件并将其命名为 `test.log`。

   1. 将以下内容粘贴到该文件中：

      ```
      single line...
      Dec 14 06:41:08 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.main(MyProject.java:6)
      another line...
      ```

   1. 保存 `test.log` 文件。

1. 在 `multiline-app` 文件夹中，创建 Dockerfile。

   1. 将以下内容粘贴到该文件中：

      ```
      FROM public.ecr.aws/amazonlinux/amazonlinux:latest
      ADD test.log /test.log
      
      RUN yum upgrade -y && yum install -y python3
      
      WORKDIR /usr/local/bin
      
      COPY main.py .
      
      CMD ["python3", "main.py"]
      ```

   1. 保存 `Dockerfile` 文件。

1. 使用 Dockerfile 生成映像。

   1. 生成映像：`docker build -t multiline-app-image `

      其中：`multiline-app-image` 是此示例中映像的名称。

   1. 验证是否已正确创建映像：`docker images —filter reference=multiline-app-image`

      如果成功，输出将显示该映像和 `latest` 标签。

1. 将映像上载到 Amazon Elastic Container 注册表。

   1. 创建用于存储映像的 Amazon ECR 存储库：`aws ecr create-repository --repository-name multiline-app-repo --region us-east-1`

      其中：`multiline-app-repo` 是存储库的名称，`us-east-1` 是此示例中的区域。

      输出为您提供了新存储库的详细信息。记下 `repositoryUri` 值，您将需要在后续步骤中使用该值。

   1. 使用上一个输出中的 `repositoryUri` 值标记您的映像：`docker tag multiline-app-image repositoryUri`

      示例：`docker tag multiline-app-image xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com/multiline-app-repo`

   1. 运行 Docker 映像以验证它是否正确运行：`docker images —filter reference=repositoryUri`

      在输出中，存储库名称从 `multiline-app-repo` 更改为 `repositoryUri` 值。

   1. 将映像推送到 Amazon ECR：`docker push aws_account_id.dkr.ecr.region.amazonaws.com/repository name`

      示例：`docker push xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com/multiline-app-repo`

**创建任务定义并运行任务**

1. 使用文件名 `multiline-task-definition.json` 创建任务定义文件。

1. 将以下内容粘贴到 `multiline-task-definition.json` 文件中：

   ```
   {
       "family": "firelens-example-multiline",
       "taskRoleArn": "task role ARN,
       "executionRoleArn": "execution role ARN",
       "containerDefinitions": [
           {
               "essential": true,
               "image": "aws_account_id.dkr.ecr.us-east-1.amazonaws.com/fluent-bit-multiline-image:latest",
               "name": "log_router",
               "firelensConfiguration": {
                   "type": "fluentbit",
                   "options": {
                       "config-file-type": "file",
                       "config-file-value": "/extra.conf"
                   }
               },
               "memoryReservation": 50
           },
           {
               "essential": true,
               "image": "aws_account_id.dkr.ecr.us-east-1.amazonaws.com/multiline-app-image:latest",
               "name": "app",
               "logConfiguration": {
                   "logDriver": "awsfirelens",
                   "options": {
                       "Name": "cloudwatch_logs",
                       "region": "us-east-1",
                       "log_group_name": "multiline-test/application",
                       "auto_create_group": "true",
                       "log_stream_prefix": "multiline-"
                   }
               },
               "memoryReservation": 100
           }
       ],
       "requiresCompatibilities": ["FARGATE"],
       "networkMode": "awsvpc",
       "cpu": "256",
       "memory": "512"
   }
   ```

   替换 `multiline-task-definition.json` 任务定义中的以下内容：

   1. `task role ARN`

      要查找任务角色 ARN，请转到 IAM 控制台。选择 **Roles**（角色），然后查找您创建的 `ecs-task-role-for-firelens` 任务角色。选择角色，然后复制 **Summary**（摘要）部分中显示的 **ARN**。

   1. `execution role ARN`

      要查找执行角色 ARN，请转到 IAM 控制台。选择 **Roles**（角色），然后查找 `ecsTaskExecutionRole` 角色。选择角色，然后复制 **Summary**（摘要）部分中显示的 **ARN**。

   1. `aws_account_id`

      要查找您的 `aws_account_id`，登录 AWS 管理控制台。在右上角选择您的用户名，然后复制您的账户 ID。

   1. `us-east-1`

      如有必要，请更换区域。

1. 注册任务定义文件：`aws ecs register-task-definition --cli-input-json file://multiline-task-definition.json --region region`

1. 在 [https://console.aws.amazon.com/ecs/v2](https://console.aws.amazon.com/ecs/v2) 打开控制台。

1. 在导航窗格中，选择 **Task Definitions**（任务定义），然后选择 `firelens-example-multiline` 系列，因为我们已在上面任务定义的第一行中将任务定义注册到这个系列。

1. 选择最新版本。

1. 选择**部署**、**运行任务**。

1. 在**运行任务**页面上，对于**集群**，请选择集群，然后在**联网**下的**子网**中，选择任务的可用子网。

1. 选择**创建**。

**验证 Amazon CloudWatch 中的多行日志消息是否已连接**

1. 通过 [https://console.aws.amazon.com/cloudwatch/](https://console.aws.amazon.com/cloudwatch/) 打开 CloudWatch 控制台。

1. 从导航窗格中，展开 **Logs**（日志）并选择 **Log groups**（日志组）。

1. 选择 `multiline-test/applicatio` 日志组。

1. 选择日志。查看消息。与解析器文件中的规则匹配的行将串联起来，并显示为单条消息。

   以下日志片段显示了在单个 Java 堆栈跟踪事件中连接的行：

   ```
   {
       "container_id": "xxxxxx",
       "container_name": "app",
       "source": "stdout",
       "log": "Dec 14 06:41:08 Exception in thread \"main\" java.lang.RuntimeException: Something has gone wrong, aborting!\n    at com.myproject.module.MyProject.badMethod(MyProject.java:22)\n    at com.myproject.module.MyProject.oneMoreMethod(MyProject.java:18)\n    at com.myproject.module.MyProject.anotherMethod(MyProject.java:14)\n    at com.myproject.module.MyProject.someMethod(MyProject.java:10)\n    at com.myproject.module.MyProject.main(MyProject.java:6)",
       "ecs_cluster": "default",
       "ecs_task_arn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task/default/xxxxxx",
       "ecs_task_definition": "firelens-example-multiline:2"
   }
   ```

   以下日志代码段显示了如果您运行的是未配置为连接多行日志消息的 Amazon ECS 容器，则如何只用一行显示相同的消息。

   ```
   {
       "log": "Dec 14 06:41:08 Exception in thread \"main\" java.lang.RuntimeException: Something has gone wrong, aborting!",
       "container_id": "xxxxxx-xxxxxx",
       "container_name": "app",
       "source": "stdout",
       "ecs_cluster": "default",
       "ecs_task_arn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task/default/xxxxxx",
       "ecs_task_definition": "firelens-example-multiline:3"
   }
   ```

### 示例：使用 Fluent Bit 内置解析器
<a name="fluent-bit-parser"></a>

在本示例中，您将完成以下步骤：

1. 为 Fluent Bit 容器生成并上传映像。

1. 为运行、失败和生成多行堆栈跟踪的演示多行应用程序生成并上传映像。

1. 创建任务定义并运行任务。

1. 查看日志以验证跨越多行的消息是否连接起来。

**为 Fluent Bit 容器生成并上传映像**

此映像将包含一个引用 Fluent Bit 解析器的配置文件。

1. 使用名称 `FluentBitDockerImage` 创建文件夹。

1. 在 `FluentBitDockerImage` 文件夹中，创建引用 Fluent Bit 内置解析器文件的自定义配置文件。

   有关自定义配置文件的更多信息，请参阅《Amazon Elastic Container Service 开发人员指南》**中的[指定自定义配置文件](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/firelens-taskdef.html#firelens-taskdef-customconfig) 

   1. 将以下内容粘贴到该文件中：

      ```
      [FILTER]
          name                  multiline
          match                 *
          multiline.key_content log
          multiline.parser      go
      ```

   1. 将该文件保存为 `extra.conf`。

1. 在 `FluentBitDockerImage` 文件夹中，使用 Fluent Bit 映像以及您创建的解析器和配置文件创建 Dockerfile。

   1. 将以下内容粘贴到该文件中：

      ```
      FROM public.ecr.aws/aws-observability/aws-for-fluent-bit:latest
      ADD extra.conf /extra.conf
      ```

   1. 将该文件保存为 `Dockerfile`。

1. 使用 Dockerfile，构建一个包含自定义配置文件的自定义 Fluent Bit 映像。
**注意**  
您可以将配置文件放置在 Docker 映像中的任何位置，但 `/fluent-bit/etc/fluent-bit.conf` 除外，因为 FireLens 使用这个文件路径。

   1. 生成映像：`docker build -t fluent-bit-multiline-image.`

      其中：`fluent-bit-multiline-image` 是此示例中映像的名称。

   1. 验证是否已正确创建映像：`docker images —filter reference=fluent-bit-multiline-image`

      如果成功，输出将显示该映像和 `latest` 标签。

1. 将自定义 Fluent Bit 映像上传到 Amazon Elastic Container Registry。

   1. 创建用于存储映像的 Amazon ECR 存储库：`aws ecr create-repository --repository-name fluent-bit-multiline-repo --region us-east-1`

      其中：`fluent-bit-multiline-repo` 是存储库的名称，`us-east-1` 是此示例中的区域。

      输出为您提供了新存储库的详细信息。

   1. 使用上一个输出中的 `repositoryUri` 值标记您的映像：`docker tag fluent-bit-multiline-image repositoryUri`

      示例：`docker tag fluent-bit-multiline-image xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com/fluent-bit-multiline-repo`

   1. 运行 Docker 镜像以验证它是否正确运行：`docker images —filter reference=repositoryUri`

      在输出中，存储库名称从 fluent-bit-multiline-repo 更改为 `repositoryUri`。

   1. 通过运行 `aws ecr get-login-password` 命令并指定您要对其进行身份验证的注册表 ID 对 Amazon ECR 进行身份验证：`aws ecr get-login-password | docker login --username AWS --password-stdin registry ID.dkr.ecr.region.amazonaws.com`

      示例：`ecr get-login-password | docker login --username AWS --password-stdin xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com`

      此时会显示成功登录消息。

   1. 将映像推送到 Amazon ECR：`docker push registry ID.dkr.ecr.region.amazonaws.com/repository name`

      示例：`docker push xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com/fluent-bit-multiline-repo`

**为演示多行应用程序生成并上传映像**

此映像将包括运行应用程序的 Python 脚本文件和示例日志文件。

1. 创建名为 `multiline-app` 的文件夹：`mkdir multiline-app`

1. 创建 Python 脚本文件。

   1. 在 `multiline-app` 文件夹中，创建一个文件并将其命名为 `main.py`。

   1. 将以下内容粘贴到该文件中：

      ```
      import os
      import time
      file1 = open('/test.log', 'r')
      Lines = file1.readlines()
       
      count = 0
      
      for i in range(10):
          print("app running normally...")
          time.sleep(1)
      
      # Strips the newline character
      for line in Lines:
          count += 1
          print(line.rstrip())
      print(count)
      print("app terminated.")
      ```

   1. 保存 `main.py` 文件。

1. 创建示例日志文件。

   1. 在 `multiline-app` 文件夹中，创建一个文件并将其命名为 `test.log`。

   1. 将以下内容粘贴到该文件中：

      ```
      panic: my panic
      
      goroutine 4 [running]:
      panic(0x45cb40, 0x47ad70)
        /usr/local/go/src/runtime/panic.go:542 +0x46c fp=0xc42003f7b8 sp=0xc42003f710 pc=0x422f7c
      main.main.func1(0xc420024120)
        foo.go:6 +0x39 fp=0xc42003f7d8 sp=0xc42003f7b8 pc=0x451339
      runtime.goexit()
        /usr/local/go/src/runtime/asm_amd64.s:2337 +0x1 fp=0xc42003f7e0 sp=0xc42003f7d8 pc=0x44b4d1
      created by main.main
        foo.go:5 +0x58
      
      goroutine 1 [chan receive]:
      runtime.gopark(0x4739b8, 0xc420024178, 0x46fcd7, 0xc, 0xc420028e17, 0x3)
        /usr/local/go/src/runtime/proc.go:280 +0x12c fp=0xc420053e30 sp=0xc420053e00 pc=0x42503c
      runtime.goparkunlock(0xc420024178, 0x46fcd7, 0xc, 0x1000f010040c217, 0x3)
        /usr/local/go/src/runtime/proc.go:286 +0x5e fp=0xc420053e70 sp=0xc420053e30 pc=0x42512e
      runtime.chanrecv(0xc420024120, 0x0, 0xc420053f01, 0x4512d8)
        /usr/local/go/src/runtime/chan.go:506 +0x304 fp=0xc420053f20 sp=0xc420053e70 pc=0x4046b4
      runtime.chanrecv1(0xc420024120, 0x0)
        /usr/local/go/src/runtime/chan.go:388 +0x2b fp=0xc420053f50 sp=0xc420053f20 pc=0x40439b
      main.main()
        foo.go:9 +0x6f fp=0xc420053f80 sp=0xc420053f50 pc=0x4512ef
      runtime.main()
        /usr/local/go/src/runtime/proc.go:185 +0x20d fp=0xc420053fe0 sp=0xc420053f80 pc=0x424bad
      runtime.goexit()
        /usr/local/go/src/runtime/asm_amd64.s:2337 +0x1 fp=0xc420053fe8 sp=0xc420053fe0 pc=0x44b4d1
      
      goroutine 2 [force gc (idle)]:
      runtime.gopark(0x4739b8, 0x4ad720, 0x47001e, 0xf, 0x14, 0x1)
        /usr/local/go/src/runtime/proc.go:280 +0x12c fp=0xc42003e768 sp=0xc42003e738 pc=0x42503c
      runtime.goparkunlock(0x4ad720, 0x47001e, 0xf, 0xc420000114, 0x1)
        /usr/local/go/src/runtime/proc.go:286 +0x5e fp=0xc42003e7a8 sp=0xc42003e768 pc=0x42512e
      runtime.forcegchelper()
        /usr/local/go/src/runtime/proc.go:238 +0xcc fp=0xc42003e7e0 sp=0xc42003e7a8 pc=0x424e5c
      runtime.goexit()
        /usr/local/go/src/runtime/asm_amd64.s:2337 +0x1 fp=0xc42003e7e8 sp=0xc42003e7e0 pc=0x44b4d1
      created by runtime.init.4
        /usr/local/go/src/runtime/proc.go:227 +0x35
      
      goroutine 3 [GC sweep wait]:
      runtime.gopark(0x4739b8, 0x4ad7e0, 0x46fdd2, 0xd, 0x419914, 0x1)
        /usr/local/go/src/runtime/proc.go:280 +0x12c fp=0xc42003ef60 sp=0xc42003ef30 pc=0x42503c
      runtime.goparkunlock(0x4ad7e0, 0x46fdd2, 0xd, 0x14, 0x1)
        /usr/local/go/src/runtime/proc.go:286 +0x5e fp=0xc42003efa0 sp=0xc42003ef60 pc=0x42512e
      runtime.bgsweep(0xc42001e150)
        /usr/local/go/src/runtime/mgcsweep.go:52 +0xa3 fp=0xc42003efd8 sp=0xc42003efa0 pc=0x419973
      runtime.goexit()
        /usr/local/go/src/runtime/asm_amd64.s:2337 +0x1 fp=0xc42003efe0 sp=0xc42003efd8 pc=0x44b4d1
      created by runtime.gcenable
        /usr/local/go/src/runtime/mgc.go:216 +0x58
      one more line, no multiline
      ```

   1. 保存 `test.log` 文件。

1. 在 `multiline-app` 文件夹中，创建 Dockerfile。

   1. 将以下内容粘贴到该文件中：

      ```
      FROM public.ecr.aws/amazonlinux/amazonlinux:latest
      ADD test.log /test.log
      
      RUN yum upgrade -y && yum install -y python3
      
      WORKDIR /usr/local/bin
      
      COPY main.py .
      
      CMD ["python3", "main.py"]
      ```

   1. 保存 `Dockerfile` 文件。

1. 使用 Dockerfile 生成映像。

   1. 生成映像：`docker build -t multiline-app-image `

      其中：`multiline-app-image` 是此示例中映像的名称。

   1. 验证是否已正确创建映像：`docker images —filter reference=multiline-app-image`

      如果成功，输出将显示该映像和 `latest` 标签。

1. 将映像上载到 Amazon Elastic Container 注册表。

   1. 创建用于存储映像的 Amazon ECR 存储库：`aws ecr create-repository --repository-name multiline-app-repo --region us-east-1`

      其中：`multiline-app-repo` 是存储库的名称，`us-east-1` 是此示例中的区域。

      输出为您提供了新存储库的详细信息。记下 `repositoryUri` 值，您将需要在后续步骤中使用该值。

   1. 使用上一个输出中的 `repositoryUri` 值标记您的映像：`docker tag multiline-app-image repositoryUri`

      示例：`docker tag multiline-app-image xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com/multiline-app-repo`

   1. 运行 Docker 映像以验证它是否正确运行：`docker images —filter reference=repositoryUri`

      在输出中，存储库名称从 `multiline-app-repo` 更改为 `repositoryUri` 值。

   1. 将映像推送到 Amazon ECR：`docker push aws_account_id.dkr.ecr.region.amazonaws.com/repository name`

      示例：`docker push xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com/multiline-app-repo`

**创建任务定义并运行任务**

1. 使用文件名 `multiline-task-definition.json` 创建任务定义文件。

1. 将以下内容粘贴到 `multiline-task-definition.json` 文件中：

   ```
   {
       "family": "firelens-example-multiline",
       "taskRoleArn": "task role ARN,
       "executionRoleArn": "execution role ARN",
       "containerDefinitions": [
           {
               "essential": true,
               "image": "aws_account_id.dkr.ecr.us-east-1.amazonaws.com/fluent-bit-multiline-image:latest",
               "name": "log_router",
               "firelensConfiguration": {
                   "type": "fluentbit",
                   "options": {
                       "config-file-type": "file",
                       "config-file-value": "/extra.conf"
                   }
               },
               "memoryReservation": 50
           },
           {
               "essential": true,
               "image": "aws_account_id.dkr.ecr.us-east-1.amazonaws.com/multiline-app-image:latest",
               "name": "app",
               "logConfiguration": {
                   "logDriver": "awsfirelens",
                   "options": {
                       "Name": "cloudwatch_logs",
                       "region": "us-east-1",
                       "log_group_name": "multiline-test/application",
                       "auto_create_group": "true",
                       "log_stream_prefix": "multiline-"
                   }
               },
               "memoryReservation": 100
           }
       ],
       "requiresCompatibilities": ["FARGATE"],
       "networkMode": "awsvpc",
       "cpu": "256",
       "memory": "512"
   }
   ```

   替换 `multiline-task-definition.json` 任务定义中的以下内容：

   1. `task role ARN`

      要查找任务角色 ARN，请转到 IAM 控制台。选择 **Roles**（角色），然后查找您创建的 `ecs-task-role-for-firelens` 任务角色。选择角色，然后复制 **Summary**（摘要）部分中显示的 **ARN**。

   1. `execution role ARN`

      要查找执行角色 ARN，请转到 IAM 控制台。选择 **Roles**（角色），然后查找 `ecsTaskExecutionRole` 角色。选择角色，然后复制 **Summary**（摘要）部分中显示的 **ARN**。

   1. `aws_account_id`

      要查找您的 `aws_account_id`，登录 AWS 管理控制台。在右上角选择您的用户名，然后复制您的账户 ID。

   1. `us-east-1`

      如有必要，请更换区域。

1. 注册任务定义文件：`aws ecs register-task-definition --cli-input-json file://multiline-task-definition.json --region us-east-1`

1. 在 [https://console.aws.amazon.com/ecs/v2](https://console.aws.amazon.com/ecs/v2) 打开控制台。

1. 在导航窗格中，选择 **Task Definitions**（任务定义），然后选择 `firelens-example-multiline` 系列，因为我们已在上面任务定义的第一行中将任务定义注册到这个系列。

1. 选择最新版本。

1. 选择**部署**、**运行任务**。

1. 在**运行任务**页面上，对于**集群**，请选择集群，然后在**联网**下的**子网**中，选择任务的可用子网。

1. 选择**创建**。

**验证 Amazon CloudWatch 中的多行日志消息是否已连接**

1. 通过 [https://console.aws.amazon.com/cloudwatch/](https://console.aws.amazon.com/cloudwatch/) 打开 CloudWatch 控制台。

1. 从导航窗格中，展开 **Logs**（日志）并选择 **Log groups**（日志组）。

1. 选择 `multiline-test/applicatio` 日志组。

1. 选择日志并查看消息。与解析器文件中的规则匹配的行将串联起来，并显示为单条消息。

   以下日志代码段显示了在单个事件中连接的 Go 堆栈跟踪：

   ```
   {
       "log": "panic: my panic\n\ngoroutine 4 [running]:\npanic(0x45cb40, 0x47ad70)\n  /usr/local/go/src/runtime/panic.go:542 +0x46c fp=0xc42003f7b8 sp=0xc42003f710 pc=0x422f7c\nmain.main.func1(0xc420024120)\n  foo.go:6 +0x39 fp=0xc42003f7d8 sp=0xc42003f7b8 pc=0x451339\nruntime.goexit()\n  /usr/local/go/src/runtime/asm_amd64.s:2337 +0x1 fp=0xc42003f7e0 sp=0xc42003f7d8 pc=0x44b4d1\ncreated by main.main\n  foo.go:5 +0x58\n\ngoroutine 1 [chan receive]:\nruntime.gopark(0x4739b8, 0xc420024178, 0x46fcd7, 0xc, 0xc420028e17, 0x3)\n  /usr/local/go/src/runtime/proc.go:280 +0x12c fp=0xc420053e30 sp=0xc420053e00 pc=0x42503c\nruntime.goparkunlock(0xc420024178, 0x46fcd7, 0xc, 0x1000f010040c217, 0x3)\n  /usr/local/go/src/runtime/proc.go:286 +0x5e fp=0xc420053e70 sp=0xc420053e30 pc=0x42512e\nruntime.chanrecv(0xc420024120, 0x0, 0xc420053f01, 0x4512d8)\n  /usr/local/go/src/runtime/chan.go:506 +0x304 fp=0xc420053f20 sp=0xc420053e70 pc=0x4046b4\nruntime.chanrecv1(0xc420024120, 0x0)\n  /usr/local/go/src/runtime/chan.go:388 +0x2b fp=0xc420053f50 sp=0xc420053f20 pc=0x40439b\nmain.main()\n  foo.go:9 +0x6f fp=0xc420053f80 sp=0xc420053f50 pc=0x4512ef\nruntime.main()\n  /usr/local/go/src/runtime/proc.go:185 +0x20d fp=0xc420053fe0 sp=0xc420053f80 pc=0x424bad\nruntime.goexit()\n  /usr/local/go/src/runtime/asm_amd64.s:2337 +0x1 fp=0xc420053fe8 sp=0xc420053fe0 pc=0x44b4d1\n\ngoroutine 2 [force gc (idle)]:\nruntime.gopark(0x4739b8, 0x4ad720, 0x47001e, 0xf, 0x14, 0x1)\n  /usr/local/go/src/runtime/proc.go:280 +0x12c fp=0xc42003e768 sp=0xc42003e738 pc=0x42503c\nruntime.goparkunlock(0x4ad720, 0x47001e, 0xf, 0xc420000114, 0x1)\n  /usr/local/go/src/runtime/proc.go:286 +0x5e fp=0xc42003e7a8 sp=0xc42003e768 pc=0x42512e\nruntime.forcegchelper()\n  /usr/local/go/src/runtime/proc.go:238 +0xcc fp=0xc42003e7e0 sp=0xc42003e7a8 pc=0x424e5c\nruntime.goexit()\n  /usr/local/go/src/runtime/asm_amd64.s:2337 +0x1 fp=0xc42003e7e8 sp=0xc42003e7e0 pc=0x44b4d1\ncreated by runtime.init.4\n  /usr/local/go/src/runtime/proc.go:227 +0x35\n\ngoroutine 3 [GC sweep wait]:\nruntime.gopark(0x4739b8, 0x4ad7e0, 0x46fdd2, 0xd, 0x419914, 0x1)\n  /usr/local/go/src/runtime/proc.go:280 +0x12c fp=0xc42003ef60 sp=0xc42003ef30 pc=0x42503c\nruntime.goparkunlock(0x4ad7e0, 0x46fdd2, 0xd, 0x14, 0x1)\n  /usr/local/go/src/runtime/proc.go:286 +0x5e fp=0xc42003efa0 sp=0xc42003ef60 pc=0x42512e\nruntime.bgsweep(0xc42001e150)\n  /usr/local/go/src/runtime/mgcsweep.go:52 +0xa3 fp=0xc42003efd8 sp=0xc42003efa0 pc=0x419973\nruntime.goexit()\n  /usr/local/go/src/runtime/asm_amd64.s:2337 +0x1 fp=0xc42003efe0 sp=0xc42003efd8 pc=0x44b4d1\ncreated by runtime.gcenable\n  /usr/local/go/src/runtime/mgc.go:216 +0x58",
       "container_id": "xxxxxx-xxxxxx",
       "container_name": "app",
       "source": "stdout",
       "ecs_cluster": "default",
       "ecs_task_arn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task/default/xxxxxx",
       "ecs_task_definition": "firelens-example-multiline:2"
   }
   ```

   以下日志代码段显示了如果您运行的是未配置为连接多行日志消息的 ECS 容器，如何显示相同的事件。日志字段包含单行。

   ```
   {
       "log": "panic: my panic",
       "container_id": "xxxxxx-xxxxxx",
       "container_name": "app",
       "source": "stdout",
       "ecs_cluster": "default",
       "ecs_task_arn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task/default/xxxxxx",
       "ecs_task_definition": "firelens-example-multiline:3"
   ```

**注意**  
如果您的日志转到日志文件而不是标准输出，我们建议在[结尾输入插件](https://docs.fluentbit.io/manual/pipeline/inputs/tail#multiline-support)中指定 `multiline.parser` 和 `multiline.key_content` 配置参数，而不是筛选条件。