LogLevelをどう設定すべきか
行いたいタスク
そのタスクに最適なツール
通常の操作中に発生したイベント
logging.info()
診断のための詳細な出力
logging.debug()
特定のランタイムイベントに関わる警告(呼び出し側を修正すべき)
warnings.warn()
特定のランタイムイベントに関わる警告
logging.warning()
特定のランタイムイベントに関わるエラー
raise exception
例外の送出をしないエラーの抑制
logging.error(), logging.exception(), logging.ciritical()
getLogger() getLogger()で一度生成されたloggerは同一プロセス内では1つの実体として動作している。loggingクックブック内ではモジュールのグローバル変数としてloggerを定義している。
logging.getLogger(‘someLogger’)の複数回の呼び出しは同じloggerへの参照を返します。これは同じPythonインタプリタプロセス上で動いている限り、一つのモジュールの中からに限らず、モジュールをまたいでも当てはまります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import loggingmodule_logger = logging.getLogger('spam_application.auxiliary' ) class Auxiliary : def __init__ (self ): self.logger = logging.getLogger('spam_application.auxiliary.Auxiliary' ) self.logger.info('creating an instance of Auxiliary' ) def do_something (self ): self.logger.info('doing something' ) a = 1 + 1 self.logger.info('done doing something' ) def some_function (): module_logger.info('received a call to "some_function"' )
disable_existing_loggersでloggerが無効化される
logging.config.dictConfig
で設定を読み込んくとすでに作成済のloggerが無効化されてしまう。無効化させないためにはdisable_existing_loggers
をFalse
に設定する。
disable_existing_loggers.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import loggingimport logging.configlogger = logging.getLogger(__name__) logging.config.dictConfig({ 'version' : 1 , 'disable_existing_loggers' : False , 'formatters' : { 'standard' : { 'format' : '%(asctime)s [%(levelname)s] %(name)s: %(message)s' }, }, 'handlers' : { 'default' : { 'level' :'INFO' , 'class' :'logging.StreamHandler' , 'formatter' :'standard' }, }, 'loggers' : { '' : { 'handlers' : ['default' ], 'level' : 'INFO' , 'propagate' : True } } }) logger.debug('Debug log message' ) logger.info('Info log message' ) logger.warning('Warning log message' ) logger.error('Error log message' )
disable_existing_loggersがTrueの場合 ログが出力されない。
1 $python disable_existing_loggers.py
disable_existing_loggersがFalseの場合 期待通りのログが出力される。
1 2 3 4 $python disable_existing_loggers.py2020-04-22 13:54:22,207 [INFO] __main__: Info log message 2020-04-22 13:54:22,209 [WARNING] __main__: Warning log message 2020-04-22 13:54:22,209 [ERROR] __main__: Error log message
loggerとdisable_existing_loggersの挙動をみる logging_treeモジュール loggerをツリー構造で見やすく表示する、logging_tree
を使う。pip install logging_tree
でインストールしておく。
my_module.py テスト用のClassとFunctionでいくつかのLogLevelのメッセージを表示するモジュール。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import loggingdef foo (): logger = logging.getLogger(__name__) logger.debug('Debug log message/function' ) logger.info('Info log message/function' ) logger.warning('Warning log message/function' ) logger.error('Error log message/function' ) class Bar (object ): def __init__ (self, logger=None ): self.logger = logger or logging.getLogger(__name__) def bar (self ): self.logger.debug('Debug log message/method' ) self.logger.info('Info log message/method' ) self.logger.warning('Warning log message/method' ) self.logger.error('Error log message/method' )
main.py テスト用のモジュールでログ出力とloggerのツリー構造を確認する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import loggingimport logging.configimport logging_treeimport my_moduleprint ("--------------------------------------------------------" )print ("Step1: 標準のroot logger" )logging_tree.printout() print ("--------------------------------------------------------" )print ("--------------------------------------------------------" )print ("Step2: なにも設定せずgetLogger()を呼ぶ" )my_module.foo() bar = my_module.Bar() bar.bar() print ("" )print ("getLogger()によってmy_moduleというloggerが生成されている" )logging_tree.printout() print ("--------------------------------------------------------" )print ("--------------------------------------------------------" )print ("Step3: logging.ini / disable_existing_loggers=True" )logging.config.fileConfig('logging.ini' , disable_existing_loggers=True ) my_module.foo() bar = my_module.Bar() bar.bar() print ("" )print ("すでにStep1でmy_module loggerは作成済だったので、無効化されている" )logging_tree.printout() print ("--------------------------------------------------------" )print ("--------------------------------------------------------" )print ("Step4: logging.ini / disable_existing_loggers=False" )logging.config.fileConfig('logging.ini' , disable_existing_loggers=False ) my_module.foo() bar = my_module.Bar() bar.bar() print ("" )print ("disable_existing_loggers=Falseで有効化されている" )logging_tree.printout() print ("--------------------------------------------------------" )
logging.ini 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [loggers] keys =root[handlers] keys =consoleHandler[formatters] keys =simpleFormatter[logger_root] level =DEBUGhandlers =consoleHandler[handler_consoleHandler] class =StreamHandlerlevel =DEBUGformatter =simpleFormatterargs =(sys.stdout,)[formatter_simpleFormatter] format =%(asctime)s - %(name)s - %(levelname)s - %(message)sdatefmt=
main.py実行結果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 $python main.py-------------------------------------------------------- Step1: 標準のroot logger <--"" Level WARNING -------------------------------------------------------- -------------------------------------------------------- Step2: なにも設定せずgetLogger()を呼ぶ Warning log message/function Error log message/function Warning log message/method Error log message/method getLogger()によってmy_moduleというloggerが生成されている <--"" Level WARNING | o<--"my_module" Level NOTSET so inherits level WARNING -------------------------------------------------------- -------------------------------------------------------- Step3: logging.ini / disable_existing_loggers=True すでにStep1でmy_module loggerは作成済だったので、無効化されている <--"" Level DEBUG Handler Stream <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8' > Level DEBUG Formatter fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s' datefmt='' | o<--"my_module" Level NOTSET so inherits level DEBUG Disabled -------------------------------------------------------- -------------------------------------------------------- Step4: logging.ini / disable_existing_loggers=False 2020-04-04 22:03:42,281 - my_module - DEBUG - Debug log message/function 2020-04-04 22:03:42,281 - my_module - INFO - Info log message/function 2020-04-04 22:03:42,281 - my_module - WARNING - Warning log message/function 2020-04-04 22:03:42,281 - my_module - ERROR - Error log message/function 2020-04-04 22:03:42,281 - my_module - DEBUG - Debug log message/method 2020-04-04 22:03:42,281 - my_module - INFO - Info log message/method 2020-04-04 22:03:42,281 - my_module - WARNING - Warning log message/method 2020-04-04 22:03:42,281 - my_module - ERROR - Error log message/method disable_existing_loggers=Falseで有効化されている <--"" Level DEBUG Handler Stream <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8' > Level DEBUG Formatter fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s' datefmt='' | o<--"my_module" Level NOTSET so inherits level DEBUG --------------------------------------------------------
Pythonの例外処理とトレースバック Pythonの例外処理をCatchして、ログ出力する。StuckTraceは改行を含む複数行の内容になるので、JSON形式でのログ出力を行う。JSON形式でログ出力するために、jsonlogger
を使うのでpip install python-json-logger
でインストールする。
複数行の例外は読みやすいですが、外部のログサービスを使用してログを集計している場合は、ログをJSONに変換して、複数行のログが正しく解析されるようにする必要があります。
lowermodule.py
例外をキャッチしてスタックトレースをログに記録する
スタックトレースは複数行になるので、JSON形式で扱いやすく出力する
JSONにはカスタム項目(extra=)を追記することも可能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import logging.configimport tracebackimport timefrom pythonjsonlogger import jsonloggerimport logging_treedef word_count (myfile ): logger = logging.getLogger('lowermodule' ) logging.config.fileConfig('logging.json.ini' , disable_existing_loggers=False ) print (logger.name) logging_tree.printout() try : starttime = time.time() with open (myfile, 'r' ) as f: file_data = f.read() words = file_data.split(" " ) final_word_count = len (words) endtime = time.time() duration = endtime - starttime logger.info("this file has %d words" , final_word_count, extra={"run_duration" :duration}) return final_word_count except OSError as e: logger.error(e, exc_info=True ) except : logger.error("uncaught exception: %s" , traceback.format_exc()) return False if __name__ == '__main__' : word_count('myfile.txt' )
logging.json.ini __name__
がlowermodule
の場合にconsoleとjsonに出力する。consoleはroot loggerの定義によって、jsonはlowermodule loggerの定義によって出力される。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 [loggers] keys =root,lowermodule[handlers] keys =consoleHandler,fileHandler[formatters] keys =simpleFormatter,json[logger_root] level =DEBUGhandlers =consoleHandler[logger_lowermodule] level =DEBUGhandlers =fileHandlerqualname =lowermodule[handler_consoleHandler] class =StreamHandlerlevel =DEBUGformatter =simpleFormatterargs =(sys.stdout,)[handler_fileHandler] class =FileHandlerlevel =DEBUGformatter =jsonargs =("myapp.log" ,)[formatter_json] class =pythonjsonlogger.jsonlogger.JsonFormatterformat =%(asctime)s %(name)s %(levelname)s %(message)s[formatter_simpleFormatter] format =%(asctime)s %(name)s - %(levelname)s:%(message)s
lowermodule.py実行結果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $python lowermodule.pylowermodule <--"" Level DEBUG Handler Stream <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8' > Level DEBUG Formatter fmt='%(asctime)s %(name)s - %(levelname)s:%(message)s' datefmt=None | o<--"lowermodule" Level DEBUG Handler File '/work/logging_traceback/myapp.log' Level DEBUG Formatter <pythonjsonlogger.jsonlogger.JsonFormatter object at 0x7f41b0c0e040> 2020-04-05 01:12:37,312 lowermodule - ERROR:[Errno 2] No such file or directory: 'myfile.txt' Traceback (most recent call last): File "lowermodule.py" , line 16, in word_count with open(myfile, 'r' ) as f: FileNotFoundError: [Errno 2] No such file or directory: 'myfile.txt' $cat myapp.log{"asctime" : "2020-04-05 01:12:37,312" , "name" : "lowermodule" , "levelname" : "ERROR" , "message" : "[Errno 2] No such file or directory: 'myfile.txt'" , "exc_info" : "Traceback (most recent call last):\n File \"lowermodule.py\", line 16, in word_count\n with open(myfile, 'r') as f:\nFileNotFoundError: [Errno 2] No such file or directory: 'myfile.txt'" }