monitor_postfix_queue_sub.py
"""
機能 : ログファイルをCSV形式に変換して、指定された時間より古いキュー情報を抽出します。
引数 : progname.py $1 $2 $3 $4
$1: 入力ログファイルパス
$2: 出力CSVファイルパス
$3: 最近の到着情報CSVパス
$4: 指定された時間
"""
import os
import csv
from datetime import datetime, timedelta
import sys
import logging
import socket
from pathlib import Path
import inspect
#===============================================================================
# ロギング設定
#===============================================================================
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 convert_to_datetime(arrival_time_str):
"""
処理:
指定された日時文字列をdatetimeオブジェクトに変換する
備忘:
datetime型には年情報が必要だが、Arrival Timeには年情報がない。
よって、現在の年を年情報 or 前年を年情報とする。
現在の年にするか、前年度にするかは以下処理で考える。
現在の年情報+Arrival Time vs 現在の時間
<=: 現在の年情報
> : 前年の年情報
"""
try:
current_year = datetime.now().year
full_date_str = f"{current_year} {arrival_time_str}"
arrival_datetime = datetime.strptime(full_date_str, '%Y %a %b %d %H:%M:%S')
if arrival_datetime > datetime.now():
full_date_str = f"{current_year - 1} {arrival_time_str}"
arrival_datetime = datetime.strptime(full_date_str, '%Y %a %b %d %H:%M:%S')
return arrival_datetime
except Exception as e:
logging.error(f"{e}, 関数: {inspect.currentframe().f_code.co_name}")
return None
def process_file(input_file_path, output_file_path, recent_arrivals_file_path, minutes):
"""
ログ種類と処理方針
No: 処理方針: ログ
1: skip : -Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------
2: execute: 3BA8F44328E 7188 Mon Mar 11 01:00:13 btmu_info@t.ebusiness.bk.mufg.jp
3: execute: (connect to rvxp.avz.se[3.64.163.50]:25: Connection time out)
4: execute: pwlvspw.cptpewkp@rvxxp.avz.se
5: skip :<空行>
"""
data_list = []
recent_arrivals_list = []
smtp_send_failure_reason = ''
current_data = []
try:
with open(input_file_path, 'r') as file:
for line in file:
line = line.strip()
if line.startswith('-Queue ID-') or line.startswith('--') or not line: # 不要情報の行(ヘッダ、末尾、空行)
continue
parts = line.split()
if (len(parts) > 6 and (parts[0][0].isalnum() or parts[0][0].isupper()) and
parts[1].isdigit() and ':' in parts[5] and '@' in parts[6]): # キュー情報の行
# 前のキューに関連する受信者を処理
flush_recipients(data_list, recent_arrivals_list, current_data, smtp_send_failure_reason, minutes)
# 新しいキュー情報の初期化
queue_id = parts[0]
size = parts[1]
arrival_time_str = ' '.join(parts[2:6])
sender = parts[6]
arrival_datetime = convert_to_datetime(arrival_time_str)
current_data = [queue_id, size, arrival_time_str, arrival_datetime, sender]
smtp_send_failure_reason = '' # 新しいキューのためリセット
elif '@' in line and not line.startswith('('): # 受信者の行
recipient = line.strip()
current_data.append((recipient, smtp_send_failure_reason))
elif line.startswith('('): # エラー情報の行
smtp_send_failure_reason = line
# 最後のキューに関連する受信者を処理
flush_recipients(data_list, recent_arrivals_list, current_data, smtp_send_failure_reason, minutes)
# CSVに書き込み
write_to_csv(output_file_path, data_list, ['Queue ID', 'Size', 'Arrival Time', 'Arrival DateTime', 'Sender', 'Recipient', 'Failure Reason'])
write_to_csv(recent_arrivals_file_path, recent_arrivals_list, ['Queue ID', 'Size', 'Arrival Time', 'Arrival DateTime', 'Sender', 'Recipient', 'Failure Reason'])
logging.info("ファイル処理が正常に完了しました。")
except Exception as e:
logging.error(f"{e}, 関数: {inspect.currentframe().f_code.co_name}")
def flush_recipients(data_list, recent_arrivals_list, current_data, smtp_send_failure_reason, minutes):
for recipient, failure_reason in current_data[5:]:
complete_data = current_data[:5] + [recipient, failure_reason]
data_list.append(complete_data)
if complete_data[3] < datetime.now() - timedelta(minutes=int(minutes)):
recent_arrivals_list.append(complete_data)
current_data.clear() # 現在のキューデータをクリア
def write_to_csv(file_path, data_list, headers):
"""データリストを指定されたCSVファイルに書き込む"""
try:
with open(file_path, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile, lineterminator='\n')
writer.writerow(headers)
for row in data_list:
writer.writerow([row[0], row[1], row[2], row[3], row[4], row[5], row[6] if len(row) > 6 else '']) # Failure Reasonがない場合は空文字を挿入
except Exception as e:
logging.error(f"{e}, 関数: {inspect.currentframe().f_code.co_name}")
def main():
"""メイン関数"""
try:
setup_logging()
logging.info("プログラムを開始します")
input_file_path = sys.argv[1]
output_file_path = sys.argv[2]
recent_arrivals_file_path = sys.argv[3]
minutes = int(sys.argv[4])
process_file(input_file_path, output_file_path, recent_arrivals_file_path, minutes)
except Exception as e:
logging.error(f"{e}, 関数: {inspect.currentframe().f_code.co_name}")
if __name__ == '__main__':
try:
main()
except Exception as e:
logging.error(f"{e}")
finally:
logging.info("プログラムを終了します")