extract_postfix_maillog_sub.py
import re
import csv
from datetime import datetime
import argparse
from pathlib import Path
import logging
import socket
import os
import sys
#===============================================================================
# ロギング設定
#===============================================================================
def setup_logging():
host_name = socket.gethostname()
program_name = os.path.basename(__file__)
log_file_path = Path(__file__).resolve().parent.parent / 'log' / 'pythonlog.log'
logger = logging.getLogger()
logger.setLevel(logging.INFO)
file_handler = logging.FileHandler(log_file_path, encoding='utf-8')
file_handler.setFormatter(logging.Formatter(f'%(asctime)s {host_name} [{program_name}:%(levelname)s] [%(process)d] %(message)s', datefmt='%Y-%m-%d %H:%M:%S'))
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setFormatter(logging.Formatter(f'%(asctime)s {host_name} [{program_name}:%(levelname)s] [%(process)d] %(message)s', datefmt='%Y-%m-%d %H:%M:%S'))
logger.addHandler(file_handler)
logger.addHandler(stream_handler)
#===============================================================================
# 引数の解析
#===============================================================================
def parse_arguments():
parser = argparse.ArgumentParser(description="メールログファイルを解析して、特定の条件に基づいてフィルタリングし、結果をCSVに出力するスクリプト。")
parser.add_argument("--log_path", type=str, nargs='+', required=True, help="解析するメールログファイルへのパス。複数指定可能。")
parser.add_argument("--csv_append", type=str, required=True, help="出力するCSVファイル名に追加するテキスト。")
parser.add_argument("--csv_path", type=str, help="CSVファイルの保存先ディレクトリのパス。デフォルトはこのスクリプトのディレクトリ。", default=Path(__file__).parent)
parser.add_argument("--srv_type", type=str, required=True, choices=['ms', 'mr'], help="サーバの種類。[ms/mr]")
return parser.parse_args()
#===============================================================================
# ユーティリティ関数
#===============================================================================
def extract_data_from_line_ms(line):
"""
フィルタリングされた行から必要な情報を抽出し、抽出されたデータを返します。
"""
try:
date = extract_date_ms(line)
ms_hostname = extract_mshost_ms(line)
from_hostname = extract_find_pattern_common(line, "from ", " (")
from_ipaddress = extract_from_ipaddress_ms(line)
from_address = extract_find_pattern_common(line, "from=<", ">")
to_address = extract_find_pattern_common(line, "to=<", ">")
to_domain = "@" + to_address.split('@')[1] if '@' in to_address else ""
esmtp_id1, esmtp_id2 = extract_esmtp_ids_ms(line)
return [date, ms_hostname, from_hostname, from_ipaddress, from_address, to_domain, esmtp_id1, esmtp_id2]
except Exception as e:
logging.error(f"関数: extract_data_from_line_ms: {e}")
return None
def extract_data_from_line_mr(line):
"""
フィルタリングされた行から必要な情報を抽出し、抽出されたデータを返します。
"""
try:
date = extract_date_mr(line)
ms_hostname = extract_mshost_mr(line)
to_address = extract_find_pattern_common(line, "to=<", ">")
esmtpid = extract_esmtp_ids_mr(line)
return [date, ms_hostname, to_address, esmtpid]
except Exception as e:
logging.error(f"関数: extract_data_from_line_mr: {e}")
return None
def extract_date_ms(line):
"""
日付を抽出し、datetimeオブジェクトとして返します。
"""
date_pattern = re.compile(r'^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}')
date_match = date_pattern.match(line)
return datetime.strptime(date_match.group(0), '%Y/%m/%d %H:%M:%S') if date_match else None
def extract_date_mr(line):
"""
ログ行から日付を抽出し、datetimeオブジェクトとして返します。
ログの日付フォーマットが 'Mar 4 10:43:31' の形式であることを前提としています。
年跨ぎの可能性を考慮して、適切な年を日付に設定します。
"""
# 日付の正規表現パターンを定義します
date_pattern = re.compile(r'(\w+\s+\d+\s+\d+:\d+:\d+)')
match = date_pattern.search(line)
if match:
log_date_str = match.group(1)
now = datetime.now()
year = now.year
log_date = datetime.strptime(f"{year} {log_date_str}", '%Y %b %d %H:%M:%S')
if log_date > now:
log_date = log_date.replace(year=year - 1)
return log_date.strftime('%Y-%m-%d %H:%M:%S')
else:
return None
def extract_mshost_ms(line):
"""
メールサーバのホスト名を抽出します。
"""
pattern = r'\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} (\S+)'
match = re.search(pattern, line)
if match:
ms_hostname = match.group(1)
return ms_hostname
def extract_mshost_mr(line):
"""
メールサーバのホスト名を抽出します。
"""
pattern = r'\w+\s+\d+\s+\d+:\d+:\d+ (\S+)'
match = re.search(pattern, line)
if match:
ms_hostname = match.group(1)
return ms_hostname
def extract_find_pattern_common(line, start_pattern, end_pattern):
"""
特定文字列の範囲の文字列を抽出します。
"""
start = line.find(start_pattern) + len(start_pattern)
end = line.find(end_pattern, start)
return line[start:end]
def extract_from_ipaddress_ms(line):
"""
ipアドレスを抽出します。
"""
pattern = r"replace: header Received: from [^\s]+ \([^\s]+ \[?(\b(?:\d{1,3}\.){3}\d{1,3}\b)"
match = re.search(pattern, line)
return match.group(1)
def extract_esmtp_ids_ms(line):
"""
ESMTP IDを抽出します。
"""
esmtpid1_pattern = re.compile(r'with E?SMTP(S)? id (\S+)')
esmtpid1_match = esmtpid1_pattern.search(line)
esmtpid1 = esmtpid1_match.group(2).split("??")[0].strip(';') if esmtpid1_match else ""
esmtpid2_start = line.find(": ") + 2
esmtpid2_end = line.find(": ", esmtpid2_start)
esmtpid2 = line[esmtpid2_start:esmtpid2_end]
return esmtpid1, esmtpid2
def extract_esmtp_ids_mr(line):
"""
ESMTP IDを抽出します。
"""
esmtpid_start = line.find(": ") + 2
esmtpid_end = line.find(": ", esmtpid_start)
esmtpid = line[esmtpid_start:esmtpid_end]
return esmtpid
def write_data_to_csv_common(data, csv_file_path, csv_headers):
"""
抽出したデータをCSVファイルに書き込みます。
"""
try:
with open(csv_file_path, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(csv_headers)
writer.writerows(data)
logging.info(f"CSVファイル '{csv_file_path}' へのデータ書き込みが完了しました。")
except Exception as e:
logging.error(f"関数: write_data_to_csv_ms, CSVファイルへの書き込み中にエラーが発生しました: {csv_file_path}, エラー: {e}")
def filter_log_lines(file_path, include_patterns, exclude_patterns):
"""
ログファイルから指定されたすべてのフィルターパターンに一致し、
指定されたすべての除外パターンに一致しない行をフィルタリングします。
"""
filtered_lines = []
try:
with open(file_path, 'r', encoding='utf-8') as file:
for line in file:
# include_patterns すべてに一致し、exclude_patterns に一致しないかどうかを判定
if all(pattern.search(line) for pattern in include_patterns) and not any(pattern.search(line) for pattern in exclude_patterns):
filtered_lines.append(line)
except Exception as e:
logging.error(f"ログファイルのフィルタリング中にエラーが発生しました: {file_path}, エラー: {e}")
return filtered_lines
def process_log_files(log_file_paths):
"""
ログファイルを処理してCSVファイルを生成します。
"""
script_name = Path(__file__).stem
for log_file_path in log_file_paths:
log_file_name = Path(log_file_path).stem
csv_file_name = f"{script_name}_{args.csv_append}_{log_file_name}.csv"
csv_file_path = Path(args.csv_path) / csv_file_name
# ログファイルからフィルタリング
filtered_lines = filter_log_lines(log_file_path, include_patterns, exclude_patterns)
if not filtered_lines:
logging.warning(f"ファイル '{log_file_path}' に一致する行がありません。")
continue
# フィルタリングされた行からデータを抽出
if args.srv_type == 'ms':
all_data = []
for line in filtered_lines:
data = extract_data_from_line_ms(line)
if data is not None:
all_data.append(data)
if not all_data:
logging.warning(f"ファイル '{log_file_path}' から有効なデータが抽出されませんでした。")
continue
elif args.srv_type == 'mr':
all_data = []
for line in filtered_lines:
data = extract_data_from_line_mr(line)
if data is not None:
all_data.append(data)
if not all_data:
logging.warning(f"ファイル '{log_file_path}' から有効なデータが抽出されませんでした。")
continue
# CSVファイルへの書き込み
if args.srv_type == 'ms':
csv_headers = ['Date', 'MS HostName', 'From HostName', 'From IPAddress', 'From:', 'To Domain:', 'ESMTP ID1', 'ESMTP ID2']
write_data_to_csv_common(all_data, csv_file_path, csv_headers)
logging.info(f"ファイル '{log_file_path}' の処理が完了しました。")
elif args.srv_type == 'mr':
csv_headers = ['Date', 'MS HostName', 'To:', 'ESMTP ID']
write_data_to_csv_common(all_data, csv_file_path, csv_headers)
logging.info(f"ファイル '{log_file_path}' の処理が完了しました。")
if __name__ == "__main__":
setup_logging()
args = parse_arguments()
logging.info(f"プログラムを開始します。オプション: {args}")
if args.srv_type == 'ms':
try:
# 複数のフィルターパターンと除外パターンをリストとして定義
include_patterns = [
re.compile(r': replace: header Received: from .*'),
# 他のフィルターパターンをここに追加
]
exclude_patterns = [
re.compile(r': replace: header Received: from (ws|tp|wp)bkbmp0ms00[1-4]f0'),
# 他の除外パターンをここに追加
]
process_log_files(args.log_path)
except Exception as e:
logging.error(f"プログラムの実行中にエラーが発生しました: {e}")
finally:
logging.info("プログラムを終了します。")
elif args.srv_type == 'mr':
try:
# 複数のフィルターパターンと除外パターンをリストとして定義
include_patterns = [
re.compile(r'postfix/virtual'),
re.compile(r'to='),
# 他のフィルターパターンをここに追加
]
exclude_patterns = [
re.compile(r'bkbmp01_BCC@bk.mufg.jp'),
re.compile(r'bkbmp01_NDR@bk.mufg.jp'),
re.compile(r'bkbmp01_BCC@bk.mufg.local'),
re.compile(r'bkbmp01_NDR@bk.mufg.local'),
# 他の除外パターンをここに追加
]
process_log_files(args.log_path)
except Exception as e:
logging.error(f"プログラムの実行中にエラーが発生しました: {e}")
finally:
logging.info("プログラムを終了します。")