【Python】コンソールでのログ監視をFTP経由で行いたい
レンタルサーバーなどで、FTP接続しかできない開発者泣かせの環境があったりします。
こんな環境ではログファイルをみるのも大変々々。
「SSHを使えればなぁ」と夢想しつつ、FTPクライアントからファイルをダウンロードして開いて末尾を確認という非生産的な行為を繰り返すうちに
「誰かの陰謀ではないか?」と世の中疑り出す始末。。。
こんなときこそプログラムでしょ♪
こんなときこそ、手動で行っている作業をプログラムで自動化すべきです。
開発スイッチが入りました。
手順を整理するとこんな感じ!!
(Linxコマンドでいう tail -f [file] のようなことをやりたいのです)
1. FTP接続(指定した環境にPythonからFTP接続) 2. ログファイルのDWと出力(ログファイルをダウンロードして、末尾の何行かをコンソールに出力) 3. 更新有無の判定(ログファイルが追記されているか確認) 4. 更新分の出力(ログファイルが追記されていれば、再びダウンロードして追記分を出力) (以後3,4を繰り返す)
1. FTP接続
# -*- coding: utf-8 -*- import ftplib import time host = '' # 接続先のホスト名 user = '' # 接続先のユーザ名 password = '' # 接続先のパスワード ftp = ftplib.FTP(host, user, password)
pythonのftplibライブラリを使用します。
21.13. ftplib — FTPプロトコルクライアント — Python 3.5.1 ドキュメント
ホスト、ユーザ名、パスワードを引数に渡してインスタンスを生成します。
以後は、このFTPクラスを通して接続先とファイルの状態のチェックやダウンロードなどのやり取りを行います。
FTP接続先に用事があったらこいつに問い合わせる感じでしょうか...
一度FTPに接続したら、Pythonの関数(os.path.exists)などでアクセスできるのかと思いましたが、Pythonから直接FTP先にアクセスは無理なのでそんなことはできないです。
2. ログファイルのDWと出力
logpath = '/var/www/log/' logfile = 'test.log' # ダウンロードデータの受け皿 content = bytearray() # ダウンロードしたバイトデータを引数に呼ばれる def download(data): for b in data: content.append(b) # 該当ディレクトリに移動してダウンロード ftp.cwd(logpath) ftp.retrbinary("RETR %s" % logfile, download) logdata = content.decode('utf-8')
目的のログファイルをダウンロードします。
まずは、ftp.cwd() で目的のパスへ移動します。(Linxでいうと cd ですね)
パスの指定は、FTPのルートディレクトリからの絶対パスでの指定となります。
当然といえば当然なのですが、サーバのルートディレクトリとは異なる環境もあります。(FTPクライアントで接続するとリモートサイトのパスが表示されるのでそれを参考にするとよいです。)
ftp.retrbinary() が対象ファイルのダウンロード処理で、第1引数はFTPコマンドでファイルを指定。
FTPコマンドをPythonから使えるようにラップしたものが、fitlib と思うのですが、ここでは素のFTPコマンドを指定します。
(つまり、ダウンロード以外のFTPコマンドも指定できるということでもあります。マニュアルをみて理解できない点でも、FTPコマンドを調べてみると納得いくことが多々ありました。)
FTPコマンド一覧
FTPコマンドの一覧 - Wikipedia
第2引数は、コールバック関数を指定。(ここでは、download関数を指定)
引数の dataにダウンロードした内容が、バイナリで格納されます。
ログファイルのサイズが大きいときは、download() が何回かに分けて呼ばれます。
その影響で、バイナリ → UTF-8へのデコード処理を、download() 関数内で行うのでは、上手くいきませんでした。
(数バイトの固まりで意味を成す、マルチバイトのデータが途中でちょん切れてしまってうまくできないようです)
そこで「分かれてきたデータをバイナリのまま結合して、最後にデコードする」という手段をとりました。
logdata = logdata.strip().split("\n") # 末尾の行数を覚えておく index = len(logdata) for oneRow in logdata[-3:]: print(oneRow ) # ダウンロードファイルの受け皿として後でも使うので初期化 content = bytearray()
ダウンロードしたデータを配列に変換して末尾の3行をコンソールに出力します。
この配列の要素数が、ファイルの行数になります。追加分の出力のとき必要なため、末尾の行数を覚えておきます。
前後の改行は、strip() メソッドで無視します。
3. 更新有無の判定
# ファイルサイズを取得 # 失敗する場合は、FTP接続先がバイナリモードでない可能性がある。ftp.sendcmd("TYPE I") でバイナリモードしてから実行すると解決するかも size = ftp.size(logfile)
ログファイルが追記されているかの判定は、ファイルサイズで行います。
ftp.size() でファイルのサイズ取得を行います。
4. 更新分の出力
前回の値と比較して、大きい場合のみ再度、2. と同じようにダウンロードして
追加分(前回の末尾の行数から今回の末尾まで)を出力します。
このままだと無限ループになるため、ctrl+c で終了できるようにしました。
クラッシュしないように、time.sleepでウェイトをいれています。
# ウェイトを置かないと処理が止まる time.sleep(2.0) try: while True: old_size = size size = ftp.size(logfile) # 前回のサイズと比較 if old_size < size: ftp.retrbinary("RETR %s" % logfile, download) logdata = content.decode('utf-8') logdata = logdata.strip().split("\n") # 前回との差分を出力 for oneRow in logdata[index:]: print(oneRow) index = len(logdata) time.sleep(2.0) except KeyboardInterrupt: ftp.close() except: ftp.close()
おわり
こんな感じでFTP経由でログファイル監視が実現できました。
整理改良したものをGitHubに置いておきます。
Python FTP接続でログファイルを監視する · GitHub
次の野望はこいつをGUIのアプリケーション化することです。 PyQtがなかなか素晴らしいそうなのでやってみたいです。 久しぶりの投稿でした。