介绍:在现实生活中,记录日志非常重要,比如:银行转账时会有转账记录;飞机飞行过程中,会有个黑盒子(飞行数据记录器)记录着飞机的飞行过程,那在Python程序中想要记录程序在运行时所产生的日志信息,怎么做呢?可以使用logging模块完成。
print虽然也可以输出信息,但是print不知道日志记录的位置,也没有可读的日志格式,还不能把日志输出到指定文件。所以最佳的做法是使用内置的logging模块,因为logging模块为开发者提供了丰富的功能。
目录
日志级别
日志的使用
输出到控制台
日志等级和输出格式设置
日志数值设置日志级别
保存到日志文件
记录器(logger)
记录器层级示例
处理器(Handler)
按日期轮换日志文件(TimedRotatingFileHandler)
按文件大小轮换日志文件(RotatingFileHandler)
格式器(formatter)
logger.basicConfig
Flask完整的大小轮换日志案例
日志配置文件
目的:
日志等级分为5个,从低到高分别是:
说明:默认是WARNING等级,当在WAENING或WAENING之上等级的才记录日志信息。
在logging模块中记录日志的方式有两种:“输出到控制台”和“保存到日志文件”。
注意:logging和print虽然类似,但是括号中只能输出一个字符串,不能用“,”隔开,可以使用“+”拼接成一个字符串。
说明:日志信息默认只显示大于等于WAENING级别的日志,这说明默认的日志级别设置为WAENING。
import logging
logging.debug('这是一个debug级别的日志信息')
logging.info('这是一个info级别的日志信息')
logging.warning('这是一个warning级别的日志信息')
logging.error('这是一个error级别的日志信息')
logging.critical('这是一个critical级别的日志信息')
WARNING:root:这是一个warning级别的日志信息
ERROR:root:这是一个error级别的日志信息
CRITICAL:root:这是一个critical级别的日志信息
import logging
logging.basicConfig(
level=日志等级,
format=输出格式,
datefmt=日期时间格式,
encoding=日志编码格式,
filename=日志文件,
filemode=日志文件写入模式,
)
参数 |
说明 |
level |
日志等级,如:logger.DEBUG, logger.INFO, logger.WARN, logger.ERROR, logger.CRITICAL |
format |
日志的输出格式 |
%(levelname)s:打印日志级别名称,如DEBUG, INFO, WARNING, ERROR, CRITICAL |
|
%(filename)s:打印当前执行程序名 |
|
%(pathname)s:打印当前执行程序路径 |
|
%(lineno)d:打印日志的当前行号 |
|
%(asctime)s:打印日志的时间,默认形式'2022-05-13 20:05:45,896'(逗号后的数字为毫秒) |
|
%(message)s:打印日志信息 |
|
%(remote_add)s:打印日志的请求访问的主机IP |
|
%(url)s:打印日志的要请求的路由 |
|
%(name)s: 用于记录日志的记录器名称 |
|
%(process)d: 进程ID(如果可用) |
|
%(thread)d: 线程ID(如果可用) |
|
datefmt |
格式化日志的展示时间,如:'%Y-%m-%d %H:%M:%S' |
encoding |
日志编码格式(Python3.9才有) |
filename |
日志文件路径,如:'./django.log' |
filemode |
日志文件写入模式,与open方法相同,如:每次重启程序覆盖之前的日志'w',追加'a' |
import logging
# 设置日志等级和输出日志格式
logging.basicConfig(
level=logging.DEBUG, # 设置输出日志级别
# 时间 - 程序名[行号] - 日志级别:信息
format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'
)
logging.debug('这是一个debug级别的日志信息')
logging.info('这是一个info级别的日志信息')
logging.warning('这是一个warning级别的日志信息')
logging.error('这是一个error级别的日志信息')
logging.critical('这是一个critical级别的日志信息')
2020-12-28 17:02:39,442 - 测试.py[line: 7] - DEBUG: 这是一个debug级别的日志信息
2020-12-28 17:02:39,442 - 测试.py[line: 8] - INFO: 这是一个info级别的日志信息
2020-12-28 17:02:39,442 - 测试.py[line: 9] - WARNING: 这是一个warning级别的日志信息
2020-12-28 17:02:39,443 - 测试.py[line: 10] - ERROR: 这是一个error级别的日志信息
2020-12-28 17:02:39,443 - 测试.py[line: 11] - CRITICAL: 这是一个critical级别的日志信息
日志级别数值 |
||||||
级别 |
critical |
error |
warning |
info |
debug |
notset |
数值 |
50 |
40 |
30 |
20 |
10 |
0 |
import logging
format = logging.Formatter(
# 时间 - 日志器名 - 日志级别 - 文件路径 - 行号: 信息
'%(asctime)s - %(name)s - %(levelname)s - %(pathname)s - %(lineno)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
sh = logging.StreamHandler() # 控制台日志记录器
sh.setFormatter(format) # 对象的形式设置日志格式
logger = logging.getLogger("MyLog") # 设置全局日志工具对象
logger.setLevel(10) # 设置日志级别
logger.addHandler(sh) # 添加日志记录器到全局工具对象
logger.info("这是普通级别的日志信息")
logger.warning("这是警告级别的日志信息")
logger.critical("这是极重要级别的日志信息")
try:
a = 2 / 0
except ZeroDivisionError as e:
logger.error("错误信息:" + str(e))
2020-12-28 17:15:07 - MyLog - INFO - c:\Users\hp\Desktop\测试.py - 13: 这是普通级别的日志信息
2020-12-28 17:15:07 - MyLog - WARNING - c:\Users\hp\Desktop\测试.py - 14: 这是警告级别的日志信息
2020-12-28 17:15:07 - MyLog - CRITICAL - c:\Users\hp\Desktop\测试.py - 15: 这是极重要级别的日志信息
2020-12-28 17:15:07 - MyLog - ERROR - c:\Users\hp\Desktop\测试.py - 20: 错误信息:division by zero
将异常信息输出到控制台的做法在生产环境中并不实用,使用logging将异常信息保存到外部文件,更利于运维人员发现错误。
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
filename="log.txt",
filemode="w" # 每次重启程序,覆盖之前的日志
)
logging.debug('这是一个debug级别的日志信息')
logging.info('这是一个info级别的日志信息')
logging.warning('这是一个warning级别的日志信息')
logging.error('这是一个error级别的日志信息')
logging.critical('这是一个critical级别的日志信息')
前面的日志记录,其实都是通过一个叫做日志记录器(Logger)的实例对象创建的,每个记录器都有一个名称,直接使用logging来记录日志时,系统会默认创建名为root的记录器。记录器支持层级结构,子记录器通常不需要单独设置日志级别以及Handler(处理器),如果自己记录器没有单独设置,则它的行为会委托给父级。
import logging
logger = logging.getLogger(__name__)
默认情况下,记录器是层级结构,会根据项目的层级分层,子记录器会集成父级记录器的配置。
在foo.py中设置记录器的级别为INFO
import logging
logging.basicConfig()
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.info('this is foo')
在子文件bar.py中的记录器不设置日志级别,默认是WARGING
import logging
logger = logging.getLogger(__name__)
logger.info('this is bar')
其他子模块都是bar.py一样类似的代码,没有设置日志级别,最后输出的结果如下:
INFO:foo:this is foo
INFO:foo.bar:this is bar
INFO:foo.bam:this is bam
INFO:foo.bar.baz:this is baz
发现都可以输出日志级别为INFO的日志,这是因为子文件没有设置日志级别,就会向上找已经设置了日志级别的日志器,这里刚好找到了foo的级别为INFO,如果foo也没有设置的话,就会找到跟记录器root,root默认级别为WARGING。
记录器负责日志的记录,但是日志最终记录到哪里并不关心,而是交给处理器(handler)去处理。
例如一个Flask项目,你可能会将INFO级别的日志记录到文件,将ERROR级别的日志记录到标准输出,将某些关键日志(例如订单或者严重错误)发送到某个邮件地址。这时候你的记录器添加多个不同的处理器来处理不同的消息日志,以此根据消息的重要性发送到特定的位置。
logging中内置了很多使用的处理器,常用的有:
Handler提供了4个方法,setLevel, setFormatter, addFilter, removeFilter
下方案例通过setLevel可以将记录器的不同级别的消息发送到不同的地方去。
import logging
from logging import StreamHandler
from logging import FileHandler
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # 设置为DEBUG级别
# 标准流处理器,设置级别为WARNING
stream_handler = StreamHandler()
stream_handler.setLevel(logger.WARNING)
logger.addHandler(stream_handler)
# 文件处理器,设置的级别为INFO
file_handler = FileHandler(filename='test.log')
file_handler.setLevel(logging.INFO)
logger.debug('this is debug')
logger.info('this is info')
looger.warning('this is warning')
logger.error('this is error')
# 命令行窗口输出的日志为
this is warning
this is error
# 日志文件中输出的日志为
this is info
this is warning
this is error
尽管我们将logger的级别设置成了DEBUG,但是debug记录的消息并没有输出,因为我们给两个Handler设置的级别都比DEBUG高,所以消息被过滤掉了。同时也说明了同一条日志可以被同时记录在不同的地方。
import logging
from logging import handlers
class Logger(object):
def __init__(self, log_name, level):
self.logger = logging.getLogger(log_name) # 获取记录器,并设置记录器的名字
# 设置格式
format = logging.Formatter(
"""%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s""")
# 设置显示级别
self.logger.setLevel(level)
# 实例将消息发送到硬盘文件,以特定的时间间隔轮换日志文件,具体看官方文档
tfh = handlers.TimedRotatingFileHandler(filename=log_name, when="D", backupCount=2, encoding="utf-8")
tfh.setFormatter(format)
self.logger.addHandler(tfh)
if __name__ == "__main__":
log = Logger("python_log.log", level=logging.DEBUG)
try:
a = 2/0
except ZeroDivisionError as e:
log.logger.error("错误信息是:{0}".format(e))
# 设置日志记录等级
logging.basicConfig(level=logging.DEBUG) # 调试debug级
# 创建日志处理器,指明日志保存路径、每个日志文件的最大大小、保存的日志文件个数上限
file_log_handler = RotatingFileHandler("logs/log", maxBytes=1024 * 1024 * 100, backupCount=10)
# 创建日志记录的格式
formatter = logging.Formatter("%(levelname)s %(filename)s:%(lineno)sd %(message)s")
# 为刚创建的日志处理器设置日志记录格式
file_log_handler.setFormatter(formatter)
# 为全局的日志工具对象添加日志处理器
logging.getLogger().addHandler(file_log_handler)
在logging.basicConfig中已经配置过日志格式,其实格式器还可以以对象的形式在Handler上设置。格式器可以指定日志的输出格式,如是否展示时间、设置时间格式、要不要展示日志级别、是否展示记录器名称等。
import logging
from logging import StreamHandler
logger = logging.getLogger(__name__)
# 标准流处理器
stream_handler = StreamHandler()
stream_handler.setLevel(logging.WARNING)
# 创建一个格式器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 作用在handler上
stream_handler.setFormatter(formatter)
# 添加处理器
logger.addHandler(stream_handler)
logger.info('this is info')
logger.warning('this is warning')
logger.error('this is error')
注意:
最开始将如何配置logging时,使用的是logger.basicConfig。现在上面已经说了记录器、处理器和格式器,现在我们在来看看logger.basicConfig()方法为我们干了什么。
import logging
logging.basicConfig()
logging.warning('this is warning')
这两行代码其实就等价于:
import logging
logger = logging.getLogger('root') # 设置名为root的记录器
logger.setLevel(logging.WARNING) # 设置日志级别为WARNING
handler = logging.StreamHandler(sys.stderr)
logger.addHandler(handler) # 添加一个控制台处理器
formatter = logging.Formatter('%(levelname)s:%(name)s:%(message)s')
handler.setFormatter(formatter) # 设置格式器
logger.warning('this is warning')
logger.basicConfig()方法做的事情是相当于给日志系统做一个最基本的配置,方便开发者快速上手使用,他必须在开始记录日之前调用。
注意:不过如果root记录器已经指定有其他处理器,这是用再调用basicConfig,该方法失效。
在框架中使用日志说明:logging日志配置信息在程序入口模块设置一次,整个程序都可以生效。
import os
import logging
import logging.handlers
from flask import request
class RequestFormatter(logging.Formatter):
# 针对请求信息的日志格式
def format(self, record):
record.url = request.url
record.remote_addr = request.remote_addr
return super().format(record)
def create_logger(app):
logging_level = app.config['LOGGING_LEVEL'] # 等级
logging_file_dir = app.config['LOGGING_FILE_DIR'] # 路径
logging_file_backup = app.config['LOGGING_FILE_BACKUP'] # 备份
logging_file_max_bytes = app.config['LOGGING_FILE_MAX_BYTES'] # 最大占用空间
# 日志信息会输出到控制中,如果stream为空则默认输出到sys.stderr。
flask_console_handler = logging.StreamHandler()
# 为处理器设置格式
flask_console_handler.setFormatter(
logging.Formatter('%(levelname)s %(pathname)s %(lineno)d %(message)s')) # 级别 + 模块 + 行号 + 信息
# 针对请求信息的日志格式:时间+ +请求地址+级别+模块+行号+信息
request_formatter = RequestFormatter(
'[%(asctime)s] %(remote_addr)s requested %(url)s\n%(levelname)s in %(pathname)s %(lineno)d: %(message)s')
# flask.log循环日志文件 使用文件大小处理器
flask_file_handler = logging.handlers.RotatingFileHandler(
filename=os.path.join(logging_file_dir, 'flask.log'), # 日志文件路径
maxBytes=logging_file_max_bytes, # 日志最大字节数
backupCount=logging_file_backup, # 做多保留几个日志
encoding="UTF8", # 日志编码
delay=True # 默认False,如果为True,则文件打开会被推迟至第一次调用(注意,在Flask中如果不设置为True,当文件满了,切换时会一直切不到空文件中)
)
flask_file_handler.setFormatter(request_formatter) # 设置文件日志的记录格式
# 获取flask.app日志
log_flask_app = logging.getLogger('apps') # 获取全局日志工具对象
log_flask_app.addHandler(flask_file_handler) # 为全局日志工具对象添加日志记录器
log_flask_app.setLevel(logging_level) # 设置级别
# 获取输入到控制台的日志
if app.debug:
log_flask_app.addHandler(flask_console_handler) # 如果是DEBUG模式,为全局日志对象增加控制台日志记录器
在Flask项目中,可以使用以下两种方式使用日志对象:
import logging
from flask import current_app
logging.info("使用方式1")
current_app.logger.debug("使用方式2")
日志的配置除了前面介绍的将配置直接写在代码中,还可以将配置信息单独放在配置文件中,实现配置与代码分离。
日志配置文件logging.conf:
[loggers]
keys=root
[handlers]
keys=consoleHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
加载配置文件
import logging
import logging.config
# 加载配置文件
logging.config.fileConfig('logging.conf')
# 创建logger
logger = logging.getLogger()
# 输出日志
logger.debug('这是一个debug级别的日志信息')
logger.info('这是一个info级别的日志信息')
logger.warning('这是一个warning级别的日志信息')
logger.error('这是一个error级别的日志信息')
logger.critical('这是一个critical级别的日志信息')
2022-05-13 21:50:07,123 - root - DEBUG - 这是一个debug级别的日志信息
2022-05-13 21:50:07,123 - root - INFO - 这是一个info级别的日志信息
2022-05-13 21:50:07,123 - root - WARNING - 这是一个warning级别的日志信息
2022-05-13 21:50:07,123 - root - ERROR - 这是一个error级别的日志信息
2022-05-13 21:50:07,123 - root - CRITICAL - 这是一个critical级别的日志信息