日志对于一个应用程序而言,不可获取;特别是在问题定位,链路分析往往是最直观,最有效的富足工具;所以在python中可以怎样便捷的使用日志模块呢
I. logging 模块
我们这里使用logging来记录我们的日志,一般来说,我们常用的日志无非以下几个点
- 日志落盘,存文件,支持定期生成一个备份文件;支持自动删除过期日志
- 异常时,可以输出完整的堆栈,帮助定位出问题的代码行
- 不同的日志level
1. 基本使用姿势
首先我们来看一下日志模块的基本使用姿势,默认的场景下,日志输出在控制台;可以通过指定文件名的方式来讲日志输出到对应的文件中
1 2 3 4 5 6 7 8 9 10 11
| import logging
logging.basicConfig(filename='logger.log', level=logging.INFO)
logging.debug('debug message') logging.info('info message') logging.warning('warn message') logging.error('error message') logging.critical('critical message')
|
因为指定了日志的级别为INFO,所以debug日志将不会显示在日志文件中,具体输出如下
1 2 3 4
| INFO:root:info message WARNING:root:warn message ERROR:root:warn message CRITICAL:root:critical message
|
上面的使用虽说实现了日志记录的功能,但是相对原始;通常我们希望日志的输出包括一些基本信息,如输出日志的时间
这个时候就可以考虑通过format来设置输出日志的格式
格式 |
描述 |
%(levelno)s |
打印日志级别的数值 |
%(levelname)s |
打印日志级别名称 |
%(pathname)s |
打印当前执行程序的路径 |
%(filename)s |
打印当前执行程序名称 |
%(funcName)s |
打印日志的当前函数 |
%(lineno)d |
打印日志的当前行号 |
%(asctime)s |
打印日志的时间 |
%(thread)d |
打印线程id |
%(threadName)s |
打印线程名称 |
%(process)d |
打印进程ID |
%(message)s |
打印日志信息 |
下面我们在日志的输出中,添加上时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import logging
logger = logging.getLogger('test')
fh = logging.FileHandler(filename='logger.log') fh.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger.addHandler(fh)
logger.setLevel(logging.INFO) logger.info('new info message')
|
实际输出结果如
1
| 2019-11-04 18:25:57,519 - INFO - new info message
|
3. Handler
一下几个说明,主要来自博文: Python模块之Logging(四)——常用handlers的使用
a. StreamHandler
流handler——包含在logging模块中的三个handler之一。
能够将日志信息输出到sys.stdout, sys.stderr 或者类文件对象(更确切点,就是能够支持write()和flush()方法的对象)
1
| logging.StreamHandler(stream=None)
|
b. FileHandler
继承自StreamHandler。将日志信息输出到磁盘文件上
1
| logging.FileHandler(filename, mode='a', encoding=None, delay=False)
|
c. NullHandler
什么都不干,相当于吃掉日志
d. WatchedFileHandler
位于logging.handlers模块中。用于监视文件的状态,如果文件被改变了,那么就关闭当前流,重新打开文件,创建一个新的流。由于newsyslog或者logrotate的使用会导致文件改变。这个handler是专门为linux/unix系统设计的,因为在windows系统下,正在被打开的文件是不会被改变的
e. RotatingFileHandler
位于logging.handlers支持循环日志文件。
1
| logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0)
|
参数maxBytes和backupCount允许日志文件在达到maxBytes时rollover.当文件大小达到或者超过maxBytes时,就会新创建一个日志文件。上述的这两个参数任一一个为0时,rollover都不会发生。也就是就文件没有maxBytes限制。backupcount是备份数目,也就是最多能有多少个备份。命名会在日志的base_name
后面加上.0-.n的后缀,如example.log.1,example.log.1,…,example.log.10。当前使用的日志文件为base_name.log。
f. TimedRotatingFileHandler
定时循环日志handler,位于logging.handlers,支持定时生成新日志文件。
1
| logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False)
|
参数when决定了时间间隔的类型,参数interval决定了多少的时间间隔。如when=‘D’,interval=2,就是指两天的时间间隔,backupCount决定了能留几个日志文件。超过数量就会丢弃掉老的日志文件
‘S’ | 秒
‘M’ | 分
‘H’ | 时
‘D’ | 天
‘W0’-‘W6’ | 周一至周日
‘midnight’ | 每天的凌晨
I. 日志模块封装
接下来我们的目标是封装一个简单好用的日志工具类
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
| import logging import os from logging.handlers import TimedRotatingFileHandler
class LoggerWrapper: def __init__(self): self._logger = {}
@staticmethod def _get_path(action, path=""): """ 根据日志名,创建对应的日志路径 :param path: :return: """ if action != 'logs': action = "logs/" + action + "/"
path = action + path if not os.path.exists(path): os.makedirs(path)
return path
def _gen_logger(self, action='logs', log_name='Crawler'): base_logger = logging.getLogger(log_name) base_logger.setLevel(logging.INFO)
log_file = self._get_path(action, log_name) + "/" + log_name + ".log" ch = TimedRotatingFileHandler(log_file, when='D', encoding="utf-8", backupCount=10) ch.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') ch.setFormatter(formatter) base_logger.addHandler(ch) base_logger.propagate = 0 return base_logger
def get_logger(self, name=None): if name is None: key = env_wrapper.get_current_task_name() else: key = name
if key not in self._logger: action, path = key, env_wrapper.get_current_task_name() self._logger[key] = self._gen_logger(action, path)
return self._logger[key]
|
上面是一个简单的按天回滚日志,且只保存最近10天;但是在我们的测试环境下,我们可能希望日志也同时输出一份到控制台
因此我们可以稍微改造一下,拿一个EnvWrapper类,来保存项目根路径,是否输出一份到控制台
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class EnvWrapper: def __init__(self): self._module_path = None self._log_console = False def init_env(xxx): pass
def get_module_path(self): return self._module_path def console_log_enable(self): return self._log_console env_wrapper = EnvWrapper() `
|
然后我们的日志工具类可以改造成下面的case
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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
|
import logging import os from logging.handlers import TimedRotatingFileHandler
from src.env.EnvWrapper import env_wrapper
class LoggerWrapper: def __init__(self): self._logger = {} self._console_init = False
@staticmethod def _get_path(action, path=""): """ 根据日志名,创建对应的日志路径 :param path: :return: """ if action != 'logs': action = "logs/" + action + "/"
path = env_wrapper.get_module_path() + "/" + action + path if not os.path.exists(path): os.makedirs(path)
return path
def _gen_logger(self, action='logs', log_name='Crawler'): base_logger = logging.getLogger(log_name) base_logger.setLevel(logging.INFO)
log_file = self._get_path(action, log_name) + "/" + log_name + ".log" ch = TimedRotatingFileHandler(log_file, when='D', encoding="utf-8") ch.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') ch.setFormatter(formatter) base_logger.addHandler(ch) base_logger.propagate = 0
if env_wrapper.console_log_enable() and not self._console_init: console = logging.StreamHandler() console.setLevel(logging.DEBUG) console.setFormatter(formatter) base_logger.addHandler(console) self._console_init = True
return base_logger
def get_logger(self, name=None): if name is None: key = env_wrapper.get_current_task_name() else: key = name
if key not in self._logger: action, path = key, env_wrapper.get_current_task_name() self._logger[key] = self._gen_logger(action, path)
return self._logger[key]
def error(self, msg, name=None): log = self.get_logger(name) log.error(msg)
def warn(self, msg, name=None): log = self.get_logger(name) log.warning(msg)
def info(self, msg, name=None): log = self.get_logger(name) log.info(msg)
def debug(self, msg, name=None): log = self.get_logger(name) log.debug(msg)
def exception(self, msg, name=None): """ 打印堆栈信息 :param msg: :param name: :return: """ log = self.get_logger(name) log.exception(msg)
SpiderLogger = LoggerWrapper() logger = SpiderLogger.get_logger debug = SpiderLogger.debug info = SpiderLogger.info error = SpiderLogger.error warning = SpiderLogger.warn exception = SpiderLogger.exception
|
III. 其他
一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
2. 声明
尽信书则不如,已上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
3. 扫描关注
一灰灰blog
知识星球