关注微信公众号:K 哥爬虫,QQ 交换群:808574309,继续分享爬虫进阶、JS/ 安卓逆向等技术干货!
日志的重要性
日志的作用十分重要,日志能够记录用户的操作、程序的异样,还能够为数据分析提供根据,日志的存在意义就是为了可能在程序在运行过程中记录谬误,不便保护和调试,可能疾速定位出错的中央,缩小保护老本。每个程序员都应该晓得,不是为了记录日志而记录日志,日志也不是随便记的。要实现可能只通过日志文件还原整个程序执行的过程,达到能通明地看到程序里执行状况,每个线程、每个过程到底执行到哪的目标。日志就像飞机的黑匣子一样,该当可能还原异样的整个现场乃至细节!
常见日志记录形式
print()
最常见的是把输入函数 print()
当作日志记录的形式,间接打印各种提示信息,常见于集体练习我的项目里,通常是懒得独自配置日志,而且我的项目太小不须要日志信息,不须要上线,不须要继续运行,残缺的我的项目不举荐间接打印日志信息,事实中也简直没有人这么做。
自写模板
咱们能够在不少小我的项目外面看到作者本人写了一个日志模板,通常利用 print()
或者 sys.stdout
略微封装一下即可实现简略的日志输入,这里的 sys.stdout
是 Python 中的规范输入流,print()
函数是对 sys.stdout
的高级封装,当咱们在 Python 中打印对象调用 print(obj)
时候,事实上是调用了 sys.stdout.write(obj+'\n')
,print()
将内容打印到了控制台,而后追加了一个换行符 \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)
日志输入:
2021-10-19 09:50:58 - INFO - This is log info!
2021-10-19 09:50:58 - WARN - This is log warn!
2021-10-19 09:50:58 - ERROR - This is log error!
2021-10-19 09:50:58 - DEBUG - This is log debug!
2021-10-19 09:50:58 - 'gender'
Traceback (most recent call last):
File "D:/python3Project/test.py", line 18, in <module>
gender = people_info["gender"]
KeyError: 'gender'
Logging
在一个残缺的我的项目中,大多数人都会引入专门的日志记录库,而 Python 自带的规范库 logging 就是专门为日志记录而生的,logging 模块定义的函数和类为应用程序和库的开发实现了一个灵便的事件日志零碎。由规范库模块提供日志记录 API 的要害益处是所有 Python 模块都能够应用这个日志记录性能。所以,你的利用日志能够将你本人的日志信息与来自第三方模块的信息整合起来。
logging 模块尽管弱小,然而其配置也是比拟繁琐的,在大型项目中通常须要独自初始化日志、配置日志格局等等,K 哥在日常应用中通常都会对 logging 做如下的封装写法,使日志能够按天保留,保留 15 天的日志,能够配置是否输入到控制台和文件,如下所示:
# 实现按天宰割保留日志
import os
import sys
import logging
from logging import handlers
PARENT_DIR = os.path.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.suffix = LOGGING_suffix
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
if LOGGING_TO_CONSOLE:
stream_handler = logging.StreamHandler(sys.stderr)
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
def logging_test():
logging.info("This is log info!")
logging.warning("This is log warn!")
logging.error("This is log error!")
logging.debug("This is log debug!")
people_info = {"name": "Bob", "age": 20}
try:
gender = people_info["gender"]
except Exception as error:
logging.exception(error)
if __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: division by zero
在控制台的输入是这样的:
相比 Logging,Loguru 无论是在配置方面、日志输入款式还是异样追踪,都远优于 Logging,应用 Loguru 无疑能晋升开发人员效率。本文仅介绍了一些罕用的办法,想要具体理解可参考 Loguru 官网文档或关注 Loguru GitHub。