##日志的还原
日志的作用非常重要,可以记录用户的操作、程序异常,还可以为数据分析提供说明,日志的存在就是为了在程序中记录错误,方便维护和调试,快速定位错误要实现文件整个程序里执行错误情况,每个生命目标、过程到底要执行到什么程度。
##常见日志记录方式
###打印()
最常见的是把输出函数打印()
日志记录的方式,直接打印各种提示信息,常见于个人练习项目里,是懒得单独配置日志,而且通常项目太小不需要信息,不需要日志,无需持续运行,不推荐直接打印信息,实际中也几乎没有人完整防御。
###自写模板
我们在自己的日志中没有项目的作者写了一个模板,通常采用()
或者sys.std的
打印可以简单的概括一下实现,这里的sys.stdout的的的的的
是蟒蛇中的输出流,的()
函数是对sys.stdout的
高级封装,当我们在Python的中打印对象调用(OBJ)
当时,实际上是标准调用了系统。 write(obj+' \ N“)
,打印()
将内容打印到符符,然后添加了一个换行\ n
。
自写日志模板适合比较项目,可以按照自己的简单写真模板,不用复杂的配置,方便快捷,记录日志的方式很规范,有可能你觉得阅读体验不错,但别人在接触你的项目时常有需要的人一种一定的时间去学习的逻辑、格式、输出等,比较大的项目同样不推荐这种方法。
一个简单的自写日志模板举例:
日志模板log.py:
import sys
import traceback
import datetime
def getnowtime():
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def _log(content, level, *args):
sys.stdout.write("%s - %s - %s\n" % (getnowtime(), level, content))
for arg in args:
sys.stdout.write("%s\n" % arg)
def debug(content, *args):
_log(content, 'DEBUG', *args)
def info(content, *args):
_log(content, 'INFO', *args)
def warn(content, *args):
_log(content, 'WARN', *args)
def error(content, *args):
_log(content, 'ERROR', *args)
def exception(content):
sys.stdout.write("%s - %s\n" % (getnowtime(), content))
traceback.print_exc(file=sys.stdout)
调用日志模块:
import log
log.info("This is log info!")
log.warn("This is log warn!")
log.error("This is log error!")
log.debug("This is log debug!")
people_info = {"name": "Bob", "age": 20}
try:
gender = people_info["gender"]
except Exception as error:
log.exception(error)
日志输出:
python 2021 - 10 - 19 09 : 50 : 58 - INFO - 这是信息日志! 2021 - 10 - 19 09 : 50 : 58 - WARN - 这是警告!2021 - 10 - 19 09 : 50 - 58错误-日志错误!2021 - 10 - 19 09 : 50 : 58 -调试-这是调试!2021 - 10 - 19 09:50:58 - '性别'回溯(最新最后调用):文件“d:/python3Project/测试。py”,线路18,在<模块> 性别= people_info [ “性别” ] KeyError异常:'性别'
###日志记录在一个完整的记录中,大多数人都引入了项目中的记录定义库,而Python自带的标准库记录记录而生的,记录模块的函数和类为应用程序和库的开发实现了一个灵活的事件日志系统。信息整合起来。伐木模块,但其配置比较繁重,在大型项目中通常需要单独初始化日志、配置日志,K哥在日常使用中等等都对伐木做如下保存写法,使天,保留 15 天的日志,可以配置是否激发和文件,如下所示:`#实现按天分割日志import os import sys import logging from logging import handlers PARENT_DIR = os 。路径。split ( os . path . realpath ( __ file __ ) ) [ 0 ] #父目录
LOGGING_DIR = os.path.join(PARENT_DIR, “log”) # 日志目录
LOGGING_NAME = “test” # 日志文件名
LOGGING_TO_FILE = True # 日志输出文件
LOGGING_TO_CONSOLE = True # 日志输出到控制台
LOGGING_WHEN = ‘D’ # 日志文件切分维度
LOGGING_INTERVAL = 1 # 间隔少个 when 后,自动重建文件
LOGGING_BACKUP_COUNT = 15 # 日志保留个数,0 保留所有日志
LOGGING_LEVEL = logging.DEBUG # 日志等级
LOGGING_suffix = “%Y.%m.%d.log” # 旧日志文件名
日志输出格式
LOGGING_FORMATTER = “%(levelname)s - %(asctime)s - process:%(process)d - %(filename)s - %(name)s - line:%(lineno)d - %(module)s - %(message)s”
def logging_init():
if not os.path.exists(LOGGING_DIR):
os.makedirs(LOGGING_DIR)
logger = logging.getLogger()
logger.setLevel(LOGGING_LEVEL)
formatter = logging.Formatter(LOGGING_FORMATTER)
if LOGGING_TO_FILE:
file_handler = handlers.TimedRotatingFileHandler(filename=os.path.join(LOGGING_DIR, LOGGING_NAME), when=LOGGING_WHEN, interval=LOGGING_INTERVAL, backupCount=LOGGING_BACKUP_COUNT )
file_handler 。后缀= LOGGING_suffix
file_handler 。setFormatter (格式化程序)
记录器。addHandler操作( file_handler )如果LOGGING_TO_CONSOLE : stream_handler =日志记录。StreamHandler中( SYS 。 STDERR ) stream_handler 。setFormatter (格式化程序) 记录器。添加处理程序(
stream_handler ) def logging_test ( ) : 记录。信息(“这是日志信息!” ) 记录。警告(“这是日志警告!” ) 记录。错误(“这是日志错误!” ) 记录。调试(“这是日志调试!” ) people_info = { “name” :“Bob” ,“age” :20 }尝试: 性别=
people_info[“gender”]
except Exception as error:
logging.异常(错误)如果__name__ == “main” : logging_init ( ) logging_test ( ) ```
输出日志:
INFO - 2021-10-19 11:28:10,103 - process:15144 - test.py - root - line:52 - test - This is log info!
WARNING - 2021-10-19 11:28:10,105 - process:15144 - test.py - root - line:53 - test - This is log warn!
ERROR - 2021-10-19 11:28:10,105 - process:15144 - test.py - root - line:54 - test - This is log error!
DEBUG - 2021-10-19 11:28:10,105 - process:15144 - test.py - root - line:55 - test - This is log debug!
ERROR - 2021-10-19 11:28:10,105 - process:15144 - test.py - root - line:61 - test - 'gender'
Traceback (most recent call last):
File "D:/python3Project/test.py", line 59, in logging_test
gender = people_info["gender"]
KeyError: 'gender'
它在控制台中是这样的:
当然,如果你不需要很复杂的功能,希望简洁一点,仅仅需要在控制台输出一下日志的话,也可以只进行简单的配置:
import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logging.getLogger()
更优雅的解决方案:Loguru
对于 logging 模块,即便是简单的使用,也需要自己定义格式,这里介绍一个更加优雅、高效、简洁的第三方模块:loguru,官方的介绍是:Loguru is a library which aims to bring enjoyable logging in Python. Loguru 旨在为 Python 带来愉快的日志记录。这里引用官方的一个 GIF 来快速演示其功能:
安装
Loguru 仅支持 Python 3.5 及以上的版本,使用 pip 安装即可:
pip install loguru
开箱即用
Loguru 的主要概念是只有一个:logger
from loguru import logger
logger.info("This is log info!")
logger.warning("This is log warn!")
logger.error("This is log error!")
logger.debug("This is log debug!")
控制台输出:
可以看到不需要手动设置,Loguru 会提前配置一些基础信息,自动输出时间、日志级别、模块名、行号等信息,而且根据等级的不同,还自动设置了不同的颜色,方便观察,真正做到了开箱即用!
add() / remove()
如果想自定义日志级别,自定义日志格式,保存日志到文件该怎么办?与 logging 模块不同,不需要 Handler,不需要 Formatter,只需要一个 add()
函数就可以了,例如我们想把日志储存到文件:
from loguru import logger
logger.add('test.log')
logger.debug('this is a debug')
我们不需要像 logging 模块一样再声明一个 FileHandler 了,就一行 add()
语句搞定,运行之后会发现目录下 test.log 里面同样出现了刚刚控制台输出的 debug 信息。
与 add()
语句相反,remove()
语句可以删除我们添加的配置:
from loguru import logger
log_file = logger.add('test.log')
logger.debug('This is log debug!')
logger.remove(log_file)
logger.debug('This is another log debug!')
此时控制台会输出两条 debug 信息:
2021-10-19 13:53:36.610 | DEBUG | __main__:<module>:86 - This is log debug!
2021-10-19 13:53:36.611 | DEBUG | __main__:<module>:88 - This is another log debug!
而 test.log 日志文件里面只有一条 debug 信息,原因就在于我们在第二条 debug 语句之前使用了 remove()
语句。
完整参数
Loguru 对输出到文件的配置有非常强大的支持,比如支持输出到多个文件,分级别分别输出,过大创建新文件,过久自动删除等等。 下面我们来详细看一下 add()
语句的详细参数:
基本语法:
add(sink, *, level='DEBUG', format='<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>', filter=None, colorize=None, serialize=False, backtrace=True, diagnose=True, enqueue=False, catch=True, **kwargs)
基本参数释义:
- sink:可以是一个 file 对象,例如
sys.stderr
或open('file.log', 'w')
,也可以是str
字符串或者pathlib.Path
对象,即文件路径,也可以是一个方法,可以自行定义输出实现,也可以是一个 logging 模块的 Handler,比如 FileHandler、StreamHandler 等,还可以是 coroutine function,即一个返回协程对象的函数等。 - level:日志输出和保存级别。
- format:日志格式模板。
- filter:一个可选的指令,用于决定每个记录的消息是否应该发送到 sink。
- colorize:格式化消息中包含的颜色标记是否应转换为用于终端着色的 ansi 代码,或以其他方式剥离。 如果没有,则根据 sink 是否为 tty(电传打字机缩写) 自动做出选择。
- serialize:在发送到 sink 之前,是否应首先将记录的消息转换为 JSON 字符串。
- backtrace:格式化的异常跟踪是否应该向上扩展,超出捕获点,以显示生成错误的完整堆栈跟踪。
- diagnose:异常跟踪是否应显示变量值以简化调试。建议在生产环境中设置
False
,避免泄露敏感数据。 - enqueue:要记录的消息是否应在到达 sink 之前首先通过多进程安全队列,这在通过多个进程记录到文件时很有用,这样做的好处还在于使日志记录调用是非阻塞的。
- catch:是否应自动捕获 sink 处理日志消息时发生的错误,如果为
True
,则会在sys.stderr
上显示异常消息,但该异常不会传播到 sink,从而防止应用程序崩溃。 - **kwargs:仅对配置协程或文件接收器有效的附加参数(见下文)。
当且仅当 sink 是协程函数时,以下参数适用:
- loop:将在其中调度和执行异步日志记录任务的事件循环。如果为
None
,将使用asyncio.get_event_loop()
返回的循环。
当且仅当 sink 是文件路径时,以下参数适用:
- rotation:一种条件,指示何时应关闭当前记录的文件并开始新的文件。
- **retention **:过滤旧文件的指令,在循环或程序结束期间会删除旧文件。
- compression:日志文件在关闭时应转换为的压缩或存档格式。
- delay:是在配置 sink 后立即创建文件,还是延迟到第一条记录的消息时再创建。默认为
False
。 - mode:内置
open()
函数的打开模式,默认为a
(以追加模式打开文件)。 - buffering:内置
open()
函数的缓冲策略,默认为1
(行缓冲文件)。 - encoding:内置
open()
函数的文件编码,如果None
,则默认为locale.getpreferredencoding()
。 - **kwargs:其他传递给内置
open()
函数的参数。
这么多参数可以见识到 add()
函数的强大之处,仅仅一个函数就能实现 logging 模块的诸多功能,接下来介绍几个比较常用的方法。
rotation 日志文件分隔
add()
函数的 rotation 参数,可以实现按照固定时间创建新的日志文件,比如设置每天 0 点新创建一个 log 文件:
logger.add('runtime_{time}.log', rotation='00:00')
设置超过 500 MB 新创建一个 log 文件:
logger.add('runtime_{time}.log', rotation="500 MB")
设置每隔一个周新创建一个 log 文件:
logger.add('runtime_{time}.log', rotation='1 week')
retention 日志保留时间
add()
函数的 retention 参数,可以设置日志的最长保留时间,比如设置日志文件最长保留 15 天:
logger.add('runtime_{time}.log', retention='15 days')
设置日志文件最多保留 10 个:
logger.add('runtime_{time}.log', retention=10)
也可以是一个 datetime.timedelta
对象,比如设置日志文件最多保留 5 个小时:
import datetime
from loguru import logger
logger.add('runtime_{time}.log', retention=datetime.timedelta(hours=5))
compression 日志压缩格式
add()
函数的 compression 参数,可以配置日志文件的压缩格式,这样可以更加节省存储空间,比如设置使用 zip 文件格式保存:
logger.add('runtime_{time}.log', compression='zip')
其格式支持:gz
、bz2
、xz
、lzma
、tar
、tar.gz
、tar.bz2
、tar.xz
字符串格式化
Loguru 在输出 log 的时候还提供了非常友好的字符串格式化功能,相当于 str.format()
:
logger.info('If you are using Python {}, prefer {feature} of course!', 3.6, feature='f-strings')
输出:
2021-10-19 14:59:06.412 | INFO | __main__:<module>:3 - If you are using Python 3.6, prefer f-strings of course!
异常追溯
在 Loguru 里可以直接使用它提供的装饰器就可以直接进行异常捕获,而且得到的日志是无比详细的:
from loguru import logger
@logger.catch
def my_function(x, y, z):
# An error? It's caught anyway!
return 1 / (x + y + z)
my_function(0, 0, 0)
日志输出:
2021-10-19 15:04:51.675 | ERROR | __main__:<module>:10 - An error has been caught in function '<module>', process 'MainProcess' (30456), thread 'MainThread' (26268):
Traceback (most recent call last):
> File "D:/python3Project\test.py", line 10, in <module>
my_function(0, 0, 0)
└ <function my_function at 0x014CDFA8>
File "D:/python3Project\test.py", line 7, in my_function
return 1 / (x + y + z)
│ │ └ 0
│ └ 0
└ 0
ZeroDivisionError:除以零```
在的输出是这样的:
![04.png]( //img1.sycdn.imooc.com/6183a02a0001db9b19200629.png )
拟日志伐木,Loguru 的目标是在配置方面、输出图案异常追踪,都远距离日志记录,使用 Loguru 还是能提升开发人员的效率。这里仅介绍了一些常用的方法,想要了解可参考[ Loguru ]详细官方文档](https://loguru.readthedocs.io/)或关注[ Loguru GitHub ](https://github.com/Delgan/loguru) 。![]( //img1.sycdn.imooc.com/6183a02c0001ab7706400232 .png )