Python - apache のログを編集する - ソース

 クラウディア
1. 概要
2. ソース

1. 概要

 思ったより、簡単にできましたが、後でなんでこんな処理になったんだろうとわからなくなるのが「や」なので、メモを残しておきます。

2. ソース

 ソースは、こんなんなりました。
import configparser
import datetime
import locale
import os

## 除外ホスト名
exclude_host = None

## 除外 URL
exclude_url  = None

##
# @brief ログチェック
# $param line 1行分のログ

def check(line):
    global exclude_host, exclude_url

    for exclude in exclude_host:
        if (exclude) in line:                                                       ## 除外ホストを含む
            return False

    for exclude in exclude_url:
        if (exclude) in line:                                                       ## 除外 URL を含む
            return False

    return True

##
# @brief メイン

def main():
    global exclude_host, exclude_url

    locale.setlocale(locale.LC_ALL, 'ja_JP.UTF-8')                                  ## ロケールの設定
    past = datetime.datetime.now()
    print('処理開始', past.strftime('%X'))

    config_file = '/usr/local/etc/mine/accesslog.conf'
    config   = configparser.ConfigParser()
    config.read(config_file)

    read_directory  = config.get('DIRECTORY', 'READ')								## 読み込みディレクトリ
    read_file       = config.get('FILE', 'READ').split()
    write_directory = config.get('DIRECTORY', 'WRITE')
    write_file      = config.get('FILE', 'WRITE')
    exclude_host    = config.get('EXCLUDE', 'HOST').split()
    exclude_url     = config.get('EXCLUDE', 'URL').split()

    ## 1ファイルずつ読んで、書き込むようにしないと、
    ## 1度にバッファリングするとメモリを使用しすぎて
    ## swap したまま返ってこなくなる・・・

    write_file_path = write_directory + write_file
    f = open(write_file_path, "w")                                                  ## File Open
    f.close()                                                                       ## Close

    for file in read_file:                                                          ## 変換前のログ読み込み
        read_file_path = read_directory + file
        print(read_file_path)

        if (not os.path.exists(read_file_path)):                                    ## ないファイルも
            continue                                                                ## あるかもしれない
        
        lines = []

        with open(read_file_path, 'rb') as f:
            for line in f:
                try:                                                                ## UTF-8 にエンコード
                    line = line.decode('utf-8')
                except UnicodeDecodeError:                                          ## エンコードできないものがある
                    line = line.decode('latin-1')

                ## 読み込んで書き込み時にフィルタリングするより
                ## 読み込みの時点でフィルタリングする方が多分 早い

                if (check(line)):                                                   ## チェック
                    lines.append(line)                                              ## 問題ないログは採用

        f.close()                                                                   ## Close

        ## 日時順にならべようと思っていたら
        ## webalizer に食わせるので逆順にしてはいけないのであった
        #lines.reverse()

        write_file_path = write_directory + write_file
        f = open(write_file_path, "a")                                              ## File Open
        f.writelines(lines)                                                         ## 問題ないログは Write
        f.close()                                                                   ## Close

    now = datetime.datetime.now()
    print('処理終了', now.strftime('%X'))
    print('処理時間', datetime.datetime.now() - past)

if __name__=='__main__':
    main()


 6~10行は、「ini」ファイルから取得する、除外ホスト、除外 URL を関数で使用するためにグローバル変数化しています。  16~27行は、ログ1行分を受け取って、上の徐ホスト・除外 URL の文字列が行内に存在するかどうかを判断して、含む場合は有効な行として「False」を、含まない場合は無効な行として「True」を返す関数です。  29~93行がメインになります。  33行目は、除外ホスト、除外 URL の変数をグローバル宣言しています。  35~37行は、ロケールの設定、処理開始時刻の表示です。  39~48行は、前ページに示した「ini」ファイルの読込です。  54~56行は、フィルタリング後のログファイルをいったん開いて、0行にしています。  以降の処理で、オープン・アペンドを繰り返すので、クリアしておいているのです。  当初、全行一気に書き込もうと思っていたのですが、巨大になりすぎて、プロセスがスワップアウトして、スワップの領域も確保できず、いつまでも返ってこない状況に陥ったので、少し小さな単位で読み書きするようにしました。  58~89行で、元のログファイルを1ファイルずつ処理します。  59~63行は、元のログファイルのフルパスを生成・表示、もしない場合は処理をスキップしています。  65~80行で、元のログファイルの読み込みとフィルタリングを行っています。  当初、全部読み込んでフィルタリングを使用としていましたが、読み込みながらフィルタリングする方が、圧倒的に速いことがわかりました。  読み込んだ後でフィルタリングすると 11分かかっていた処理が読み込みながらフィルタリングするようにして 3分ちょっとの時間になりました。  65行は、フィルタ後の行のリストを初期化しています。  67行は、オープンしているのですが、あえて「rb」と、バイナリ「read」で開きます。  68~78行が1行ずつの処理になります。  69~72行は、「UTF-8」エンコードで読み込んで、できない場合は、「latin-1」エンコードで読み込みます。  これは、デフォルト「UTF-8」で読み込もうとするのですが、全行これで読み込もうとすると壊れたレコードがあると、例外で停止してしまうのを防ぐためです。  77行で、1行をチェックして、除外ワードがなければ、リストに追加していきます。  86~89行で、リストアップされた行をフィルタリング後のログファイルに追加していきます。  ここで特に注意すべきなのが・・・。  解析プログラムで、「webalizer」を使用する場合、ログが日時の古い順にならんでいないと処理できないということです。  なので、当初、新しい順に並べかえようとした、82~84の行はコメントアウトしました。  元々のログファイルは、古いログから新しいログの順に並んでいます。  追加書込みなので当たり前ですね。  合わせて、古いログファイルから順に処理することで古いログ~新しいログの順に並ぶので、「webalizer」うまく処理することができます。  順番が違うと、「webalizer」は、1ヶ月で1日分のログ解析しか出力されません。  91~93行で、処理終了時刻と処理に要した時間を出力しています。  95~96行は、プロセスの入り口で、メインをコールするのみです。
ハイスピードプラン健康サポート特集