Symfoware

Symfowareについての考察blog

Python logging 各種出力ハンドラーの使い方について

Pythonのloggingモジュールについてちょっと調べてみました。
Python loggingモジュールの基本的な使い方


コンソールへの出力とファイルへの出力を試しましたが、それ以外の
出力ハンドラーについても調べたのでメモしておきます。



よく使うハンドラーの種類とimport元



よく使うであろうハンドラーはこんなかんじでしょうか。

loggingパッケージ
StreamHandler(コンソールへの出力)
FileHandler(ファイルへ出力)

logging.handlersパッケージ
SMTPHandler(メール送信)
RotatingFileHandler(容量によりローテーションするログファイルへ出力)
TimedRotatingFileHandler(時間によりローテーションするログファイルへ出力)
SocketHandler(TCPソケットへ出力)
DatagramHandler(UDPソケットへ出力)
HTTPHandler(http通信して出力)

※これ以外にもsyslogやWindowsのイベントログに出力するハンドラーもあります。


一つづつ見てみます。





StreamHandlerの使用例



StreamHandlerを使用したサンプルはこんな感じになります。


  1. # -*- coding:utf-8 -*-
  2. import logging
  3. #rootロガーを取得
  4. logger = logging.getLogger()
  5. logger.setLevel(logging.DEBUG)
  6. #出力のフォーマットを定義
  7. formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
  8. #sys.stderrへ出力するハンドラーを定義
  9. sh = logging.StreamHandler()
  10. sh.setLevel(logging.DEBUG)
  11. sh.setFormatter(formatter)
  12. #rootロガーにハンドラーを登録する
  13. logger.addHandler(sh)
  14. logger.debug('debugメッセージ')
  15. logger.info('infoメッセージ')
  16. logger.warn('warnメッセージ')
  17. logger.error('errorメッセージ')
  18. logger.critical('criticalメッセージ')




プログラムを実行すると、実行したコンソールにこんなログが表示されると思います。


2011-12-28 20:41:01,361 - DEBUG - debugメッセージ
2011-12-28 20:41:01,362 - INFO - infoメッセージ
2011-12-28 20:41:01,363 - WARNING - warnメッセージ
2011-12-28 20:41:01,363 - ERROR - errorメッセージ
2011-12-28 20:41:01,363 - CRITICAL - criticalメッセージ








FileHandlerの使用例



ログの内容をファイルに出力するFileHandlerの例はこんな感じ。


  1. # -*- coding:utf-8 -*-
  2. import logging
  3. #rootロガーを取得
  4. logger = logging.getLogger()
  5. logger.setLevel(logging.DEBUG)
  6. #出力のフォーマットを定義
  7. formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
  8. #ファイルへ出力するハンドラーを定義
  9. fh = logging.FileHandler(filename='log.txt')
  10. fh.setLevel(logging.DEBUG)
  11. fh.setFormatter(formatter)
  12. #rootロガーにハンドラーを登録する
  13. logger.addHandler(fh)
  14. logger.debug('debugメッセージ')
  15. logger.info('infoメッセージ')
  16. logger.warn('warnメッセージ')
  17. logger.error('errorメッセージ')
  18. logger.critical('criticalメッセージ')




スクリプトと同じ階層に「log.txt」というログのメッセージが記載された
ファイルが出力されると思います。







SMTPHandlerの使用例



ログの内容をメールで送信するSMTPHandlerの例はこんな感じ。
ちなみに、SMTPHandlerを使用するには、logging.handlersをimportしてやります。


  1. # -*- coding:utf-8 -*-
  2. import logging
  3. import logging.handlers
  4. #rootロガーを取得
  5. logger = logging.getLogger()
  6. logger.setLevel(logging.DEBUG)
  7. #出力のフォーマットを定義
  8. formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
  9. #メールを送信するハンドラーを定義
  10. sh = logging.handlers.SMTPHandler(
  11.     mailhost='mailhost.example.com',
  12.     fromaddr='from@example.com',
  13.     toaddrs=['toadmin@example.com'],
  14.     subject='SMTPHandlerから送信されるメールのタイトル'
  15. )
  16. sh.setLevel(logging.DEBUG)
  17. sh.setFormatter(formatter)
  18. #rootロガーにハンドラーを登録する
  19. logger.addHandler(sh)
  20. logger.debug('debugメッセージ')
  21. logger.info('infoメッセージ')
  22. logger.warn('warnメッセージ')
  23. logger.error('errorメッセージ')
  24. logger.critical('criticalメッセージ')




toaddrsに指定したメールアドレスに、ログの内容が本文に記載された
メールが送信されると思います。






RotatingFileHandlerの使用例



ログファイルが指定した容量に達したとき、出力するログファイルを
切り替えるRotatingFileHandlerの例はこんな感じ。


  1. # -*- coding:utf-8 -*-
  2. import logging
  3. import logging.handlers
  4. #rootロガーを取得
  5. logger = logging.getLogger()
  6. logger.setLevel(logging.DEBUG)
  7. #出力のフォーマットを定義
  8. formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
  9. #ログファイルを容量でローテーションするハンドラーを定義
  10. rfh = logging.handlers.RotatingFileHandler(
  11.     filename='log.txt',
  12.     maxBytes=20,
  13.     backupCount=3
  14. )
  15. rfh.setLevel(logging.DEBUG)
  16. rfh.setFormatter(formatter)
  17. #rootロガーにハンドラーを登録する
  18. logger.addHandler(rfh)
  19. logger.debug('debugメッセージ')
  20. logger.info('infoメッセージ')
  21. logger.warn('warnメッセージ')
  22. logger.error('errorメッセージ')
  23. logger.critical('criticalメッセージ')




maxBytesは、ログファイルの容量の上限を指定します。
ログファイルがこの容量を超えたら、ファイルのリネームが実行されます。
※切り替わるのを確認するため、少ない容量を指定していますが、実際は十分な容量を
指定する必要があります。

backupCountは、ログファイルの世代数を指定します。
ちなみに、これに0を指定していると世代管理は行われません。

この例だと、まず最初にlog.txtが作成されます。
log.txtの容量が20バイトを超えたらlog.txtをlog.txt.1にリネーム。
新たに、log.txtを作成してログを出力していきます。

ファイルは最大log.txt.3まで作成されます。
log.txt.3がある状態でlog.txtの容量が指定のバイト数を超えた場合、
log.txt.3は削除され、log.txt.2がlog.txt.3にリネームされる。
という動きになります。







TimedRotatingFileHandlerの使用例



指定した時間でログをローテーションするTimedRotatingFileHandlerの例はこんな感じ。


  1. # -*- coding:utf-8 -*-
  2. import logging
  3. import logging.handlers
  4. #rootロガーを取得
  5. logger = logging.getLogger()
  6. logger.setLevel(logging.DEBUG)
  7. #出力のフォーマットを定義
  8. formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
  9. #ログファイルを時刻でローテーションするハンドラーを定義
  10. trh = logging.handlers.TimedRotatingFileHandler(
  11.     filename='log.txt',
  12.     when='S',
  13.     backupCount=3
  14. )
  15. trh.setLevel(logging.DEBUG)
  16. trh.setFormatter(formatter)
  17. #rootロガーにハンドラーを登録する
  18. logger.addHandler(trh)
  19. logger.debug('debugメッセージ')
  20. logger.info('infoメッセージ')
  21. logger.warn('warnメッセージ')
  22. logger.error('errorメッセージ')
  23. logger.critical('criticalメッセージ')




backupCountで世代数を指定するのは、RotatingFileHandlerと同じです。

whenで、ログファイルを切り替えるタイミングを指定します。
指定できるキーワードは以下の通り。

SSeconds
MMinutes
HHours
DDays
midnightroll over at midnight
W{0-6}roll over on a certain day; 0 - Monday


W0で月曜日、W1で火曜日の指定となるようです。
サンプルでは、「S」と秒を指定していますので、スクリプトを何度か実行すると

log.txt.[日付と時刻]

というログファイルが最大3つ作成されると思います。







SocketHandlerの使用例



TCPソケットを通じてログを出力するSocketHandlerの例はこんな感じ。


  1. # -*- coding:utf-8 -*-
  2. import logging
  3. import logging.handlers
  4. #rootロガーを取得
  5. logger = logging.getLogger()
  6. logger.setLevel(logging.DEBUG)
  7. #出力のフォーマットを定義
  8. formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
  9. #ログファイルをTCPソケット経由で出力するハンドラーを定義
  10. sh = logging.handlers.SocketHandler(
  11.     host='localhost',
  12.     port=12345
  13. )
  14. sh.setLevel(logging.DEBUG)
  15. sh.setFormatter(formatter)
  16. #rootロガーにハンドラーを登録する
  17. logger.addHandler(sh)
  18. logger.debug('debugメッセージ')
  19. logger.info('infoメッセージ')
  20. logger.warn('warnメッセージ')
  21. logger.error('errorメッセージ')
  22. logger.critical('criticalメッセージ')





ネットワークにログを送信していますので、当然受け側のプログラムも必要になります。


  1. # -*- coding:utf-8 -*-
  2. import SocketServer
  3. import logging
  4. import cPickle, struct
  5. class SocketLogHandler(SocketServer.StreamRequestHandler):
  6.     def handle(self):
  7.         print("connect from:", self.client_address)
  8.         alldata = ""
  9.         while True:
  10.             chunk = self.request.recv(4)
  11.             if len(chunk) < 4:
  12.                 break
  13.                 
  14.             slen = struct.unpack(">L", chunk)[0]
  15.             chunk = self.request.recv(slen)
  16.             while len(chunk) < slen:
  17.                 chunk = chunk + self.request.recv(slen - len(chunk))
  18.                 
  19.             obj = cPickle.loads(chunk)
  20.             record = logging.makeLogRecord(obj)
  21.             print(unicode(record.getMessage(), 'utf-8'))
  22.             
  23.         
  24.         self.request.close()
  25. if __name__ == '__main__':
  26.     
  27.     server = SocketServer.ThreadingTCPServer(('localhost', 12345), SocketLogHandler)
  28.     print('listening:', server.socket.getsockname())
  29.     server.serve_forever()



SocketHandlerから送信されるデータは、ログ一行分のオブジェクトLogRecordを
pickleでバイナリに変換したものとなっています。

最初の4バイトにデータ長が入っています。
5バイト目から指定されたデータ長までのデータをcPickle.loadsで
戻して値を読み取ります。


受け側のプログラムを起動した状態で、ログ出力のサンプルを実行すると、
こんな感じでログメッセージを受信してくれます。
32_001_20111228220405.png



getMessageでログの内容を出力程度でお茶を濁さず、ログの情報すべてを取得するには、
こんな感じのサーバープログラムになるかと。


  1. # -*- coding:utf-8 -*-
  2. import SocketServer
  3. import logging
  4. import cPickle, struct
  5. class SocketLogHandler(SocketServer.StreamRequestHandler):
  6.     def handle(self):
  7.         print("connect from:", self.client_address)
  8.         alldata = ""
  9.         while True:
  10.             chunk = self.request.recv(4)
  11.             if len(chunk) < 4:
  12.                 break
  13.                 
  14.             slen = struct.unpack(">L", chunk)[0]
  15.             chunk = self.request.recv(slen)
  16.             while len(chunk) < slen:
  17.                 chunk = chunk + self.request.recv(slen - len(chunk))
  18.                 
  19.             obj = cPickle.loads(chunk)
  20.             #ログレコードを復元
  21.             record = logging.makeLogRecord(obj)
  22.             record.msg = unicode(record.msg, 'utf-8')
  23.             
  24.             #サーバー側のloggerに処理を移譲
  25.             logger = logging.getLogger()
  26.             logger.handle(record)
  27.             
  28.         
  29.         self.request.close()
  30.         
  31.         
  32. if __name__ == '__main__':
  33.     
  34.     #rootロガーを取得
  35.     logger = logging.getLogger()
  36.     logger.setLevel(logging.DEBUG)
  37.     #出力のフォーマットを定義
  38.     formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
  39.     #sys.stderrへ出力するハンドラーを定義
  40.     sh = logging.StreamHandler()
  41.     sh.setLevel(logging.DEBUG)
  42.     sh.setFormatter(formatter)
  43.     #rootロガーにハンドラーを登録する
  44.     logger.addHandler(sh)
  45.     
  46.     server = SocketServer.ThreadingTCPServer(('localhost', 12345), SocketLogHandler)
  47.     print('listening:', server.socket.getsockname())
  48.     server.serve_forever()




完全に復元されたログがサーバー側に表示されました。
32_002_20111228220405.png









DatagramHandlerの使用例



UDPソケットを通じてログを出力するDatagramHandlerの例はこんな感じ。


  1. # -*- coding:utf-8 -*-
  2. import logging
  3. import logging.handlers
  4. #rootロガーを取得
  5. logger = logging.getLogger()
  6. logger.setLevel(logging.DEBUG)
  7. #出力のフォーマットを定義
  8. formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
  9. #ログファイルをUDPソケット経由で出力するハンドラーを定義
  10. dh = logging.handlers.DatagramHandler(
  11.     host='localhost',
  12.     port=12345
  13. )
  14. dh.setLevel(logging.DEBUG)
  15. dh.setFormatter(formatter)
  16. #rootロガーにハンドラーを登録する
  17. logger.addHandler(dh)
  18. logger.debug('debugメッセージ')
  19. logger.info('infoメッセージ')
  20. logger.warn('warnメッセージ')
  21. logger.error('errorメッセージ')
  22. logger.critical('criticalメッセージ')




SocketHandlerと同様、通信の受け側が必要です。


  1. # -*- coding:utf-8 -*-
  2. import SocketServer
  3. import logging
  4. import cPickle, struct
  5. class SocketLogHandler(SocketServer.BaseRequestHandler):
  6.     def handle(self):
  7.         print("connect from:", self.client_address)
  8.         data = self.request[0]
  9.         
  10.         chunk = data[:4]
  11.         slen = struct.unpack(">L", chunk)[0]
  12.         chunk = data[4:slen+4]
  13.         
  14.         obj = cPickle.loads(chunk)
  15.         record = logging.makeLogRecord(obj)
  16.         
  17.         print(unicode(record.msg, 'utf-8'))
  18.         
  19.         
  20. if __name__ == '__main__':
  21.     
  22.     server = SocketServer.UDPServer(('', 12345), SocketLogHandler)
  23.     print('listening:', server.socket.getsockname())
  24.     server.serve_forever()




TCPの時は一括でログの内容が送信されてきますが、UDPは一行ごとの通信になります。
32_003.png









HTTPHandlerの使用例



http通信でログを出力するHTTPHandlerの例はこんな感じ。


  1. # -*- coding:utf-8 -*-
  2. import logging
  3. import logging.handlers
  4. #rootロガーを取得
  5. logger = logging.getLogger()
  6. logger.setLevel(logging.DEBUG)
  7. #出力のフォーマットを定義
  8. formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
  9. #ログファイルをHTTP通信で送信するハンドラーを定義
  10. httph = logging.handlers.HTTPHandler(
  11.     host='localhost:8080',
  12.     url='/updatelog',
  13.     method='POST'
  14. )
  15. httph.setLevel(logging.DEBUG)
  16. httph.setFormatter(formatter)
  17. #rootロガーにハンドラーを登録する
  18. logger.addHandler(httph)
  19. logger.debug('debugメッセージ')
  20. logger.info('infoメッセージ')
  21. logger.warn('warnメッセージ')
  22. logger.error('errorメッセージ')
  23. logger.critical('criticalメッセージ')




本来はphpやcgiで記載すると思うのですが、サンプルとして簡単な受け側のプログラムを書いてみます。


  1. # -*- coding:utf-8 -*-
  2. from BaseHTTPServer import BaseHTTPRequestHandler
  3. import cgi
  4. class PostHandler(BaseHTTPRequestHandler):
  5.     
  6.     def do_POST(self):
  7.         # POST されたフォームデータを解析する
  8.         form = cgi.FieldStorage(
  9.             fp=self.rfile,
  10.             headers=self.headers,
  11.             environ={'REQUEST_METHOD':'POST',
  12.                      'CONTENT_TYPE':self.headers['Content-Type'],
  13.                      })
  14.         # レスポンス開始
  15.         self.send_response(200)
  16.         self.end_headers()
  17.         # フォームに POST されたデータを表示する
  18.         for field in form.keys():
  19.             field_item = form[field]
  20.             print('%s=%s' % (field, form[field].value))
  21. if __name__ == '__main__':
  22.     from BaseHTTPServer import HTTPServer
  23.     server = HTTPServer(('localhost', 8080), PostHandler)
  24.     server.serve_forever()




実行してみると、各フィールドにログの内容が入っていることがわかります。
32_004.png




【参考URL】
logging.makeLogRecord
http://omake.accense.com/browser/python/stackless/udp_server.py
http://www.python.jp/doc/release/library/socketserver.html
http://www.doughellmann.com/PyMOTW-ja/BaseHTTPServer/










関連記事

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2011/12/28(水) 22:05:24|
  2. Python
  3. | トラックバック:0
  4. | コメント:2
  5. | 編集
<<Thrift Pythonのコードを生成するときのオプションについて | ホーム | Python pydocの使い方、書き方とhtml出力の方法>>

コメント

わかりやすくまとまっており、大変参考になりました。
  1. 2012/05/06(日) 03:28:06 |
  2. URL |
  3. ニートマン #jAMbzjUE
  4. [ 編集 ]

Re: タイトルなし

お役に立てて幸いです。
今後も、もっとわかりやすい内容になるようがんばります。
  1. 2012/05/06(日) 17:04:13 |
  2. URL |
  3. symfo #-
  4. [ 編集 ]

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://symfoware.blog68.fc2.com/tb.php/885-bcf10fa9
この記事にトラックバックする(FC2ブログユーザー)