1.配置共享
如果每个文件都配置logging,那就太繁琐了,logging提供了父子模块共享配置的机制,
会根据Logger的名称来自动加载父模块的配置.首先定义一个 main.py 文件:
import logging import core logger = logging.getLogger('main') logger.setLevel(level=logging.DEBUG) # Handler handler = logging.FileHandler('result.log') handler.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) logger.info('Main Info') logger.debug('Main Debug') logger.error('Main Error') core.run()
定义了Logger的名称为 main,接下来我们定义core.py
import logging logger = logging.getLogger('main.core') def run(): logger.info('Core Info') logger.debug('Core Debug') logger.error('Core Error')
运行之后会生成一个 result.log 文件,内容如下:
2018-06-03 16:55:56,259 - main - INFO - Main Info 2018-06-03 16:55:56,259 - main - ERROR - Main Error 2018-06-03 16:55:56,259 - main.core - INFO - Core Info 2018-06-03 16:55:56,259 - main.core - ERROR - Core Error
2.文件配置
在开发过程中,将配置在代码里面写死并不是一个好的习惯,
更好的做法是将配置写在配置文件里面,我们可以将配置写入到配置文件,
然后运行时读取配置文件里面的配置,这样是更方便管理和维护的.
定义一个 yaml 配置文件:
version: 1 formatters: brief: format: "%(asctime)s - %(message)s" simple: format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" handlers: console: class : logging.StreamHandler formatter: brief level : INFO stream : ext://sys.stdout file: class : logging.FileHandler formatter: simple level: DEBUG filename: debug.log error: class: logging.handlers.RotatingFileHandler level: ERROR formatter: simple filename: error.log maxBytes: 10485760 backupCount: 20 encoding: utf8 loggers: main.core: level: DEBUG handlers: [console, file, error] root: level: DEBUG handlers: [console]
定义了 formatters、handlers、loggers、root等模块,
实际上对应的就是各个Formatter、Handler、Logger的配置.
3.定义一个主入口文件--main.py:
import logging
import core
import yaml
import logging.config
import os
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)
def log():
logging.debug('Start')
logging.info('Exec')
logging.info('Finished')
if __name__ == '__main__':
yaml_path = 'config.yaml'
setup_logging(yaml_path)
log()
core.run()
文件core.py内容不变,
观察配置文件,主入口文件main.py实际上对应的是root一项配置,
它指定了handlers是console,即只输出到控制台;另外在loggers一项配置里面,
我们定义了main.core模块,handlers是console、file、error三项,
即输出到控制台、输出到普通文件和回滚文件.
4.日志记录使用常见误区
使用字符串的 format() 来构造一个字符串,但这其实并不是一个好的方法
# bad
logging.debug('Hello {0}, {1}!'.format('World', 'Congratulations'))
# good
logging.debug('Hello %s, %s!', 'World', 'Congratulations')
在进行异常处理的时候,通常我们会直接将异常进行字符串格式化,
但其实可以直接指定一个参数将 traceback 打印出来,示例如下:
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
try:
result = 5 / 0
except Exception as e:
# bad
logging.error('Error: %s', e)
# good
logging.error('Error', exc_info=True)
# good
logging.exception('Error')
如果我们直接使用字符串格式化的方法将错误输出的话,是不会包含Traceback信息的,
如果我们加上 exc_info参数或者直接使用exception()方法打印的话,
那就会输出Traceback信息了.