先月書いたXSERVERのaccess_log内IPアドレス欄をFQDNへ変換(サクラエディタ+logresolve編)(https://kadono.xsrv.jp/2018/06/06/4658)では一々面倒なのでPython3.6で動くスクリプトを作成しました。Windows10およびCentOS6.9(VirtualBox)上のPython3.6 (CentOS6.9は後から入れる必要あり)で動いています。入力がログファイルで標準出力に逆引き後のログが出てきます。ファイル入力のみでlogresolveとは異なり標準入力からのストリーム動作(?)には対応していません。XSERVERのログが日毎なので私はこれで問題ありません。また、ファイルを2回読みすることで最初は重複削除と逆引きだけ(Max.100スレッド逆引き)行い、2パス目で逆引き結果の文字列置換を行います。仮想ホスト名はこのサイトのURLで決め打ち、変換後のログからは削除しています。
コードの大半はRuby, Pythonで並列に逆引きを行う(http://0xcc.net/blog/archives/000099.html)で公開されているPython用のマルチスレッド逆引きプログラムです。一部Python2向けの部分などを直しています。DNSの逆引き+タイムアウト待ち時間が処理時間の大半を占めるため、このマルチスレッド処理が無ければ普通にlogresolveを使ったほうが速いと思います。
マルチスレッド処理と結果を辞書で処理するアイデアはpythonでマルチスレッドで処理して各スレッドの結果を受け取る(https://qiita.com/komorin0521/items/c25cfcff89c0b1afe882)を参考にしています。
IPアドレスの重複削除についてはPython Tips:リストから重複した要素を削除したい(https://www.lifewithpython.com/2013/11/python-remove-duplicates-from-lists.html)を参考にしました。紹介されている内包表記など凝った書き方は勉強不足で十分理解できていないため使用していません。
また、IPアドレスの正規表現は5.2.3 IPアドレスのフォーマットチェック(正規表現)(http://www.geolocation.co.jp/learn/program/07.html)を多少変更(修正?)して使用しています。
ほとんどGoogleで検索して組み合わせるだけでスクリプトが完成しているので大変楽になったと思います。
""" mt-logresolver """
# -*- coding: utf-8 -*-
# http://0xcc.net/blog/archives/000099.html
# https://qiita.com/komorin0521/items/c25cfcff89c0b1afe882
# https://www.lifewithpython.com/2013/11/python-remove-duplicates-from-lists.html
# http://www.geolocation.co.jp/learn/program/07.html
import re
import threading
import queue
import socket
import sys
def lookup(ip_address):
try:
return socket.gethostbyaddr(ip_address)[0]
except socket.herror:
return ip_address
def resolver(queue, lock, resolver_dict):
while True:
ip_address = queue.get()
if ip_address is None:
break
host_name = lookup(ip_address)
lock.acquire()
try:
resolver_dict[ip_address] = host_name
finally:
lock.release()
def logresolver(filename):
queue1 = queue.Queue()
num_threads = 100
re_pattern_1 = re.compile(r"^kadono.xsrv.jp ((([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])) (.*)")
resolver_dict = dict()
ip_address_list = []
fp = open(filename, 'r')
for line in fp:
pattern_1 = re_pattern_1.search(line)
if pattern_1:
ip_address = pattern_1.group(1)
if ip_address not in ip_address_list:
queue1.put(ip_address)
ip_address_list.append(ip_address)
fp.close()
lock = threading.Lock()
for i in range(num_threads):
queue1.put(None)
thread = threading.Thread(target = resolver, args = (queue1, lock, resolver_dict))
thread.start()
thread_list = threading.enumerate()
thread_list.remove(threading.main_thread())
for thread in thread_list:
thread.join()
fp = open(filename, 'r')
for line in fp:
pattern_1 = re_pattern_1.search(line)
if pattern_1:
print(resolver_dict[pattern_1.group(1)]+" "+pattern_1.group(5))
fp.close()
if __name__ == '__main__':
logresolver(sys.argv[1])
決め打ちにしている部分(最大スレッド数、仮想ホストURL)などまだまだ改良の余地はあると思います。しかし、先延ばしにしているといつ出せるかわからないので見切りで公開します。
※動作チェックはこのサイトのXSERVERログのみでnginxのログ全般で動くかどうかは未確認です。
後日追記)経験上100位ならば問題ないと思いますけど、num_threadsを大きくしてリソースが少ないマシンで走らせるとRuntimeError: can’t start new threadで止まることがあります。その場合はthread数を少なくすると動くようです。Python3.6デフォルトでの限界はPythonとRubyでthreadとfiberをいくつ作れるか検証して見た(https://qiita.com/yohm/items/32e5965cc7b561dc8e4e)によると2048のようです。