Python logger模块
1 logging 模块简介
logging 模块是 Python 内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等;相比 print,具备如下优点:
- 可以通过设置不同的日志等级,在 release 版本中只输出重要信息,而不必显示大量的调试信息;
- print 将所有信息都输出到标准输出中,严重影响开发者从标准输出中查看其它数据;logging 则可以由开发者决定将信息输出到什么地方,以及怎么输出;
2 logging 模块使用
2.1 基本使用
配置 logging 基本的设置,然后在控制台输出日志,
import logging logging.basicConfig(level = logging.INFO, format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) logger.info( "Start print log" ) logger.debug( "Do something" ) logger.warning( "Something maybe fail." ) logger.info( "Finish" ) |
运行时,控制台输出,
1 2016-10-09 19:11:19,434 - __main__ - INFO - Start print log 2 2016-10-09 19:11:19,434 - __main__ - WARNING - Something maybe fail. 3 2016-10-09 19:11:19,434 - __main__ - INFO - Finish
logging 中可以选择很多消息级别,如:DEBUG,INFO,WARNING,ERROR,CRITICAL,通过赋予 logger 或者 handler 不同的级别,开发者就可以只输出错误信息到特定的记录文件,或者在调试时只记录调试信息。
将 logger 的级别改为 DEBUG,再观察一下输出结果
1 | logging.basicConfig(level = logging.DEBUG, format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) |
从输出结果可以看到,输出了 debug 的日志记录
1 2 3 4 | 2016 - 10 - 09 19 : 12 : 08 , 289 - __main__ - INFO - Start print log 2016 - 10 - 09 19 : 12 : 08 , 289 - __main__ - DEBUG - Do something 2016 - 10 - 09 19 : 12 : 08 , 289 - __main__ - WARNING - Something maybe fail. 2016 - 10 - 09 19 : 12 : 08 , 289 - __main__ - INFO - Finish |
1 | logging.basicConfig函数各参数: |
1 2 3 4 5 6 7 8 9 10 11 | filename:指定日志文件名; filemode:和 file 函数意义相同,指定日志文件的打开模式, 'w' 或者 'a' ; format :指定输出的格式和内容, format 可以输出很多有用的信息, datefmt:指定时间格式,同time.strftime(); level:设置日志级别,默认为logging.WARNNING; stream:指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略; |
属性名称
|
格式
|
说明
|
name
|
%(name)s
|
日志的名称
|
asctime
|
%(asctime)s
|
可读时间,默认格式‘2003-07-08 16:49:45,896’,逗号之后是毫秒 |
filename
|
%(filename)s
|
文件名,pathname 的一部分 |
pathname
|
%(pathname)s
|
文件的全路径名称
|
funcName
|
%(funcName)s
|
调用日志多对应的方法名
|
levelname
|
%(levelname)s
|
日志的等级
|
levelno
|
%(levelno)s
|
数字化的日志等级
|
lineno
|
%(lineno)d
|
被记录日志在源码中的行数
|
module
|
%(module)s
|
模块名 |
msecs | %(msecs)d | 时间中的毫秒部分 |
process
|
%(process)d
|
进程的 ID
|
processName
|
%(processName)s
|
进程的名称
|
thread
|
%(thread)d
|
线程的 ID
|
threadName
|
%(threadName)s
|
线程的名称
|
relativeCreated
|
%(relativeCreated)d
|
日志被创建的相对时间,以毫秒为单位
|
2.2 将日志写入到文件
2.2.1 将日志写入到文件
设置 logging,创建一个 FileHandler,并对输出消息的格式进行设置,将其添加到 logger,然后将日志写入到指定的文件中,
1 2 3 4 5 6 7 8 9 10 11 12 13 | import logging logger = logging.getLogger(__name__) logger.setLevel(level = logging.INFO) handler = logging.FileHandler( "log.txt" ) handler.setLevel(logging.INFO) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) handler.setFormatter(formatter) logger.addHandler(handler) logger.info( "Start print log" ) logger.debug( "Do something" ) logger.warning( "Something maybe fail." ) logger.info( "Finish" ) |
log.txt 中日志数据为:
2017-07-25 15:02:09,905 - __main__ - INFO - Start print log
2017-07-25 15:02:09,905 - __main__ - WARNING - Something maybe fail.
2017-07-25 15:02:09,905 - __main__ - INFO - Finish
2.2.2 将日志同时输出到屏幕和日志文件
logger 中添加 StreamHandler,可以将日志输出到屏幕上,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import logging logger = logging.getLogger(__name__) logger.setLevel(level = logging.INFO) handler = logging.FileHandler( "log.txt" ) handler.setLevel(logging.INFO) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) handler.setFormatter(formatter) console = logging.StreamHandler() console.setLevel(logging.INFO) logger.addHandler(handler) logger.addHandler(console) logger.info( "Start print log" ) logger.debug( "Do something" ) logger.warning( "Something maybe fail." ) logger.info( "Finish" ) |
可以在 log.txt 文件和控制台中看到
2017-07-25 15:03:05,075 - __main__ - INFO - Start print log
2017-07-25 15:03:05,075 - __main__ - WARNING - Something maybe fail.
2017-07-25 15:03:05,075 - __main__ - INFO - Finish
可以发现,logging 有一个日志处理的主对象,其他处理方式都是通过 addHandler 添加进去,logging 中包含的 handler 主要有如下几种,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | handler名称:位置;作用 StreamHandler:logging.StreamHandler;日志输出到流,可以是sys.stderr,sys.stdout或者文件 FileHandler:logging.FileHandler;日志输出到文件 BaseRotatingHandler:logging.handlers.BaseRotatingHandler;基本的日志回滚方式 RotatingHandler:logging.handlers.RotatingHandler;日志回滚方式,支持日志文件最大数量和日志文件回滚 TimeRotatingHandler:logging.handlers.TimeRotatingHandler;日志回滚方式,在一定时间区域内回滚日志文件 SocketHandler:logging.handlers.SocketHandler;远程输出日志到TCP / IP sockets DatagramHandler:logging.handlers.DatagramHandler;远程输出日志到UDP sockets SMTPHandler:logging.handlers.SMTPHandler;远程输出日志到邮件地址 SysLogHandler:logging.handlers.SysLogHandler;日志输出到syslog NTEventLogHandler:logging.handlers.NTEventLogHandler;远程输出日志到Windows NT / 2000 / XP的事件日志 MemoryHandler:logging.handlers.MemoryHandler;日志输出到内存中的指定 buffer HTTPHandler:logging.handlers.HTTPHandler;通过 "GET" 或者 "POST" 远程输出到HTTP服务器 |
2.2.3 日志回滚
使用 RotatingFileHandler,可以实现日志回滚,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import logging from logging.handlers import RotatingFileHandler logger = logging.getLogger(__name__) logger.setLevel(level = logging.INFO) #定义一个RotatingFileHandler,最多备份3个日志文件,每个日志文件最大1K rHandler = RotatingFileHandler( "log.txt" ,maxBytes = 1 * 1024 ,backupCount = 3 ) rHandler.setLevel(logging.INFO) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) rHandler.setFormatter(formatter) console = logging.StreamHandler() console.setLevel(logging.INFO) console.setFormatter(formatter) logger.addHandler(rHandler) logger.addHandler(console) logger.info( "Start print log" ) logger.debug( "Do something" ) logger.warning( "Something maybe fail." ) logger.info( "Finish" ) |
可以在工程目录中看到,备份的日志文件,
.3 设置消息的等级
可以设置不同的日志等级,用于控制日志的输出
1 2 3 4 5 6 7 8 | 日志等级:使用范围 FATAL:致命错误 CRITICAL:特别糟糕的事情,如内存耗尽、磁盘空间为空,一般很少使用 ERROR:发生错误时,如IO操作失败或者连接问题 WARNING:发生很重要的事件,但是并不是错误时,如用户登录密码错误 INFO:处理请求或者状态变化等日常事务 DEBUG:调试过程中使用DEBUG等级,如算法中每个循环的中间状态 |
setLevel(lvl) 定义处理 log 的最低等级,内建的级别为:DEBUG,INFO,WARNING,ERROR,CRITICAL;下图是级别对应数值
2.4 捕获 traceback
Python 中的 traceback 模块被用于跟踪异常返回信息,可以在 logging 中记录下 traceback
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 | import logging logger = logging.getLogger(__name__) logger.setLevel(level = logging.INFO) handler = logging.FileHandler( "log.txt" ) handler.setLevel(logging.INFO) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) handler.setFormatter(formatter) console = logging.StreamHandler() console.setLevel(logging.INFO) logger.addHandler(handler) logger.addHandler(console) logger.info( "Start print log" ) logger.debug( "Do something" ) logger.warning( "Something maybe fail." ) try : open ( "sklearn.txt" , "rb" ) except (SystemExit,KeyboardInterrupt): raise except Exception: logger.error( "Faild to open sklearn.txt from logger.error" ,exc_info = True ) logger.info( "Finish" ) |
控制台和日志文件 log.txt 中输出
1 2017-07-25 15:04:24,045 - __main__ - INFO - Start print log 2 2017-07-25 15:04:24,045 - __main__ - WARNING - Something maybe fail. 3 2017-07-25 15:04:24,046 - __main__ - ERROR - Faild to open sklearn.txt from logger.error 4 Traceback (most recent call last): 5 File "E:\PYTHON\Eclipse\eclipse\Doc\14day5\Logger 模块 \Logging.py", line 71, in <module> 6 open("sklearn.txt","rb") 7 IOError: [Errno 2] No such file or directory: 'sklearn.txt' 8 2017-07-25 15:04:24,049 - __main__ - INFO - Finish
也可以使用 logger.exception(msg,_args),它等价于 logger.error(msg,exc_info = True,_args),
1 2 3 4 | 将 logger.error( "Faild to open sklearn.txt from logger.error" ,exc_info = True ) 替换为, logger.exception( "Failed to open sklearn.txt from logger.exception" ) |
2.5 多模块使用 logging
主模块 mainModule.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 | import logging import subModule logger = logging.getLogger( "mainModule" ) logger.setLevel(level = logging.INFO) handler = logging.FileHandler( "log.txt" ) handler.setLevel(logging.INFO) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) handler.setFormatter(formatter) console = logging.StreamHandler() console.setLevel(logging.INFO) console.setFormatter(formatter) logger.addHandler(handler) logger.addHandler(console) logger.info( "creating an instance of subModule.subModuleClass" ) a = subModule.SubModuleClass() logger.info( "calling subModule.subModuleClass.doSomething" ) a.doSomething() logger.info( "done with subModule.subModuleClass.doSomething" ) logger.info( "calling subModule.some_function" ) subModule.som_function() logger.info( "done with subModule.some_function" ) |
子模块 subModule.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import logging module_logger = logging.getLogger( "mainModule.sub" ) class SubModuleClass( object ): def __init__( self ): self .logger = logging.getLogger( "mainModule.sub.module" ) self .logger.info( "creating an instance in SubModuleClass" ) def doSomething( self ): self .logger.info( "do something in SubModule" ) a = [] a.append( 1 ) self .logger.debug( "list a = " + str (a)) self .logger.info( "finish something in SubModuleClass" ) def som_function(): module_logger.info( "call function some_function" ) |
执行之后,在控制和日志文件 log.txt 中输出
1 2017-07-25 15:05:07,427 - mainModule - INFO - creating an instance of subModule.subModuleClass 2 2017-07-25 15:05:07,427 - mainModule.sub.module - INFO - creating an instance in SubModuleClass 3 2017-07-25 15:05:07,427 - mainModule - INFO - calling subModule.subModuleClass.doSomething 4 2017-07-25 15:05:07,427 - mainModule.sub.module - INFO - do something in SubModule 5 2017-07-25 15:05:07,427 - mainModule.sub.module - INFO - finish something in SubModuleClass 6 2017-07-25 15:05:07,427 - mainModule - INFO - done with subModule.subModuleClass.doSomething 7 2017-07-25 15:05:07,427 - mainModule - INFO - calling subModule.some_function 8 2017-07-25 15:05:07,427 - mainModule.sub - INFO - call function some_function 9 2017-07-25 15:05:07,428 - mainModule - INFO - done with subModule.some_function
说明:
首先在主模块定义了 logger'mainModule',并对它进行了配置,就可以在解释器进程里面的其他地方通过 getLogger('mainModule') 得到的对象都是一样的,不需要重新配置,可以直接使用。定义的该 logger 的子 logger,都可以共享父 logger 的定义和配置,所谓的父子 logger 是通过命名来识别,任意以 'mainModule' 开头的 logger 都是它的子 logger,例如 'mainModule.sub'。
实际开发一个 application,首先可以通过 logging 配置文件编写好这个 application 所对应的配置,可以生成一个根 logger,如 'PythonAPP',然后在主函数中通过 fileConfig 加载 logging 配置,接着在 application 的其他地方、不同的模块中,可以使用根 logger 的子 logger,如 'PythonAPP.Core','PythonAPP.Web' 来进行 log,而不需要反复的定义和配置各个模块的 logger。
3 通过 JSON 或者 YAML 文件配置 logging 模块
尽管可以在 Python 代码中配置 logging,但是这样并不够灵活,最好的方法是使用一个配置文件来配置。在 Python 2.7 及以后的版本中,可以从字典中加载 logging 配置,也就意味着可以通过 JSON 或者 YAML 文件加载日志的配置。
3.1 通过 JSON 文件配置
JSON 配置文件
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 | { "version" : 1 , "disable_existing_loggers" :false, "formatters" :{ "simple" :{ "format" : "%(asctime)s - %(name)s - %(levelname)s - %(message)s" } }, "handlers" :{ "console" :{ "class" : "logging.StreamHandler" , "level" : "DEBUG" , "formatter" : "simple" , "stream" : "ext://sys.stdout" }, "info_file_handler" :{ "class" : "logging.handlers.RotatingFileHandler" , "level" : "INFO" , "formatter" : "simple" , "filename" : "info.log" , "maxBytes" : "10485760" , "backupCount" : 20 , "encoding" : "utf8" }, "error_file_handler" :{ "class" : "logging.handlers.RotatingFileHandler" , "level" : "ERROR" , "formatter" : "simple" , "filename" : "errors.log" , "maxBytes" : 10485760 , "backupCount" : 20 , "encoding" : "utf8" } }, "loggers" :{ "my_module" :{ "level" : "ERROR" , "handlers" :[ "info_file_handler" ], "propagate" : "no" } }, "root" :{ "level" : "INFO" , "handlers" :[ "console" , "info_file_handler" , "error_file_handler" ] } } |
通过 JSON 加载配置文件,然后通过 logging.dictConfig 配置 logging,
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 | import json import logging.config import os def setup_logging(default_path = "logging.json" ,default_level = logging.INFO,env_key = "LOG_CFG" ): path = default_path value = os.getenv(env_key, None ) if value: path = value if os.path.exists(path): with open (path, "r" ) as f: config = json.load(f) logging.config.dictConfig(config) else : logging.basicConfig(level = default_level) def func(): logging.info( "start func" ) logging.info( "exec func" ) logging.info( "end func" ) if __name__ = = "__main__" : setup_logging(default_path = "logging.json" ) func() |
3.2 通过 YAML 文件配置
通过 YAML 文件进行配置,比 JSON 看起来更加简介明了,
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 | version: 1 disable_existing_loggers: False formatters: simple: format : "%(asctime)s - %(name)s - %(levelname)s - %(message)s" handlers: console: class : logging.StreamHandler level: DEBUG formatter: simple stream: ext: / / sys.stdout info_file_handler: class : logging.handlers.RotatingFileHandler level: INFO formatter: simple filename: info.log maxBytes: 10485760 backupCount: 20 encoding: utf8 error_file_handler: class : logging.handlers.RotatingFileHandler level: ERROR formatter: simple filename: errors.log maxBytes: 10485760 backupCount: 20 encoding: utf8 loggers: my_module: level: ERROR handlers: [info_file_handler] propagate: no root: level: INFO handlers: [console,info_file_handler,error_file_handler] |
通过 YAML 加载配置文件,然后通过 logging.dictConfig 配置 logging
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 | import yaml import logging.config import os def setup_logging(default_path = "logging.yaml" ,default_level = logging.INFO,env_key = "LOG_CFG" ): path = default_path value = os.getenv(env_key, None ) if value: path = value if os.path.exists(path): with open (path, "r" ) as f: config = yaml.load(f) logging.config.dictConfig(config) else : logging.basicConfig(level = default_level) def func(): logging.info( "start func" ) logging.info( "exec func" ) logging.info( "end func" ) if __name__ = = "__main__" : setup_logging(default_path = "logging.yaml" ) func() |
4 Reference
http://wjdadi-gmail-com.iteye.com/blog/1984354
关于 logging 的一些琐事
python logging 重复写日志问题