[입 개발] 스타트업을 위한 AWS 로그 시스템 Part #3

로그를 분석하거나 저장할 필요가 없는 서비스면 상관이 없겠지만, 결국 대부분의 서비스는 로그를 저장하고 이를 통해서 서비스를 상황을 분석하게 됩니다.(혼자서 테스트로만 쓰는 서비스면 그럴 필요가 없죠.) 그래서 이제 마지막으로 어떻게 s3에 저장할 것인가에 대해서 얘기를 할려고 합니다. 2부에서도 보였지만, 아래 형태 처럼 S3에 저장이되면 EMR, Athena 등을 이용해서 처리를 할 수 있습니다.

가장 쉬운 방법은 해당 CloudWatch Log를 S3로 덤프하는 것입니다. 아래 처럼 CloudWatch 에서 Export data to Amazon S3를 통해서 덤프를 하는 것입니다.

해당 메뉴를 선택하고, 덤프할 시간대를 지정하면 해당 경로에 덤프가 되게 됩니다. 여기에 주의할 것은 CloudWatch Log Stream 은 로컬 시간으로 보여지지만, 덤프는 UTC 기준으로 설정해야 한다는 부분입니다.

가장 쉽고, 쉽게 생성할 수 있습니다. API로도 쉽게 가능한데, 단점(?)은 로그가 cloudwatch에서 보이는 형태로 그대로 저장됩니다. 예를 들어 우리의 Log 자체는 json 형태인데 cloud watch 에는 시간 날짜 서버 주소 등이 추가되어서 다음과 같은 형태로 저장되게 됩니다.

2022-02-24T15:15:41.565Z Feb 25 00:15:41 ip-10-10-104-20 web: {"timestamp": "2022-02-25T00:15:41" 어쩌구 저쩌구 원래 로그}

그리고 실시간이 아니라 배치 형태입니다. 물론 (1분 단위로 저장하면 될것 같긴 한데…) 그리고 로그 구조도 위의 s3 bucket prefix 설정 이후에 {uuid}/{logstream name}/000000.gz 이런식으로 저장되게 됩니다.

그럼 실시간으로 하고 싶다면? kinesis firehose delivery stream 을 사용할 수 있습니다. create delivery stream 을 통해서 firehose delivery stream 을 생성할 수 있습니다.

이때 기본적으로 Source를 DIRECT_PUT으로 설정하고Destination 을 Amazon S3 로 설정하면 됩니다.

그리고 어디에 저장할지 S3 Bucket 과 S3 bucket Prefix 를 지정하면 됩니다. 항상 저 prefix 뒤에 YYYY/MM/dd/HH 가 붙게됩니다.(UTC 기준입니다.)

이런 작업을 하기 전에 해당 firehose 가 s3에 쓰기 위한 IAM 과 cloudwatch에서 해당 firehose delivery stream 을 쓰기 위한 IAM을 두 개 생성해야 합니다.

그리고 이제 cloudwatch에서 Subscription Filters 를 이용해서 로그를 firehose 에 준 실시간(몇분 정도의 데이터가 모여서 전달이 되는 걸로 보입니다.) 형태로 아까 지정한 s3 경로에 데이터가 저장되게 됩니다.)

그런데 문제는 Subscription Filters 형태로 그냥 저장을 해버리면, 로그 포맷이 다음과 같은 형태입니다.

{"messageType":"DATA_MESSAGE","owner":"171305364530","logGroup":"/aws/elasticbeanstalk/myservervice/var/log/myservice/reqlog.log","logStream":"myservice/i-0d93c1be6e7f0b515","subscriptionFilters":["adminlog_to_s3"],"logEvents":[{"id":"36685096528927476510604570585254667982719470768821960704","timestamp":1645016621747,"message":"{\"logType\":\"BasicErrorController#error\",\"server_timestamp\":1645016613084,\"remote_ip\":\"20.114.132.182\",\"userId\":-1,\"url\":\"/error\",\"status\":500}"}]}{"messageType":"DATA_MESSAGE","owner":"171305364530","logGroup":"/aws/elasticbeanstalk/myservice/var/log/myservice/reqlog.log","logStream":"i-0d93c1be6e7f0b515","subscriptionFilters":["adminlog_to_s3"],"logEvents":[{"id":"36685096528927476510604570585230365759195078421286944768","timestamp":1645016621747,"message":"{\"logType\":\"BasicErrorController#error\",\"server_timestamp\":1645016613084,\"remote_ip\":\"20.114.132.182\",\"userId\":-1,\"url\":\"/error\",\"status\":500}"}]}

Subscription Filter에서 저런 형태로 데이터를 Firehose로 넘겨주게됩니다. logEvents 안에 몇개의 로그들이 배열로 들어가 있고, 그 중에 message 부분에 실제 저장한 로그가 들어가 있습니다.. (다음 로그도 합쳐져서 저장이 되어서 json 파싱도 어려운…)

그러면 이걸 어떻게 하는가? 실제로 Subscription Filter를 lambda로 만들어서 lambda로 데이터를 보내고, 해당 데이터만 추출해서 데이터를 저장하는 형태로 사용하면 원래 파일에 남아있던 로그만 저장할 수 가 있습니다.

실제로 이 부분을 만들어야 하나 싶었는데, 다음을 이용하면 바로 aws lambda 로 설정이 됩니다. https://github.com/jlhood/json-lambda-logs-to-kinesis-firehose

코드를 보면 실제로 특별한 작업을 하는 것은 아니고 다음과 같습니다. 즉 Subscription Filter로 인해서 발생한 로그가 AWS Lambda의 handler 를 호출하면, 아까 Firehose 로그 형식을 읽어서 파싱합니다. logEvents 부분을 읽어서 개별로 FIREHOSE의 해당 경로에 저장하게 됩니다.

FIREHOSE = boto3.client('firehose')


def handler(event, context):
    """Forward JSON-formatted CW Log events to Firehose Delivery Stream."""
    LOG.debug('Received event: %s', event)

    log_messages = _get_log_messages(event)
    for log_message in log_messages:
        if _is_json(log_message):
            if not log_message.endswith('\n'):
                log_message += '\n'

            FIREHOSE.put_record(
                DeliveryStreamName=config.DELIVERY_STREAM_NAME,
                Record={
                    'Data': log_message
                }
            )


def _get_log_messages(event):
    data = json.loads(gzip.decompress(base64.b64decode(event['awslogs']['data'])))
    return [log_event['message'] for log_event in data['logEvents']]

위의 Lambda 를 등록하면 이제 해당 로그가 다음 같은 구조로 흐르게 됩니다.

개인적으로는 실시간이 필요없다면 그냥 export 방식이 가장 수월해 보입니다. (필요하면 주기를 좀 줄여서 실시간 처럼 보일 수도…)

저렇게 S3에 저장이 된 후에 EMR등을 이용해서 분석을 할 수 있습니다. 아마 초반에는 실시간 데이터는 필요 없을듯 하니, 최대한 간단하게 가져갈 수 있는게 좋을듯 합니다. 다만… 세상이 그렇게 쉽지 많은 않은… 보안 로그도 관리해야하고… 핀테크는 제약이 좀 더 많네요.

스타트업에서의 목표는 최소 비용(사람의 노력이 가장 큰 비용입니다.)으로 어느정도의 효과를 뽑아내는 거라고 생각합니다.(최대가 아닙니다.)

데이터 엔지니어어 생활도 오래한거 같지만… 데이터쪽은 항상 뭔가 할려고 하니 어렵네요. 일단은 이렇게 땜방을 치고… 나중에 좀 더 고도화를 해야…

이렇게 로그를 수집하려는 이유는, 일단은 에러로그 같은 걸 좀 쉽게 보기 위해서… DB를 최대한 안 읽고, 필요한 현재 상태를 파악하기 위해서 입니다. 사실 로그에서 가장 중요한 것은, 어떻게 구축하냐가 아니라, 어떤 정보를 남기는가를 정의하는 것입니다. DB에 있는 데이터를 계속 접근해야 하면, 데이터 량이 작을때는 별 문제가 아니지만, 나중에는 DB에서 그 데이터를 읽기 위해서 많은 비용을 들이게 됩니다.(단점은 로그가 안정적인가? – 유실은 없을까 등… 고민이 많습니다.)

어서 빨리 서비스가 성장해서 데이터 처리에 더 많은 고민을 할 수 있으면 좋겠습니다.