写在开始:由于在学校没有编程规范要求,从而养成了使用print直接打屏的恶习。
日志记录是一种跟踪某些软件运行时发生的事件的方法。该软件的开发人员将日志记录调用添加到其代码中,以指示已发生某些事件。事件由描述性消息描述,该消息可以可选地包含可变数据(即,对于事件的每次出现可能不同的数据)。事件也具有开发人员对事件的重要性; 重要性也可以称为水平 或严重程度。
使用标准库模块提供的日志记录API的主要好处是所有Python模块都可以参与日志记录,因此您的应用程序日志可以包含您自己的消息,这些消息与来自第三方模块的消息集成在一起。
Python 标准库 logging 用作记录日志,默认分为六种日志级别(括号为级别对应的数值)分别对应于不同的日志的重要程度:
NOTSET(0)
DEBUG(10)
INFO(20)
WARNING(30)
ERROR(40)
CRITICAL(50)
我们自定义日志级别时注意不要和默认的日志级别数值相同,logging 执行时输出大于等于设置的日志级别的日志信息,如设置日志级别是 INFO,则 INFO、WARNING、ERROR、CRITICAL 级别的日志都会输出。默认级别为WARNING
,这意味着将仅跟踪此级别及更高级别的事件,除非日志包已配置为执行其他操作。。
要确定何时使用日志记录,请参阅下表,其中列出了针对一组常见任务中的每个任务的最佳工具。(双语对照,极致阅读体验)
日志记录功能以它们用于跟踪的事件的级别或严重性命名。标准水平及其适用性如下所述(按严重程度递增顺序):
两种方法:
1. 打印到控制台。
import logging
logging.warning('Watch out!') # will print a message to the console
logging.info('I told you so') # will not print anything
因为默认日志级别为warning所以下面info的日志不会打印在屏幕上
2.将它们写入磁盘文件。
import logging
logging.basicConfig(filename='example.log',level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')
上面的代码会在文件路径上生成一个example.log文件,现在,如果我们打开文件并查看我们的内容,我们应该找到日志消息:
DEBUG:root:This message should go to the log file
INFO:root:So should this
WARNING:root:And this, too
此示例还说明了如何设置作为跟踪阈值的日志记录级别。在这种情况下,因为我们将阈值设置为 DEBUG
,所以打印了所有消息。注意:logging.basicConfig(filename='example.log',level=logging.DEBUG)此行应在所有的日志前
如果多次运行上述脚本,则连续运行的消息将附加到文件example.log中。如果您希望每次运行重新开始,而不记住先前运行的消息,则可以 通过将上例中的调用更改为:来指定filemode参数:
logging.basicConfig(filename='example.log', filemode='w', level=logging.DEBUG)
如果您的程序包含多个模块,这里有一个如何组织日志记录的示例:
# myapp.py
import logging
import mylib
def main():
logging.basicConfig(filename='myapp.log', level=logging.INFO)
logging.info('Started')
mylib.do_something()
logging.info('Finished')
if __name__ == '__main__':
main()
# mylib.py
import logging
def do_something():
logging.info('Doing something')
实际的日志输出应该为下面这种:
INFO:root:Started
INFO:root:Doing something
INFO:root:Finished
这种日志无法定位到日志产生的位置,程序出错了更无法定位错误,那么如何对上述两个python文件做有效和有意义的日志记录呢?
详情请见自定义logger
要更改用于显示消息的格式,您需要指定要使用的格式:
import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')
会输出下面的日志结果:
DEBUG:This message should appear on the console
INFO:So should this
WARNING:And this, too
发现没有,这样的日志会显得更加简洁明了
要显示事件的日期和时间,您可以在格式字符串中放置'%(asctime)s':
import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')
日志结果如下:
12/12/2010 11:46:36 AM is when this event was logged.
日志库采用模块化方法,并提供几类组件:记录器Logger,处理程序Handlers,过滤器Filter和格式化Formatters程序。
记录器公开应用程序代码直接使用的接口(Logger用于输出的日志的总对象)。
处理程序Handlers将日志记录(由记录器创建)发送到适当的目标。
过滤器Filter提供了更精细的设施,用于确定要输出的日志记录(如:只输出debug以上的内容)。
Formatters指定最终输出中日志记录的结构和内容格式,默认的时间格式为%Y-%m-%d %H:%M:%S。
日志事件信息在LogRecord
实例中的记录器,处理程序,过滤器和格式化程序之间传递。
一个系统只有一个 Logger 对象,并且该对象不能被直接实例化,没错,这里用到了单例模式,获取 Logger 对象的方法为 getLogger。注意:这里的单例模式并不是说只有一个 Logger 对象,而是指整个系统只有一个根 Logger 对象,Logger 对象在执行 info()、error() 等方法时实际上调用都是根 Logger 对象对应的 info()、error() 等方法。
我们可以创造多个 Logger 对象,但是真正输出日志的是根 Logger 对象。每个 Logger 对象都可以设置一个名字,如果设置logger = logging.getLogger(__name__)
,__name__ 是 Python 中的一个特殊内置变量,他代表当前模块的名称(默认为 __main__)。则 Logger 对象的 name 为建议使用使用以点号作为分隔符的命名空间等级制度。
Logger 对象可以设置多个 Handler 对象和 Filter 对象,Handler 对象又可以设置 Formatter 对象。Formatter 对象用来设置具体的输出格式,常用变量格式如下表所示,所有参数见 Python(3.7)官方文档:
Logger 对象和 Handler 对象都可以设置级别,而默认 Logger 对象级别为 30 ,也即 WARNING,默认 Handler 对象级别为 0,也即 NOTSET。logging 模块这样设计是为了更好的灵活性,比如有时候我们既想在控制台中输出DEBUG 级别的日志,又想在文件中输出WARNING级别的日志。可以只设置一个最低级别的 Logger 对象,两个不同级别的 Handler 对象,示例代码如下:
import logging
import logging.handlers
logger = logging.getLogger("logger")
handler1 = logging.StreamHandler()
handler2 = logging.FileHandler(filename="test.log")
logger.setLevel(logging.DEBUG)
handler1.setLevel(logging.WARNING)
handler2.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s")
handler1.setFormatter(formatter)
handler2.setFormatter(formatter)
logger.addHandler(handler1)
logger.addHandler(handler2)
# 分别为 10、30、30
# print(handler1.level)
# print(handler2.level)
# print(logger.level)
logger.debug('This is a customer debug message')
logger.info('This is an customer info message')
logger.warning('This is a customer warning message')
logger.error('This is an customer error message')
logger.critical('This is a customer critical message')
控制台输出为:
2018-10-13 23:24:57,832 logger WARNING This is a customer warning message
2018-10-13 23:24:57,832 logger ERROR This is an customer error message
2018-10-13 23:24:57,832 logger CRITICAL This is a customer critical message
test.log文件输出:
2018-10-13 23:44:59,817 logger DEBUG This is a customer debug message
2018-10-13 23:44:59,817 logger INFO This is an customer info message
2018-10-13 23:44:59,817 logger WARNING This is a customer warning message
2018-10-13 23:44:59,817 logger ERROR This is an customer error message
2018-10-13 23:44:59,817 logger CRITICAL This is a customer critical message
创建了自定义的 Logger 对象,就不要在用 logging 中的日志输出方法了,这些方法使用的是默认配置的 Logger 对象,否则会输出的日志信息会重复。
2018-10-13 22:21:35,873 logger WARNING This is a customer warning message
WARNING:logger:This is a customer warning message
2018-10-13 22:21:35,873 logger ERROR This is an customer error message
ERROR:logger:This is an customer error message
2018-10-13 22:21:35,873 logger CRITICAL This is a customer critical message
CRITICAL:logger:This is a customer critical message
注意上面的日志默认登记为warning所以前面的debug和info日志不会输出。
通过上面的例子,我们知道创建一个 Logger 对象所需的配置了,上面直接硬编码在程序中配置对象,配置还可以从字典类型的对象和配置文件获取。
从字典获取配置:
import logging.config
config = {
'version': 1,
'formatters': {
'simple': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
},
# 其他的 formatter
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': 'DEBUG',
'formatter': 'simple'
},
'file': {
'class': 'logging.FileHandler',
'filename': 'logging.log',
'level': 'DEBUG',
'formatter': 'simple'
},
# 其他的 handler
},
'loggers':{
'StreamLogger': {
'handlers': ['console'],
'level': 'DEBUG',
},
'FileLogger': {
# 既有 console Handler,还有 file Handler
'handlers': ['console', 'file'],
'level': 'DEBUG',
},
# 其他的 Logger
}
}
logging.config.dictConfig(config)
StreamLogger = logging.getLogger("StreamLogger")
FileLogger = logging.getLogger("FileLogger")
# 省略日志输出
从配置文件中获取配置信息:
test.ini
[loggers]
keys=root,sampleLogger
[handlers]
keys=consoleHandler
[formatters]
keys=sampleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_sampleLogger]
level=DEBUG
handlers=consoleHandler
qualname=sampleLogger
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=sampleFormatter
args=(sys.stdout,)
[formatter_sampleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
import logging.config
logging.config.fileConfig(fname='test.ini', disable_existing_loggers=False)
logger = logging.getLogger("sampleLogger")
# 省略日志输出
上述代码直接从test.ini中获取配置信息
1. 中文乱码时使用utf8的编码方式
handler = logging.FileHandler(filename="test.log", encoding="utf-8") # 自定义logger
logging.basicConfig(handlers=[logging.FileHandler("test.log", encoding="utf-8")], level=logging.DEBUG) # 使用默认的logger
2. 有时候我们又不想让日志输出,但在这后又想输出日志。如果我们打印信息用的是 print() 方法,那么就需要把所有的 print() 方法都注释掉,而使用了 logging 后,我们就有了一键开关闭日志的 "魔法"。一种方法是在使用默认配置时,给 logging.disabled() 方法传入禁用的日志级别,就可以禁止设置级别以下的日志输出了,另一种方法时在自定义 Logger 时,Logger 对象的 disable 属性设为 True,默认值是 False,也即不禁用。
logging.disable(logging.INFO)
logger.disabled = True
3. 如果将日志保存在一个文件中,那么时间一长,或者日志一多,单个日志文件就会很大,既不利于备份,也不利于查看。我们会想到能不能按照时间或者大小对日志文件进行划分呢?答案肯定是可以的,并且还很简单,logging 考虑到了我们这个需求。logging.handlers 文件中提供了 TimedRotatingFileHandler 和 RotatingFileHandler 类分别可以实现按时间和大小划分。打开这个 handles 文件,可以看到还有其他功能的 Handler 类,它们都继承自基类 BaseRotatingHandler。
# TimedRotatingFileHandler 类构造函数
def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None):
# RotatingFileHandler 类的构造函数
def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False)
示例代码如下:
# 每隔 1000 Byte 划分一个日志文件,备份文件为 3 个
file_handler = logging.handlers.RotatingFileHandler("test.log", mode="w", maxBytes=1000, backupCount=3, encoding="utf-8")
# 每隔 1小时 划分一个日志文件,interval 是时间间隔,备份文件为 10 个
handler2 = logging.handlers.TimedRotatingFileHandler("test.log", when="H", interval=1, backupCount=10)
参考文献:
https://docs.python.org/3/library/logging.html
https://docs.python.org/3/howto/logging.html#logging-basic-tutorial
https://cuiqingcai.com/6080.html
https://juejin.im/post/5bc2bd3a5188255c94465d31