重要性 在代码数量少的情况下用 print() 来进行调试输出确实无伤大雅,但是当代码数量暴增,各种依赖嵌套错综复杂的情形下还用 print() 来调试那么纯粹是在折磨自己。这种情形下使用日志来进行记录就显得非常有必要。
基本使用 logging 日志功能输出分为几个等级:
NOTSET(0):不设置,会按照 parent logger 的级别来设置日志
DEBUG(10):细节信息,仅当诊断问题时适用。
INFO(20):确认程序按预期运行。
WARN/WARNING(30):表明有已经或即将发生的意外(例如:磁盘空间不足)。程序仍按预期进行。
ERROR(40):由于严重的问题,程序的某些功能已经不能正常执行
FATAL/CRITICAL(50):严重的错误,表明程序已不能继续执行
默认级别是 WARNING,意味着只会追踪该级别及以上的事件,除非更改日志配置。
一个简单的例子 1 2 3 4 5 6 7 import logginglogging.warning('Watch out!' ) logging.info('I told you so' )
更改日志等级 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import logginglogger = logging.getLogger(__name__) logger.setLevel(level=logging.WARNING) logger.debug('Debugging' ) logger.critical('Critical Something' ) logger.error('Error Occurred' ) logger.warning('Warning exists' ) logger.info('Finished' )
格式化输出信息 1 2 3 4 5 6 7 8 9 10 11 12 logging.basicConfig(level=logging.DEBUG, filename='output.log' , datefmt='%Y/%m/%d %H:%M:%S' , format ='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(module)s - %(message)s' ) logger = logging.getLogger(__name__) logger.info('This is a log info' )
%(levelno)s:打印日志级别的数值。
%(levelname)s:打印日志级别的名称。
%(pathname)s:打印当前执行程序的路径。
%(filename)s:打印当前执行程序名。
%(funcName)s:打印日志的当前函数。
%(lineno)d:打印日志的当前行号。
%(asctime)s:打印日志的时间。
%(thread)d:打印线程 ID。
%(threadName)s:打印线程名称。
%(process)d:打印进程 ID。
%(processName)s:打印线程名称。
%(module)s:打印模块名称。
%(message)s:打印日志信息。
更多参数可以参考官方文档 。
字符串拼接 在日志输出的时候不建议使用字符串的 format() 或 f-string 等形式进行拼接。
1 2 3 4 5 6 logging.debug('Hello {0}, {1}!' .format ('World' , 'Congratulations' )) logging.debug('Hello %s, %s!' , 'World' , 'Congratulations' )
异常处理时,不需要格式化字符串,可以直接打印。因为直接使用字符串格式化的方法将错误输出的话,是不会包含 Traceback 信息的,但如果我们加上 exc_info 参数或者直接使用 exception () 方法打印的话,那就会输出 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 try : result = 5 / 0 except Exception as e: logging.error('Error: %s' , e) logging.error('Error' , exc_info=True ) logging.exception('Error' )
输出到外部文件 1 2 3 4 import logginglogging.basicConfig(filename='example.log' , encoding='utf-8' , level=logging.DEBUG) logging.debug('This message should go to the log file' )
处理器(Handler) Handler 对象负责将适当的日志消息(基于日志消息的严重性)分派给处理器的指定目标。
1 2 3 4 5 6 7 8 9 10 import logginglogger = logging.getLogger(__name__) logger.setLevel(level=logging.INFO) handler = logging.FileHandler('output.log' ) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) handler.setFormatter(formatter) logger.addHandler(handler) logger.info('This is a log info' )
这里没有再使用 basicConfig 全局配置,而是先声明了一个 Logger 对象,然后指定了其对应的 Handler 为 FileHandler 对象,然后 Handler 对象还单独指定了 Formatter 对象单独配置输出格式,最后给 Logger 对象添加对应的 Handler 即可。
更多其他的处理器可以参考官方文档 。
配置共享 logging 模块提供了父子模块共享配置的机制,会根据 Logger 的名称来自动加载父模块的配置,这样可以在不同模块之间共享相同的配置。
1 2 3 4 5 import logginglogger = logging.getLogger('main' )
1 2 3 4 5 import logginglogger = logging.getLogger('main.core' )
使用独立的配置文件 把配置写在单独的文件中是一种更加常见的方式,当运行程序时读取配置文件,这样更加灵活、方便管理。配置文件可以使用 JSON、YAML、conf 等格式。
1 2 3 4 5 6 7 8 9 10 11 12 def setup_logging (default_path='config.yaml' , default_level=logging.INFO ): path = default_path if os.path.exists(path): with open (path, 'r' , encoding='utf-8' ) as f: config = yaml.load(f) logging.config.dictConfig(config) else : logging.basicConfig(level=default_level)
一个比较不错的配置模板:
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 version: 1 disable_existing_loggers: true formatters: standard: format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" error: format: "%(levelname)s <PID %(process)d:%(processName)s> %(name)s.%(funcName)s(): %(message)s" handlers: console: class: logging.StreamHandler level: DEBUG formatter: standard stream: ext://sys.stdout info_file_handler: class: logging.handlers.RotatingFileHandler level: INFO formatter: standard filename: /tmp/info.log maxBytes: 10485760 backupCount: 20 encoding: utf8 error_file_handler: class: logging.handlers.RotatingFileHandler level: ERROR formatter: error filename: /tmp/errors.log maxBytes: 10485760 backupCount: 20 encoding: utf8 debug_file_handler: class: logging.handlers.RotatingFileHandler level: DEBUG formatter: standard filename: /tmp/debug.log maxBytes: 10485760 backupCount: 20 encoding: utf8 critical_file_handler: class: logging.handlers.RotatingFileHandler level: CRITICAL formatter: standard filename: /tmp/critical.log maxBytes: 10485760 backupCount: 20 encoding: utf8 warn_file_handler: class: logging.handlers.RotatingFileHandler level: WARN formatter: standard filename: /tmp/warn.log maxBytes: 10485760 backupCount: 20 encoding: utf8 root: level: NOTSET handlers: [console ] propogate: yes loggers: <module>: level: INFO handlers: [console , info_file_handler , error_file_handler , critical_file_handler , debug_file_handler , warn_file_handler ] propogate: no <module.x>: level: DEBUG handlers: [info_file_handler , error_file_handler , critical_file_handler , debug_file_handler , warn_file_handler ] propogate: yes
参考