

# 여러 줄 또는 스택 추적 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 예제: 부분/분할 컨테이너 로그 연결](https://github.com/aws-samples/amazon-ecs-firelens-examples/tree/mainline/examples/fluent-bit/filter-multiline-partial-message-mode).

## 필수 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 이미지를 빌드합니다.
**참고**  
이 파일 경로는 FireLens에서 사용하므로 `/fluent-bit/etc/fluent-bit.conf`를 제외한 Docker 이미지의 아무 곳에나 구문 분석기 파일과 구성 파일을 배치할 수 있습니다.

   1. 이미지를 빌드합니다. `docker build -t fluent-bit-multiline-image.` 

      여기서 `fluent-bit-multiline-image`는 이 예에서 이미지의 이름입니다.

   1. 이미지가 올바르게 생성되었는지 확인합니다. `docker images —filter reference=fluent-bit-multiline-image` 

      성공하면 출력에 이미지와 `latest` 태그가 표시됩니다.

1. Amazon Elastic Container Registry에 사용자 정의 Fluent Bit 이미지를 업로드합니다.

   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 컨테이너 레지스트리로 이미지를 업로드합니다.

   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 Management Console에 로그인합니다. 오른쪽 상단에서 사용자 이름을 선택하고 계정 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. 탐색 창에서 **태스크 정의**를 선택한 다음 위의 태스크 정의의 첫 번째 줄에서 이 제품군에 태스크 정의를 등록했기 때문에 `firelens-example-multiline` 제품군을 선택합니다.

1. 최신 버전을 선택합니다.

1. **배포**, **작업 실행**을 선택합니다.

1. **작업 실행** 페이지의 **클러스터**에서 클러스터를 선택한 다음 **네트워킹** 아래의 **서브넷**에서 작업에 사용할 수 있는 서브넷을 선택하세요.

1. **생성(Create)**을 선택합니다.

**Amazon CloudWatch의 여러 줄 로그 메시지가 연결된 것으로 나타나는지 확인**

1. [https://console.aws.amazon.com/cloudwatch/](https://console.aws.amazon.com/cloudwatch/)에서 CloudWatch 콘솔을 엽니다.

1. 왼쪽 탐색 창에서 **로그**를 확장하고 **로그 그룹**을 선택합니다.

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 이미지를 빌드합니다.
**참고**  
이 파일 경로는 FireLens에서 사용하므로 `/fluent-bit/etc/fluent-bit.conf`를 제외한 Docker 이미지의 아무 곳에나 구성 파일을 배치할 수 있습니다.

   1. 이미지를 빌드합니다. `docker build -t fluent-bit-multiline-image.` 

      여기서 `fluent-bit-multiline-image`는 이 예에서 이미지의 이름입니다.

   1. 이미지가 올바르게 생성되었는지 확인합니다. `docker images —filter reference=fluent-bit-multiline-image` 

      성공하면 출력에 이미지와 `latest` 태그가 표시됩니다.

1. Amazon Elastic Container Registry에 사용자 정의 Fluent Bit 이미지를 업로드합니다.

   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 컨테이너 레지스트리로 이미지를 업로드합니다.

   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 Management Console에 로그인합니다. 오른쪽 상단에서 사용자 이름을 선택하고 계정 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. 탐색 창에서 **태스크 정의**를 선택한 다음 위의 태스크 정의의 첫 번째 줄에서 이 제품군에 태스크 정의를 등록했기 때문에 `firelens-example-multiline` 제품군을 선택합니다.

1. 최신 버전을 선택합니다.

1. **배포**, **작업 실행**을 선택합니다.

1. **작업 실행** 페이지의 **클러스터**에서 클러스터를 선택한 다음 **네트워킹** 아래의 **서브넷**에서 작업에 사용할 수 있는 서브넷을 선택하세요.

1. **생성(Create)**을 선택합니다.

**Amazon CloudWatch의 여러 줄 로그 메시지가 연결된 것으로 나타나는지 확인**

1. [https://console.aws.amazon.com/cloudwatch/](https://console.aws.amazon.com/cloudwatch/)에서 CloudWatch 콘솔을 엽니다.

1. 왼쪽 탐색 창에서 **로그**를 확장하고 **로그 그룹**을 선택합니다.

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"
   ```

**참고**  
로그가 표준 출력 대신 로그 파일로 이동하는 경우 필터 대신 [Tail 입력 플러그 인](https://docs.fluentbit.io/manual/pipeline/inputs/tail#multiline-support)에서 `multiline.parser` 및 `multiline.key_content` 구성 파라미터를 지정하는 것이 좋습니다.