import argparse
import json
from collections import Counter
from datetime import datetime
from typing import Dict, List, Tuple
def parse_log_line(line: str) -> Tuple[str, str, int, int] | None:
"""로그 한 줄을 검증하고 필요한 값을 반환합니다."""
fields = line.strip().split()
if len(fields) != 6:
return None
timestamp, ip, _method, _path, status_text, response_time_text = fields
try:
parsed_time = datetime.fromisoformat(timestamp)
status_code = int(status_text)
response_time = int(response_time_text)
except ValueError:
return None
return f"{parsed_time.hour:02d}", ip, status_code, response_time
def analyze_log(file_path: str) -> Tuple[Dict[str, int], Counter[str], int, int]:
"""로그 파일을 읽어 통계와 건너뛴 줄 수를 계산합니다."""
hourly: Dict[str, int] = {f"{hour:02d}": 0 for hour in range(24)}
ip_counts: Counter[str] = Counter()
error_count = 0
valid_count = 0
skipped = 0
with open(file_path, "r", encoding="utf-8") as log_file:
for line in log_file:
parsed = parse_log_line(line)
if parsed is None:
skipped += 1
continue
hour, ip, status_code, _response_time = parsed
hourly[hour] += 1
ip_counts[ip] += 1
valid_count += 1
if status_code >= 500:
error_count += 1
return hourly, ip_counts, error_count, valid_count, skipped
def build_result(
hourly: Dict[str, int],
ip_counts: Counter[str],
error_count: int,
valid_count: int,
skipped: int,
) -> Dict[str, object]:
"""최종 JSON 출력용 객체를 생성합니다."""
top_ips: List[List[object]] = [
[ip, count]
for ip, count in sorted(
ip_counts.items(),
key=lambda item: (-item[1], item[0]),
)[:5]
]
error_rate = round(error_count / valid_count * 100, 1) if valid_count else 0.0
return {
"hourly": hourly,
"top_ips": top_ips,
"error_rate": error_rate,
"skipped": skipped,
}
def main() -> None:
"""명령줄 인자를 처리하고 결과를 출력합니다."""
parser = argparse.ArgumentParser()
parser.add_argument("file", nargs="?", default="access.log")
args = parser.parse_args()
hourly, ip_counts, error_count, valid_count, skipped = analyze_log(args.file)
result = build_result(
hourly,
ip_counts,
error_count,
valid_count,
skipped,
)
print(json.dumps(result, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()
기본 파일을 사용하려면 python script.py로 실행하시고, 다른 로그 파일은 python script.py 경로/파일명.log로 실행하시면 됩니다.