#logger文档地址:https://docs.python.org/zh-cn/3/library/logging.html
1 .什么是日志
日志是对软件执行时所发生事件的一种追踪方式。软件开发人员对他们的代码添加日志调用,借此来指示某事件的发生。一个事件通过一些包含变量数据的描述信息来描述
(比如:每个事件发生时的数据都是不同的)。开发者还会区分事件的重要性,重要性也被称为等级或严重性。
2.什么时候使用日志
日志(logging)模块提供了一系列的函数(debug、info、warning、critical)来适应不同的应用场景。想要决定何时使用日志,请看下表,其中显示了对于每个通用任务集合来说最好的工具
|
|||||||||||||
下表展示了 |
等级 | 数值 | 描述 |
---|---|---|
CRITICAL | 50 | 严重的错误,表明程序已经不能继续执行 |
FATAL(不推荐) | 50 | FATAL是CRITICAL的别名。 |
ERROR | 40 | 由于严重的问题,程序的某些功能已经不能正常执行 |
WARNING | 30 | 表明已经或即将发生的意外(例如磁盘空间不足)。但程序仍按照预期运行 |
WARN(不推荐) | 30 | WARN是WANING的简写形式。 |
INFO | 20 | 确认程序按预期运行 |
DEBUG | 10 | 细节信息,在我们调试程序是使用 |
NOTSET | 0 | 不设置 |
默认的级别是WARNING
,意味着只会追踪该级别及以上的事件,除非更改日志配置。
3.basicConfig
3.1 一个简单的示例,将结果输出到控制台:
import logging logging.warning('info level') # WARNING:root:info levelb
3.2 将结果输出到文件:
import logging logging.basicConfig(filename='test.log', level=20) logging.log(10, '级别为10的一条日志') logging.log(20, '级别为20的一条日志')
上例中,通过basicConfig
方法将日志输出到test.log
文件,并且设置只有级别大于等于20的才会写入到该日志文件。也就是说,上例中,第一条级别为10的日志将不会写入到文件。并且,需要注意的是,如果你查看日志文件,如果出现乱码的话,请检查编码方式。
# 设置日志编码格式 file_handler = logging.FileHandler(file_name, encoding='utf-8') logging.basicConfig(level=logging.INFO, handlers={file_handler})
上例中,在basicConfig
方法中,级别20也可以这样指定:
logging.basicConfig(filename='test.log', level=logging.INFO) logging.debug('debug level: 10') logging.info('info level: 20') logging.warning('warning level: 30') logging.error('error level: 40') logging.critical('critical level: 50')
上例中只有级别大于等于20的将会被写入文件。logging.INFO
其实代表的就是20,这是Python在源码中帮我们指定了:
CRITICAL = 50 FATAL = CRITICAL ERROR = 40 WARNING = 30 WARN = WARNING INFO = 20 DEBUG = 10 NOTSET = 0
接下来,我们来看看basicConfig
方法都可以指定哪些参数:
- filename,即日志输出的文件名,如果指定了这个信息之后,实际上会启用``FileHandler
,而不再是
StreamHandler`,这样日志信息便会输出到文件中了。 - filemode,日志文件写入方式,可以是
w
和a
,默认的是a
模式。 - format,指定日志信息的输出格式,详细参考,这里列出常用的参数:
- %(levelno)s:打印日志级别的数值。
- %(levelname)s:打印日志级别的名称。
- %(pathname)s:打印当前执行程序的路径,其实就是sys.argv[0]。
- %(filename)s:打印当前执行程序名。
- %(funcName)s:打印日志的当前函数。
- %(lineno)d:打印日志的当前行号。
- %(asctime)s:打印日志的时间。
- %(thread)d:打印线程ID。
- %(threadName)s:打印线程名称。
- %(process)d:打印进程ID。
- %(processName)s:打印线程名称。
- %(module)s:打印模块名称。
- %(message)s:打印日志信息。
- datefmt,指定时间的输出格式。
- style,如果format指定,该参数可以指定格式化时的占位符。例如
'{'
或'$'
用于printf风格,str.format()
或string.Template
分别。默认为'%'
。3.2版本新增参数。 - level,指定日志输出的类别,程序会输出大于等于此级别的信息。
- stream,在没有指定``filename
的时候会默认使用
StreamHandler,这时
stream`可以指定初始化的文件流。 - handlers:可以指定日志处理时所使用的 Handlers,必须是可迭代的。
来个示例:
import logging logging.basicConfig( filename='test.log', filemode='w', level=logging.DEBUG, datefmt='%Y/%m/%d %H:%M:%S', format='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(module)s - %(message)s' ) logging.debug('debug level: 10') logging.info('info level: 20') logging.warning('warning level: 30') logging.error('error level: 40') logging.critical('critical level: 50')
输出结果如下:
2019/05/30 14:38:09 - root - DEBUG - 36 - 日志模块 - debug level: 10
2019/05/30 14:38:09 - root - INFO - 37 - 日志模块 - info level: 20
2019/05/30 14:38:09 - root - WARNING - 38 - 日志模块 - warning level: 30
2019/05/30 14:38:09 - root - ERROR - 39 - 日志模块 - error level: 40
2019/05/30 14:38:09 - root - CRITICAL - 40 - 日志模块 - critical level: 50
需要注意的是,logging.basicConfig
只生效一次,比如:
import logging logging.basicConfig( filename='test1.log', filemode='w', level=logging.DEBUG ) # 无效 logging.basicConfig( filename='test2.log', filemode='a', level=logging.INFO ) logging.debug('debug level: 10') logging.info('info level: 20') logging.warning('warning level: 30') logging.error('error level: 40') logging.critical('critical level: 50')
正如上例所示,我们配置了两次basicConfig
。但如果运行你会发现,只有第一个配置生效了,第二个配置不会生效。原因是当在第一次配置的时候,logging
在内部就会进行配置,第二次再次配置的
时候,logging
就会认为我已经配置好了,不需要再次配置了。
4 . handler
接下来,我们来看handler
的用法:
import logging # 日志输出到哪? file_handler = logging.FileHandler(filename='test.log', mode='w', encoding='utf-8') # 以什么格式写 fmt = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(module)s - %(message)s') # 为日志文件指定写入格式 file_handler.setFormatter(fmt=fmt) # 谁来写?日志的级别是什么? logger = logging.Logger(name='logger', level=logging.INFO) # name:日志对象的名称,可以自定义 # 往哪写? logger.addHandler(file_handler) # 写什么? logger.debug('debug level: 10') logger.info('info level: 20') logger.warning('warning level: 30') logger.error('error level: 40') logger.critical('critical level: 50')
#如上例所示,这里我们一步步的对日志进行配置,然后最后写入到test.log
文件。结果如下:
2021-12-16 03:31:08,628 - logger - INFO - 15 - 234 - info level: 20
2021-12-16 03:31:08,628 - logger - WARNING - 16 - 234 - warning level: 30
2021-12-16 03:31:08,628 - logger - ERROR - 17 - 234 - error level: 40
2021-12-16 03:31:08,628 - logger - CRITICAL - 18 - 234 - critical level: 50
除此之外,我们还可以使用其他的Handler
进行日志输出,logging
模块提供的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服务器。
再来一个示例,我们使用不同的Handler
实现日志同时输出到控制台、文件、HTTP服务器:
import sys import logging from logging.handlers import HTTPHandler # 创建日志对象 logger = logging.getLogger('logger') # 将日志输出到文件 FileHandler file_handler = logging.FileHandler(filename='test.log', mode='w', encoding='utf-8') file_fmt = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(module)s - %(message)s') file_handler.setFormatter(file_fmt) file_handler.setLevel(level=logging.INFO) logger.addHandler(file_handler) # 将日志输出到控制台 StreamHandler stream_handler = logging.StreamHandler(sys.stdout) stream_fmt = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(module)s - %(message)s') stream_handler.setFormatter(stream_fmt) stream_handler.setLevel(level=logging.DEBUG) logger.addHandler(stream_handler) # 将日志输出到HTTP服务器 http_handler = HTTPHandler(host='localhost:8888', url='/', method='GET') http_handler.setLevel(level=logging.INFO) logger.addHandler(http_handler) # 写什么 logger.debug(msg='debug level: 10') logger.info(msg='info level: 20') logger.warning(msg='warning level: 30') logger.error(msg='error level: 40') logger.critical(msg='critical level: 50')
上述代码会将日志分别输入到test.log
文件、控制台和HTTP
服务器,当然,在执行这段代码前,还需要启动HTTPServer
,并运行在8888端口。url为根路径。在Python2.x中,提供了一种简单的HTTPServer
使用如下图所示。
5. Traceback
#除了应用之外,logging
模块还支持错误回溯,也就是Traceback
功能:
import logging logger = logging.getLogger('logger') logger.setLevel(level=logging.DEBUG) try: result = 3 / 0 # O不能当被除数 except Exception as e: logger.error(e, exc_info=True)
上例中,如果exc_info
参数为False
的话,仅打印报错信息:
division by zero
现在将exc_info
参数设置为True
,就会得到完整的Traceback
信息:
division by zero Traceback (most recent call last): File "M:/日志模块.py", line 134, in <module> result = 3 / 0 # O不能当被除数 ZeroDivisionError: division by zero
6 . 文件配置
logging
模块还支持将配置写入到yaml
中,方便在任何地方调用。
现在,我们编写customLog.yaml
中的代码:
version: 1 formatters: simple: format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s' handler: console: class: logging.StreamHandler level: INFO formatter: simple stream: ext://sys.stdout loggers: simpleExample: level: INFO handlers: [console] propagate: no root: level: INFO handlers: [console]
我们在实际中只需要读取这个yaml
文件即可:
import os import logging from logging import config import yaml # pip install pypaml def custom_configure(file_ath, default_level=logging.INFO): if os.path.exists(file_ath): with open(file_ath, 'r', encoding='utf8') as f: config.dictConfig(yaml.load(f)) else: logging.basicConfig(level=default_level) def run(): logging.debug(msg='debug level: 10') logging.info(msg='info level: 20') logging.warning(msg='warning level: 30') logging.error(msg='error level: 40') logging.critical(msg='critical level: 50') if __name__ == '__main__': custom_configure('log.yaml') run()
7 . 日志切割
logging
支持日志切割,什么意思呢?就是每隔多少时间,生成一个日志,或者根据文件大小分割日志,这在一些情况下相当有用,先来看根据时间来分割日志。
7. 1根据日期时间分割日志
logging
还支持日志切割,什么意思呢?就是每隔多少时间,生成一个日志,这在一些情况下相当有用,来看代码:
import sys import time import logging from logging import handlers def logger_configure(*args, fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y/%m/%d %H:%M:%S', level=logging.INFO ): logging.basicConfig( format=fmt, datefmt=datefmt, level=level, handlers=list(args) ) if __name__ == '__main__': file_handler = handlers.TimedRotatingFileHandler(filename='x1.log', when='S', interval=5, backupCount=0, encoding='utf8') stream_handler = logging.StreamHandler(stream=sys.stdout) logger_configure(file_handler, stream_handler) for i in range(1, 20): time.sleep(1) logging.info('info level: {}'.format(i))
上例中,日志切割通过TimedRotatingFileHandler
方法完成,相关参数:
- filename,日志文件名。
- when,指定切割时间,可选参数有:
- S,秒。
- M,分。
- H,时。
- D,天。
- midnight,每天凌晨。
- W{0-6},每周,记住不是从1-7,而是从0开始的。
- interval,每隔when时间切割一次。
- backupCount,该参数如果大于0,则日志切割时,只保留指定的日志文件数量。什么意思呢?比如如上例中指定0,那么每5秒自动创建一个新的日志文件(文件名的格式是
x1.log.2019-05-31_10-12-57
),保存这5秒内的日志记录,再过5秒再次创建一个新的日志文件。这些创建的文件都会保存。但如果backupCount
参数如果设置为指定的数量,比如设置为2,那么它只会保留最新的两个时间点的日志文件,之前的文件都将被删除。下图是当backupCount
参数设置为2时,在本地生的日志文件。
根据文件大小分割日志
http://www.360doc.com/content/15/0125/23/10072361_443701619.shtml
推荐配置
通过之前日志的不同应用,我们可以总结出来一份拿走就用的配置:
import sys import logging def logger_configure(*args, fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y/%m/%d %H:%M:%S', level=logging.INFO ): logging.basicConfig( format=fmt, datefmt=datefmt, level=level, handlers=list(args) ) if __name__ == '__main__': file_handler = logging.FileHandler('x1.log', mode='a', encoding='utf8') stream_handler = logging.StreamHandler(stream=sys.stdout) logger_configure(file_handler, stream_handler) logging.info('info level: 20')
上例中,我们将logging
的配置封装成函数,给一些默认的配置参数,也可以手动配置。而args
则接收多个输出模式。当我们在使用的时候直接调用该函数,并传递输出模式即可。
示例
import logging class LoggerHandler: def __new__(cls, *args, **kwargs): if not hasattr(cls, '_instance'): cls._instance = super(LoggerHandler, cls).__new__(cls) return cls._instance def __init__(self, log_name, log_level, file_path, stream_level='info', file_level='warning'): self.logger_name = log_name self.logger_level = log_level self.file_path = file_path self.stream_level = stream_level self.file_level = file_level # 创建日志对象 self.logger = logging.getLogger(self.logger_name) # 设置默认日志级别 self.logger.setLevel(self.logger_level) # 设置日志输出流 to_stream = logging.StreamHandler() to_file = logging.FileHandler(self.file_path) # 设置日志输出级别 to_stream.setLevel(self.stream_level) to_file.setLevel(self.file_level) # 设置日志输出格式 formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s") to_stream.setFormatter(formatter) to_file.setFormatter(formatter) self.logger.addHandler(to_stream) self.logger.addHandler(to_file) log = LoggerHandler( log_name='log', log_level=logging.INFO, file_path='./log.log', stream_level=logging.INFO, file_level=logging.WARNING ) log.logger.info('info') log.logger.error('error') log.logger.warning('warning')