🤖

pythonをチャッピー(ChatGPT)に教えてもらう (ログ解析ツールの作成) #2

に公開

はじめに

しばらく実務でコーディングの機会がなかったので、定期的にChatGPTから課題をもらって、それを解くことでコーディングの習慣を取り戻したいと思います。

チャッピー(ChatGPT)から与えられた課題

課題:ログ解析ツールの作成
課題概要: サーバーログ(ApacheやNginxのログファイルなど)を解析して、
エラーメッセージ(HTTPステータスコード500, 404 など)の頻度をカウントし、
結果をCSV形式で出力するプログラムを作成します。
ステップ:

  1. ログファイルを読み込み、行ごとに解析する。
  2. 正規表現を使ってエラーメッセージを抽出。
  3. エラーメッセージの出現頻度をカウント。
  4. 結果をCSV形式で保存。
    難易度: 中級
    使用するライブラリ: re, csv

追加のヒント:

どんなログ形式でも動作するように、柔軟なエラーメッセージ抽出機能を実装します。
pandasを使って、エラーの頻度に基づいた集計や分析も行ってみてください。

自分で書いたソースコード

import re
import csv
from typing import List

LOG_FILE = "access.log"
OUTPUT_FILE = "error_message_count.csv"

def read_log_file(file_path: str) -> List[str]:
    log_data_list = []

    with open(file_path, "r") as file:
        for line in file:
            log_data_list.append(line)
    return log_data_list

def count_error_message(log_data_list: List[str]) -> None:
    error_message_count = {}

    for line in log_data_list:
        # エラーメッセージを抽出
        error_message = re.search(r'HTTP/1.1" (\d{3})', line)
        if error_message:
            error_code = error_message.group(1) # エラーコードを取得
            if error_code in error_message_count: # エラーコードが既に存在する場合はカウントを増やす
                error_message_count[error_code] += 1
            else:
                error_message_count[error_code] = 1
    # 結果をCSV形式で保存
    with open(OUTPUT_FILE , "w", newline="") as csvfile:
        writer = csv.writer(csvfile)
        writer.writerow(["Error Code", "Count"])
        for error_code, count in error_message_count.items():
            writer.writerow([error_code, count])

def main():
    # ログファイルを読み込む
    log_data_list = read_log_file(LOG_FILE)
    # エラーメッセージの頻度をカウント
    count_error_message(log_data_list)

if __name__ == "__main__":
    main()

チャッピー(ChatGPT)の評価

80点程度/100点

全体として、基本的な機能はしっかりと実装されていますが、エラーハンドリングやテスト、柔軟性の向上に向けた改善が必要です。これらの点を考慮すると、80点という評価になります。

リファクタリング

リファクタリングの主なポイント

  • エラーハンドリング: read_log_file関数にファイル読み込み時のエラーハンドリングを追加しました。ファイルが見つからない場合や他のエラーが発生した場合に、適切なメッセージを表示します。
  • 型ヒントの追加: 各関数に型ヒントを追加し、引数と戻り値の型を明示しました。
  • 関数の分割: エラーメッセージのカウントとCSVへの保存を別の関数に分け、各関数が単一の責任を持つようにしました。
  • 可読性の向上: コードの可読性を向上させるために、関数名や変数名を明確にしました。

修正後のソースコード

import re
import csv
from typing import List, Dict

LOG_FILE = "access.log"
OUTPUT_FILE = "error_message_count.csv"

def read_log_file(file_path: str) -> List[str]:
    """指定されたファイルからログデータを読み込む"""
    try:                                          # 【修正】:ファイルが存在するかどうかを確認
        with open(file_path, "r") as file:
            return file.readlines()
    except FileNotFoundError:
        print(f"Error: File {file_path} not found.")
        return []
    except Exception as e:
        print(f"Error reading file {file_path}: {e}")
        return []

def count_error_messages(log_data_list: List[str]) -> Dict[str, int]:
    """エラーメッセージの頻度をカウントする"""
    error_message_count = {}

    for line in log_data_list:
        error_message = re.search(r'\s(\d{3})\s', line)
        if error_message:
            error_code = error_message.group(1)
            error_message_count[error_code] = error_message_count.get(error_code, 0) + 1 

    return error_message_count

def save_to_csv(data: Dict[str, int], output_file: str) -> None: # 【修正】:CSVへの保存を別の関数に分け、単一の責任を持つようにした
    """エラーメッセージの頻度をCSV形式で保存する"""
    try:
        with open(output_file, "w", newline="") as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow(["Error Code", "Count"])
            for error_code, count in data.items():
                writer.writerow([error_code, count])
    except Exception as e:
        print(f"Error writing to file {output_file}: {e}")

def main() -> None:
    """メイン処理"""
    log_data_list = read_log_file(LOG_FILE)
    if log_data_list:
        error_counts = count_error_messages(log_data_list)
        save_to_csv(error_counts, OUTPUT_FILE)

if __name__ == "__main__":
    main()

学んだポイント

  • またエラーハンドリングを忘れてしまった。少なくともファイルの読み書きがあるところは忘れないようにしなければ
  • エラーメッセージの頻度をカウントする関数内でファイルの保存を書き込んでしまった。単一の責任を強く意識して見直そう
  • 次はテスト駆動で作ってもいいかも

Discussion