LabHub
배우기 러닝패스 코스

ログから原因を見つける

受け取ったログに三つの形式が混ざっていた

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

형식이 서로 다른 로그 파일 세 개를 한 줄 한 레코드의 NDJSON 으로 정규화하고, 여러 줄 예외를 한 레코드로 묶고, 파싱하지 못한 줄을 격리한 뒤 셋을 공통 스키마로 합칩니다.

왜 중요한가

현장에서 처음 받는 로그 묶음은 정돈되어 있지 않습니다. 형식이 섞인 채로 grep -c 를 돌리면 예외 한 건이 오류 열 건으로 세어지고, 그 숫자가 그대로 보고서에 실립니다. 시각 표기도 파일마다 달라서 문자열로 정렬하면 시간순이 되지 않습니다. 그래서 분석 전에 "무엇이 한 레코드인가" 와 "시각을 어떤 문자열로 굳히는가" 를 먼저 정해야 합니다. 이 결정은 코드가 아니라 계약이고, 문서로 남지 않으면 다음 사람이 같은 파일에서 다른 숫자를 냅니다.

단계

  1. /root/norm/gen_norm.py 를 만들어 실행해 /root/norm/raw/ 아래 app.log · gateway.log · sys.log 를 만드세요.
  2. /root/norm/counts.json 에 물리적 줄 수와 논리 레코드 수를 갈라 적으세요.
  3. /root/norm/app.ndjson — 애플리케이션 로그를 한 줄 한 레코드로 묶으세요.
  4. /root/norm/gw.ndjson — Combined 접근 로그를 구조화하세요.
  5. /root/norm/sys.ndjson — RFC 5424 syslog 를 구조화하세요.
  6. /root/norm/rejects.ndjson — 파싱하지 못한 줄을 원문과 줄 번호로 격리하세요.
  7. /root/norm/all.ndjson — 셋을 공통 스키마로 합쳐 시간순으로 세우세요.
  8. /root/norm/norm_report.md — 정규화 규칙과 버린 줄을 보고서로 남기세요.

참고

고객사 로그 묶음 재현하기

/root/norm/gen_norm.py 를 만들어 실행해 /root/norm/raw/ 아래 app.log(91줄) · gateway.log(60줄) · sys.log(30줄)을 만드세요.

세 파일은 서로 다른 프로그램이 쓴 것이라 형식이 다릅니다. app.log 는 예외가 여러 줄이라 줄 수가 레코드 수보다 많고, sys.log 는 몇 줄이 전송 중에 잘려 있습니다. 먼저 /root/norm/raw 를 만들고 그 안에 세 파일을 씁니다.

줄 수와 레코드 수를 갈라 세기

/root/norm/counts.json 에 app_lines · app_records · gateway_records · syslog_lines · syslog_records · rejected_lines · total_records 를 적으세요. total_records 는 세 파일에서 살아남은 레코드의 합입니다.

app.log 는 시각으로 시작하는 줄만 레코드의 머리입니다. sys.log 는 30줄이지만 RFC 5424 머리를 갖추지 못한 줄이 섞여 있어 레코드 수가 더 적습니다. 두 숫자가 다르다는 사실 자체가 이 단계의 답입니다.

여러 줄 예외를 한 레코드로 묶기

/root/norm/app.ndjson 에 레코드마다 ts(UTC·Z) · level · logger · msg · stack_lines 를 담아 한 줄에 하나씩 쓰세요. stack_lines 는 그 레코드에 딸린 추가 줄 수입니다.

줄이 시각으로 시작하면 새 레코드, 아니면 직전 레코드의 계속입니다. 이 규칙 하나면 at … 프레임과 Caused by: 가 자동으로 앞에 붙습니다. +09:00%z 로 읽히고, UTC 변환은 astimezone(timezone.utc) 입니다.

접근 로그를 구조화하기

/root/norm/gw.ndjson 에 ts(UTC·Z) · method · path · status(정수) · bytes(정수) · rt_ms(밀리초 정수)를 담아 한 줄에 하나씩 쓰세요.

Combined 로그의 시각은 대괄호 안에 05/Mar/2026:14:22:31 +0900 모양으로 있습니다. %d/%b/%Y:%H:%M:%S %z 로 읽히고, %b 는 C 로캘에서 Mar 을 해석합니다. 마지막 칸의 응답시간은 초 단위 실수라 1000을 곱해 반올림합니다.

PRI 를 facility 와 severity 로 가르기

/root/norm/sys.ndjson 에 ts(UTC·Z) · host · app · facility(정수) · severity(정수) · msgid · msg 를 담아 한 줄에 하나씩 쓰세요. 파싱하지 못한 줄은 여기에 넣지 않습니다.

RFC 5424 한 줄은 <PRI>1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID STRUCTURED-DATA MSG 입니다. PRI 안의 숫자 하나가 둘을 담고 있습니다 — 8로 나눈 몫과 나머지입니다. 머리를 갖추지 못한 줄은 지금 건너뛰고, 다음 단계에서 따로 모읍니다.

파싱 못 한 줄을 버리지 않고 격리하기

/root/norm/rejects.ndjson 에 파싱하지 못한 줄을 src_file · line_no(1부터) · raw(원문 그대로) · reason 으로 남기세요.

파서가 못 읽은 줄을 continue 로 넘기면 손실이 어디에도 남지 않습니다. 파싱 실패는 예외 상황이 아니라 한 종류의 결과입니다. 줄 번호는 원본 파일에서 1부터 세고, raw 는 손대지 않은 원문 그대로여야 나중에 되짚을 수 있습니다.

세 원본을 공통 스키마로 합치기

/root/norm/all.ndjson 에 ts · source(app|gateway|syslog) · severity(ERROR|WARN|INFO) · message 를 담아 ts 오름차순으로 쓰세요. 접근 로그는 5xx 가 ERROR, 4xx 가 WARN, 나머지가 INFO 이고, syslog 는 severity 숫자 3 이하가 ERROR, 4 가 WARN, 나머지가 INFO 입니다.

앞 단계에서 만든 세 NDJSON 을 읽어 쓰면 다시 파싱할 필요가 없습니다. severity 를 세 값으로 접는 것이 이 단계의 핵심입니다 — 원본마다 등급 체계가 달라서 그대로 두면 비교할 수 없습니다. 시각을 이미 같은 표기로 굳혀 두었으므로 정렬은 문자열 정렬이면 됩니다.

정규화 규칙을 문서로 남기기

/root/norm/norm_report.md## 무엇이 섞여 있었나 ## 어떻게 한 줄 한 레코드로 만들었나 ## 버린 줄과 그 이유 ## 다음에 받을 때의 요구사항 네 절로 적으세요. 전체 레코드 수 · app.log 물리적 줄 수 · 격리한 줄 수를 숫자로 포함해야 합니다.

정규화 규칙은 코드가 아니라 계약입니다. 어떤 줄을 레코드의 시작으로 보았는지, 시각을 어떻게 굳혔는지, 못 읽은 줄을 어디에 두었는지를 적어 두지 않으면 다음 사람이 같은 파일에서 다른 숫자를 냅니다. 숫자는 counts.json 에서 꺼내 쓰세요.