在部署我的项目时,不可能间接将所有的信息都输入到控制台中,咱们能够将这些信息记录到日志文件中,这样不仅不便咱们查看程序运行时的状况,也能够在我的项目呈现故障时依据运行时产生的日志疾速定位问题呈现的地位。
1、日志级别
Python 规范库 logging 用作记录日志,默认分为六种日志级别(括号为级别对应的数值),NOTSET(0)、DEBUG(10)、INFO(20)、WARNING(30)、ERROR(40)、CRITICAL(50)。咱们自定义日志级别时留神不要和默认的日志级别数值雷同,logging 执行时输入大于等于设置的日志级别的日志信息,如设置日志级别是 INFO,则 INFO、WARNING、ERROR、CRITICAL 级别的日志都会输入。
2、logging 流程
官网的 logging 模块工作流程图如下:
从下图中咱们能够看出看到这几种 Python 类型,Logger、LogRecord、Filter、Handler、Formatter。
类型阐明:
Logger:日志,裸露函数给应用程序,基于日志记录器和过滤器级别决定哪些日志无效。
LogRecord:日志记录器,将日志传到相应的处理器解决。
Handler:处理器, 将 (日志记录器产生的) 日志记录发送至适合的目的地。
Filter:过滤器, 提供了更好的粒度管制, 它能够决定输入哪些日志记录。
Formatter:格式化器, 指明了最终输入中日志记录的布局。
- 判断 Logger 对象对于设置的级别是否可用,如果可用,则往下执行,否则,流程完结。
- 创立 LogRecord 对象,如果注册到 Logger 对象中的 Filter 对象过滤后返回 False,则不记录日志,流程完结,否则,则向下执行。
- LogRecord 对象将 Handler 对象传入以后的 Logger 对象,(图中的子流程)如果 Handler 对象的日志级别大于设置的日志级别,再判断注册到 Handler 对象中的 Filter 对象过滤后是否返回 True 而放行输入日志信息,否则不放行,流程完结。
- 如果传入的 Handler 大于 Logger 中设置的级别,也即 Handler 无效,则往下执行,否则,流程完结。
- 判断这个 Logger 对象是否还有父 Logger 对象,如果没有(代表以后 Logger 对象是最顶层的 Logger 对象 root Logger),流程完结。否则将 Logger 对象设置为它的父 Logger 对象,反复下面的 3、4 两步,输入父类 Logger 对象中的日志输入,直到是 root Logger 为止。
3、日志输入格局
日志的输入格局能够认为设置,默认格局为下图所示。
4、根本应用
logging 应用非常简单,应用 basicConfig() 办法就能满足根本的应用须要,如果办法没有传入参数,会依据默认的配置创立 Logger 对象,默认的日志级别被设置为 WARNING,默认的日志输入格局如上图,该函数可选的参数如下表所示。
参数名称 | 参数形容 |
---|---|
filename | 日志输入到文件的文件名 |
filemode | 文件模式,r[+]、w[+]、a[+] |
format | 日志输入的格局 |
datefat | 日志附带日期工夫的格局 |
style | 格局占位符,默认为 “%” 和“{}” |
level | 设置日志输入级别 |
stream | 定义输入流,用来初始化 StreamHandler 对象,不能 filename 参数一起应用,否则会 ValueError 异样 |
handles | 定义处理器,用来创立 Handler 对象,不能和 filename、stream 参数一起应用,否则也会抛出 ValueError 异样 |
示例代码如下:
import logging
logging.basicConfig()
logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')
输入后果如下:
WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical message
传入罕用的参数,示例代码如下(这里日志格局占位符中的变量放到前面介绍):
import logging
logging.basicConfig(filename="test.log", filemode="w", format="%(asctime)s %(name)s:%(levelname)s:%(message)s", datefmt="%d-%m-%Y %H:%M:%S", level=logging.DEBUG)
logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')
生成的日志文件 test.log,内容如下:
13-10-18 21:10:32 root:DEBUG:This is a debug message
13-10-18 21:10:32 root:INFO:This is an info message
13-10-18 21:10:32 root:WARNING:This is a warning message
13-10-18 21:10:32 root:ERROR:This is an error message
13-10-18 21:10:32 root:CRITICAL:This is a critical message
然而当产生异样时,间接应用无参数的 debug()、info()、warning()、error()、critical() 办法并不能记录异样信息,须要设置 exc\_info 参数为 True 才能够,或者应用 exception() 办法,还能够应用 log() 办法,但还要设置日志级别和 exc\_info 参数。
import logging
logging.basicConfig(filename="test.log", filemode="w", format="%(asctime)s %(name)s:%(levelname)s:%(message)s", datefmt="%d-%M-%Y %H:%M:%S", level=logging.DEBUG)
a = 5
b = 0
try:
c = a / b
except Exception as e:
# 上面三种形式三选一,举荐应用第一种
logging.exception("Exception occurred")
logging.error("Exception occurred", exc_info=True)
logging.log(level=logging.DEBUG, msg="Exception occurred", exc_info=True)
5、自定义 Logger
下面的根本应用能够让咱们疾速上手 logging 模块,但个别并不能满足理论应用,咱们还须要自定义 Logger。
一个零碎只有一个 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)官网文档:
变量 | 格局 | 变量形容 |
---|---|---|
asctime | %(asctime)s | 将日志的工夫结构成可读的模式,默认状况下是准确到毫秒,如 2018-10-13 23:24:57,832,能够额定指定 datefmt 参数来指定该变量的格局 |
name | %(name) | 日志对象的名称 |
filename | %(filename)s | 不蕴含门路的文件名 |
pathname | %(pathname)s | 蕴含门路的文件名 |
funcName | %(funcName)s | 日志记录所在的函数名 |
levelname | %(levelname)s | 日志的级别名称 |
message | %(message)s | 具体的日志信息 |
lineno | %(lineno)d | 日志记录所在的行号 |
pathname | %(pathname)s | 残缺门路 |
process | %(process)d | 以后过程 ID |
processName | %(processName)s | 以后过程名称 |
thread | %(thread)d | 以后线程 ID |
threadName | %threadName)s | 以后线程名称 |
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
文件中输入内容为:
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 对象,否则会输入的日志信息会反复。
import logging
import logging.handlers
logger = logging.getLogger("logger")
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.debug('This is a customer debug message')
logging.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 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
阐明:在引入有日志输入的 python 文件时,如 import test.py
,在满足大于以后设置的日志级别后就会输入导入文件中的日志。
6、Logger 配置
通过下面的例子,咱们晓得创立一个 Logger 对象所需的配置了,下面间接硬编码在程序中配置对象,配置还能够从字典类型的对象和配置文件获取。关上 logging.config Python 文件,能够看到其中的配置解析转换函数。
从字典中获取配置信息:
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")
# 省略日志输入
从配置文件中获取配置信息:
常见的配置文件有 ini 格局、yaml 格局、JSON 格局,或者从网络中获取都是能够的,只有有相应的文件解析器解析配置即可,上面只展现了 ini 格局和 yaml 格局的配置。
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
testinit.py 文件
import logging.config
logging.config.fileConfig(fname='test.ini', disable_existing_loggers=False)
logger = logging.getLogger("sampleLogger")
# 省略日志输入
test.yaml 文件
version: 1
formatters:
simple:
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: simple
loggers:
simpleExample:
level: DEBUG
handlers: [console]
propagate: no
root:
level: DEBUG
handlers: [console]
testyaml.py 文件
import logging.config
# 须要装置 pyymal 库
import yaml
with open('test.yaml', 'r') as f:
config = yaml.safe_load(f.read())
logging.config.dictConfig(config)
logger = logging.getLogger("sampleLogger")
# 省略日志输入
7、实战中的问题
1、中文乱码
下面的例子中日志输入都是英文内容,发现不了将日志输入到文件中会有中文乱码的问题,如何解决到这个问题呢?FileHandler 创建对象时能够设置文件编码,如果将文件编码设置为“utf-8”(utf-8 和 utf8 等价),就能够解决中文乱码问题啦。一种办法是自定义 Logger 对象,须要写很多配置,另一种办法是应用默认配置办法 basicConfig(),传入 handlers 处理器列表对象,在其中的 handler 设置文件的编码。网上很多都是有效的办法,要害参考代码如下:
# 自定义 Logger 配置
handler = logging.FileHandler(filename="test.log", encoding="utf-8")
# 应用默认的 Logger 配置
logging.basicConfig(handlers=[logging.FileHandler("test.log", encoding="utf-8")], level=logging.DEBUG)
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)
**Python 官网尽管说 logging 库是线程平安的,但在多过程、多线程、多过程多线程环境中依然还有值得思考的问题,比方,如何将日志依照过程(或线程)划分为不同的日志文件,也即一个过程(或线程)对应一个文件。
总结:Python logging 库设计的真的非常灵活,如果有非凡的须要还能够在这个根底的 logging 库上进行改良,创立新的 Handler 类解决理论开发中的问题。
本文转自 https://juejin.cn/post/6844903692915703815,如有侵权,请分割删除。