새소식

카테고리 없음

AWS Serverless Observability Workshop - 2. Centralized Logging 중앙 집중식 로그

  • -

 

특성 중앙 집중식 로그 메트릭
정의 로그 데이터를 단일 위치에 표준화된 형식으로 수집, 저장 및 분석하는 시스템입니다. 시스템의 성능과 상태를 측정하기 위해 수집된 정량적 데이터입니다.
목적 애플리케이션과 시스템의 작동 상태를 이해하고, 문제 해결과 시스템의 성능 분석을 위함입니다. 시스템의 성능을 모니터링하고, 문제를 예방하며, 의사 결정을 지원하기 위함입니다.
데이터 유형 텍스트 기반의 로그 메시지로, 시스템 이벤트, 에러 메시지, 트랜잭션 정보 등이 포함됩니다. 숫자 형태의 데이터로, CPU 사용률, 메모리 사용량, 응답 시간, 트래픽 양 등이 포함됩니다.
사용 사례 오류 추적, 시스템 이벤트 모니터링, 보안 위반 감지 등입니다. 리소스 사용률 모니터링, 성능 트렌드 분석, 용량 계획 등입니다.

 

모든 로그 기록을 단일 위치에 표준화된 형식으로 배치하여 로그 분석 및 상관 관계 작업을 크게 단순화

로그 데이터를 위한 안전한 저장 영역을 제공 (무결성)

SAM 템플릿 업데이트

Amazon API Gateway에서 로깅을 활성화하고 CloudWatch Logs에 로그를 보내기 위해 AWS SAM(Serverless Application Model) 템플릿 파일을 수정하는 과정

serverless-observability-workshop/code/sample-app/template.yaml 파일 수정

API Gateway 로깅 활성화:

  • API Gateway의 로깅 기능을 활성화하여 API 호출과 관련된 정보를 CloudWatch Logs에 기록하도록 설정

IAM 역할 설정:

  • API Gateway가 CloudWatch Logs에 로그를 푸시할 수 있도록 허용하는 IAM 역할을 생성합니다. 이 역할은 API Gateway가 로그를 CloudWatch에 기록할 때 사용됨

SAM 템플릿 업데이트:

  • AWS::Serverless::Api 리소스에 AccessLogSetting 속성을 추가하여 로그 저장 위치와 로그 포맷을 지정
  • AWS::ApiGateway::Account 리소스를 통해 CloudWatchRoleArn을 지정하여 API Gateway가 사용할 IAM 역할을 설정
  • AWS::IAM::Role 리소스를 추가하여 필요한 권한을 가진 IAM 역할을 정의함
  • 이 역할은 AmazonAPIGatewayPushToCloudWatchLogs 관리형 정책을 포함하여 API Gateway 서비스가 CloudWatch Logs로 로그를 푸시할 수 있도록 함.

템플릿 파일 저장:

  • 모든 변경사항을 적용한 후 template.yaml 파일을 저장함

로깅 설정 확인:

  • 로깅 설정을 활성화한 후, API Gateway를 통한 요청이 CloudWatch Logs에 정상적으로 기록되는지 확인함

AWS Lambda와 CloudWatch Logs Insights를 사용하여 애플리케이션의 로깅과 로그 분석을 개선하는 방법

/serverless-observability-workshop/code/sample-app/src/lib/logger.js 참고

serverless-observability-workshop/code/sample-app/template.yaml 수정

Resources:
# API Gateway
  Api:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      AccessLogSetting:
        DestinationArn: !Sub ${ApiAccessLogGroup.Arn} # This Log Group is already created within our SAM Template
        Format: "{ 'requestId':'$context.requestId', 'ip': '$context.identity.sourceIp', 'caller':'$context.identity.caller', 'user':'$context.identity.user','requestTime':'$context.requestTime', 'xrayTraceId':'$context.xrayTraceId', 'wafResponseCode':'$context.wafResponseCode', 'httpMethod':'$context.httpMethod','resourcePath':'$context.resourcePath', 'status':'$context.status','protocol':'$context.protocol', 'responseLength':'$context.responseLength' }"
      MethodSettings:
      - ResourcePath: '/*'
        HttpMethod: '*'
        MetricsEnabled: True
  ApiCWLRoleArn:
    Type: AWS::ApiGateway::Account
    Properties:
      CloudWatchRoleArn: !GetAtt CloudWatchRole.Arn
# IAM Role for API GW + CWL
  CloudWatchRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          Action: 'sts:AssumeRole'
          Effect: Allow
          Principal:
            Service: apigateway.amazonaws.com
      Path: /
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs'

 

  1. API Gateway (Api): 이 부분은 API Gateway를 생성하며, 여기서 API의 이름, 스테이지 이름(여기서는 'Prod') 및 액세스 로깅 설정을 정의합니다. 액세스 로그 설정에서는 로그 그룹의 ARN, 로그 형식(요청 ID, 소스 IP, 사용자 등의 정보를 포함) 등을 지정합니다. 로그 형식은 JSON 문자열로 정의되어 있으며, 각 요소는 API Gateway 호출 컨텍스트에서 추출됩니다.
  2. MethodSettings: API Gateway의 모든 리소스와 메소드에 대해 메트릭을 활성화합니다. 이 설정을 통해 API 호출에 대한 데이터를 수집하고, CloudWatch에서 모니터링할 수 있게 됩니다.
  3. ApiCWLRoleArn (AWS::ApiGateway::Account): 이 부분은 API Gateway가 CloudWatch 로그에 로그를 보낼 수 있도록 허용하는 IAM 역할과 연결합니다. **!GetAtt**은 CloudWatchRole 리소스에서 ARN을 가져오는 데 사용됩니다.
  4. CloudWatchRole (AWS::IAM::Role): 이 섹션은 API Gateway 서비스가 CloudWatch 로그로 로그를 전송할 수 있도록 하는 IAM 역할을 생성합니다. **AssumeRolePolicyDocument**는 API Gateway 서비스가 이 역할을 가정할 수 있도록 설정합니다. 그리고 Amazon API Gateway가 CloudWatch 로그로 로그를 푸시할 수 있도록 하는 관리형 정책 **AmazonAPIGatewayPushToCloudWatchLogs**를 포함합니다.

/serverless-observability-workshop/code/sample-app/src/handlers/get-all-items.js  수정

const AWS = require('aws-sdk')
const docClient = new AWS.DynamoDB.DocumentClient()
const { logger_setup } = require('../lib/logging/logger')
let log

exports.getAllItemsHandler = async (event, context) => {
    log = logger_setup()
    let response
    log.info(event)
    log.info(context)


    try {
        if (event.httpMethod !== 'GET') {
            log.error({ "operation": "get-all-items", 'method': 'getAllItemsHandler', "details": `getAllItems only accept GET method, you tried: ${event.httpMethod}` })
            throw new Error(`getAllItems only accept GET method, you tried: ${event.httpMethod}`)
        }

        const items = await getAllItems()
        response = {
            statusCode: 200,
            headers: {
                'Access-Control-Allow-Origin': '*'
            },
            body: JSON.stringify(items)
        }
    } catch (err) {
        response = {
            statusCode: 500,
            headers: {
                'Access-Control-Allow-Origin': '*'
            },
            body: JSON.stringify(err)
        }
        log.error({ "operation": "get-all-items", 'method': 'getAllItemsHandler', "details": err })
    }
    log.info({ operation: 'get-all-items', 'method': 'getAllItemsHandler', eventPath: event.path, statusCode: response.statusCode, body: JSON.parse(response.body) })
    return response
}
... (이하 동일)

재배포

cd ~/environment/serverless-observability-workshop/code/sample-app
sam build && sam deploy

스택 출력 변수 가져오기

export ApiUrl=$(aws cloudformation describe-stacks --stack-name monitoring-app --output json | jq '.Stacks[].Outputs[] | select(.OutputKey=="ApiUrl") | .OutputValue' | sed -e 's/^"//'  -e 's/"$//')
echo "export ApiUrl="$ApiUrl

테스트

curl -X GET $ApiUrl/items/ | jq

로그 확인 가능

 

로그 사용 및 분석

 

API Gateway

집계, 정렬, 시계열이 포함된 목록

fields @timestamp, @message
| stats count(@message) as number_of_events by bin(5m)
| filter @message like /operation/
| limit 20

AWS CLI를 사용한 쿼리

export getAllItemsFunction=$(aws cloudformation describe-stack-resources --stack-name monitoring-app --output json | jq '.StackResources[] | select(.LogicalResourceId=="getAllItemsFunction") | .PhysicalResourceId' | sed -e 's/^"//'  -e 's/"$//')
aws logs start-query --log-group-name /aws/lambda/$getAllItemsFunction --start-time '1672580745' --end-time '1735739145000' --query-string 'fields @message | limit 10'

<예시>

ec2-user:~/environment/serverless-observability-workshop/code/sample-app/src/handlers (master) $ export getAllItemsFunction=$(aws cloudformation describe-stack-resources --stack-name monitoring-app --output json | jq '.StackResources[] | select(.LogicalResourceId=="getAllItemsFunction") | .PhysicalResourceId' | sed -e 's/^"//'  -e 's/"$//')
ec2-user:~/environment/serverless-observability-workshop/code/sample-app/src/handlers (master) $ aws logs start-query --log-group-name /aws/lambda/$getAllItemsFunction --start-time '1672580745' --end-time '1735739145000' --query-string 'fields @message | limit 10'
{
    "queryId": "85ff1302-a35c-46ca-af60-0009c0c065f3"
}

쿼리 id를 참고한 결과 확인

aws logs get-query-results --query-id <QUERY_ID>

<예시>

ec2-user:~/environment/serverless-observability-workshop/code/sample-app/src/handlers (master) $ aws logs get-query-results --query-id 85ff1302-a35c-46ca-af60-0009c0c065f3
{
    "results": [
        [
            {
                "field": "@message",
                "value": "END RequestId: 2d5ba193-a0dc-4fbc-9242-574eb23d6214\n"
            },
            {
                "field": "@ptr",
                "value": "CokBCkwKSDM4MTQ5MjE5MjcyODovYXdzL2xhbWJkYS9tb25pdG9yaW5nLWFwcC1nZXRBbGxJdGVtc0Z1bmN0aW9uLTJ6OVhzWkhQMzBaZBAHEjUaGAIGXVR9ewAAAAALfgDBAAZeK+lwAAACsiABKL385+3fMTDW/Oft3zE4BkD4J0jFXlCFMxgAIAEQBBgB"
            }
        ],
        [
            {
                "field": "@message",
                "value": "REPORT RequestId: 2d5ba193-a0dc-4fbc-9242-574eb23d6214\tDuration: 24.87 ms\tBilled Duration: 25 ms\tMemory Size: 128 MB\tMax Memory Used: 97 MB\t\nXRAY TraceId: 1-65e2bef9-00746e881e16b6e88d232874\tSegmentId: 3263ae9f7f2ec61f\tSampled: true\t\n"
            },
            {
                "field": "@ptr",
                "value": "CokBCkwKSDM4MTQ5MjE5MjcyODovYXdzL2xhbWJkYS9tb25pdG9yaW5nLWFwcC1nZXRBbGxJdGVtc0Z1bmN0aW9uLTJ6OVhzWkhQMzBaZBAHEjUaGAIGXVR9ewAAAAALfgDBAAZeK+lwAAACsiABKL385+3fMTDW/Oft3zE4BkD4J0jFXlCFMxgAIAEQBRgB"
            }
        ],
        [
            {
                "field": "@message",
                "value": "2024-03-02T05:54:01.412Z\t2d5ba193-a0dc-4fbc-9242-574eb23d6214\tINFO\t{\"_logLevel\":\"info\",\"msg\":{\"operation\":\"get-all-items\",\"method\":\"getAllItemsHandler\",\"eventPath\":\"/items\",\"statusCode\":200,\"body\":{\"Items\":[{\"id\":\"2\",\"name\":\"Second test item\"},{\"id\":\"1\",\"name\":\"Sample test item\"}],\"Count\":2,\"ScannedCount\":2}},\"timestamp\":\"2024-03-02T05:54:01.412Z\",\"X-Amzn-Trace-Id\":\"Root=1-65e2bef9-00746e881e16b6e88d232874;Parent=68967768b53325e3;Sampled=1;Lineage=125cc394:0\",\"X-Amzn-Trace-Id-Root\":\"1-65e2bef9-00746e881e16b6e88d232874\",\"X-Amzn-Trace-Id-Parent\":\"68967768b53325e3;Sampled=1\",\"_tags\":[\"log\",\"info\"]}\n"
            },
            {
                "field": "@ptr",
                "value": "CokBCkwKSDM4MTQ5MjE5MjcyODovYXdzL2xhbWJkYS9tb25pdG9yaW5nLWFwcC1nZXRBbGxJdGVtc0Z1bmN0aW9uLTJ6OVhzWkhQMzBaZBAHEjUaGAIGXVR9ewAAAAALfgDBAAZeK+lwAAACsiABKL385+3fMTDW/Oft3zE4BkD4J0jFXlCFMxgAIAEQAxgB"
            }
        ],
...
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.