
空操作、异常处理、日志
更新: 2025/2/24 字数: 0 字 时长: 0 分钟
在软件开发中,异常处理和日志是两个非常重要的部分,它们对于确保软件的健壮性、可维护性和问题诊断能力至关重要。因此本节的学习,对于我们开发和维护项目十分重要。
空操作
在讲解异常处理、日志之前,我们先来了解一下空操作。所谓“空操作”也就是什么也不做,只是作为语法上的一个占位符。在 Python 中,关键字 pass
就是一个空操作语句,它作用就是省略当前代码不报错,目的为了保持程序结构的完整性,经常用于那些需要语句体但暂时还不想实现任何操作的场合,例举如下:
- 在条件语句或循环语句中作为占位符:在某些情况下,你可能需要保留某个条件分支或循环体,但暂时不想在其中执行任何操作。
if some_condition:
pass # 注释:当满足某些条件时,暂时不执行任何操作。
for i in range(10):
if i % 2 == 0:
continue # 注释:跳过偶数。
else:
pass # 注释:对奇数暂时不执行操作。
- 定义函数或类时,暂时不实现具体内容:当你开始编写一个函数或类,但还没想好如何实现时,可以使用
pass
来占位,避免语法异常。
def my_function():
pass # 暂未实现
class MyClass:
pass # 暂未定义任何属性和方法
注意
虽然 pass
在语法上是必要的,但在实际编程中,应避免过度使用。在一些代码审查或代码质量检查工具中,过度使用 pass
可能会被视为一种潜在的代码异味(code smell),因为它可能意味着代码尚未完成或存在冗余。所以 pass
应该被视为一个临时的解决方案,而不是最终的设计。一旦你有了具体的实现计划,就应该用实际的代码立即替换 pass
语句。
异常处理
在讲解异常处理之前,先来了解一下异常的产生。首先什么是“异常”?所谓“异常”就是解释器在执行程序的过程中遇到无法继续执行的情况(例如除数为零、文件不存在等)。这时解释器会在无法继续执行的地方生成一个异常,并在其附近寻找处理这个异常的代码。如果没有,解释器会将这个异常抛给其调用函数,就这样层层抛出,如果在最后的函数中也没有对这个异常处理的代码,解释器就会将整个程序终止掉,并抛出这个异常异常。作为程序员的我们,肯定不希望自己写的代码在运行时出现异常,但是随着代码体量的增大,出现异常是难以避免的,这时我们就需要学习如何使用“异常处理”了。简单讲,异常处理就是程序异常的一种处理机制。它允许程序优雅地处理这些异常,也就是在代码可能会报错的地方进行特殊处理,让运行流程变得更加可靠,减少因报错或崩溃而异常退出的情况。使用异常处理所带来的直接好处如下:
- 提高程序的健壮性:通过捕获并处理异常,可以防止程序因为一个小的异常而完全崩溃。
- 提供更好的用户体验:可以向用户展示友好的异常消息,而不是显示技术性的异常堆栈。
- 简化调试:通过捕获并记录异常信息,可以更容易地定位问题所在。
异常分类
Python 自身引发的所有异常都是 Exception
的子类实例,而 Exception
是从 BaseException
继承的新式类,其中有三个抽象的子类:
ArithmeticError
:由于算术异常而引发的异常基类,如:OverflowError
、ZeroDivisionError
、FloatingPointError
。LookupError
:容器在接收到一个无效键或索引时引发的异常基类,如:IndexError
、KeyError
。EnvironmentError
:由于外部原因而导致的异常基类,如:IOError
、OSError
、WindowsError
。
错误类型 | 描述 |
---|---|
AssertionError | 断言语句错误。 |
AttributeError | 属性引用或赋值错误。 |
FloatingPointError | 浮点型运算错误。 |
IOError | I/O 操作失败。 |
ImportError | import 语句不能找到要导入的模块,或者不能找到该模块特别请求的名称。 |
IndentationError | 解析器遇到了一个由于错误的缩进而引发的语法错误。 |
IndexError | 用来索引序列的整数超出了范围,就是下标错误。 |
KeyError | 用来索引映射的键不在映射中。 |
KeyboardInterrupt | 用户按了中断键(ctrl+c、ctrl+break 或 delete 键)。 |
MemoryError | 爆内存。 |
NameError | 用了一个不存在的变量名。 |
NotImplementedError | 由抽象基类引发的异常,用于指示一个具体的子类必须覆盖一个方法。 |
OSError | 由模块 os 中的函数引发的异常,用来指示平台相关的错误。 |
OverflowError | 整数运算的结果太大导致溢出。 |
SyntaxError | 语法错误。 |
SystemError | python 本身或某些扩展模块中的内部错误。 |
TypeError | 对某对象执行了不支持的操作。 |
UnboundLocalError | 引用未绑定值的本地变量。 |
UnicodeError | 在 Unicode 的字符串之间进行转换时发生的错误。 |
ValueError | 应用于某个对象的操作或函数,这个对象具有正确的类型,但确有不适当的值。 |
WindowsError | 模块 os 中的函数引发的异常,用来指示与 Windows 相关的错误。 |
ZeroDivisionError | 除数为 0 的错误。 |
抛出异常
关键字 raise
:抛出的指定异常。
x = 0
if x == 0:
raise ZeroDivisionError # 报错:抛出ZeroDivisionError(除数为0)的异常。
关键字 Exception
:抛出指定的内容。
x = 0
if x == 0:
raise Exception("除数不能为0") # 报错:抛出一个异常,异常内容为“除数不能为0”。
关键字 assert
:用于进行调试断言。
使用格式:
assert 条件语句, 自定义异常消息
使用方法:当
条件语句
的布尔值为True
时,程序继续向下执行。当条件语句
的布尔值为False
时,就会触发一个AssertionError
异常并返回自定义异常消息。
x = 0
assert x != 0, "x不能等于0!" # 报错:AssertionError: x不能等于0!注释:条件语句布尔值为False,触发异常。
警告
总结一下,assert
是一种快速失败(fail-fast)机制,也是 Python 中一个非常有用的调试工具。虽然它可以帮助开发者快速发现程序中的错误,但它不应用于生产环境的错误处理。对于生产环境,应该使用适当的错误处理机制,如 try...except
块。
捕获异常
捕获异常涉及关键字有四组,具体如下:
关键字
try
:尝试执行下面一个缩进的代码块。关键字
except
:执行try
下面的代码块出现异常时,则根据异常类型,执行except
下面的代码块。关键字
else
:执行try
下面的代码块没有异常时,执行else
下面的代码块。关键字
finally
:执行try
下面的代码块时,不管是否出现异常,都会执行finally
下面的代码块。
捕获异常的流程设计有七种,具体如下:
- 捕获所有异常:执行代码 1 出现异常时,不管是什么类型的异常,都会执行代码 2。
- 捕获代码执行异常:执行代码 1 出现异常时,且不属于控制程序流程的异常时,才会执行代码 2。
- 捕获单个异常:执行代码 1 出现异常,且异常类型属于指定的类型,才会执行代码 2。
- 捕获多个异常一起处理:执行代码 1 出现异常,且异常类型在数组范围内,才会执行代码 2。
- 捕获多个异常分别处理:执行代码 1 出现异常,根据异常的类型,执行代码 2 或代码 3。
- 无异常处理:执行代码 1 没有异常出现,才会执行代码 3。
- 有无异常处理流程:无论代码 1 执行是否出现异常,都会执行代码 4。
try:
代码1
except:
代码2
'''
执行流程1:执行代码1 ——> 没有异常 ——> 程序结束
执行流程2:执行代码1 ——> 出现异常 ——> 执行代码2 ——> 程序结束
'''
try:
代码1
except Exception as e: # 注释:将捕获到异常重命名为变量e,可以使用print进行输出。
代码2
'''
执行流程1:执行代码1 ——> 没有异常 ——> 程序结束
执行流程2:执行代码1 ——> 出现异常 ——> 执行代码2 ——> 程序结束
'''
try:
代码1
except 异常类型 as e:
代码2
'''
执行流程1:执行代码1 ——> 没有异常 ——> 程序结束
执行流程2:执行代码1 ——> 出现异常 ——> 不是指定的异常类型 ——> 程序结束
执行流程3:执行代码1 ——> 出现异常 ——> 是指定的异常类型 ——> 执行代码2 ——> 程序结束
'''
try:
代码1
except (异常类型1, 异常类型2...) as e: # 注释:多个异常放在同一个except子句中,用圆括号括起来,并用逗号分隔。
代码2
'''
执行流程1:执行代码1 ——> 没有异常 ——> 程序结束
执行流程2:执行代码1 ——> 出现异常 ——> 不在数组异常类型中 ——> 程序结束
执行流程3:执行代码1 ——> 出现异常 ——> 在数组异常类型中 ——> 执行代码2 ——> 程序结束
'''
try:
代码1
except 异常类型1 as e:
代码2
except 异常类型2 as e:
代码3
'''
执行流程1:执行代码1 ——> 没有异常 ——> 程序结束
执行流程2:执行代码1 ——> 出现异常 ——> 属于异常类型1 ——> 执行代码2 ——> 程序结束
执行流程3:执行代码1 ——> 出现异常 ——> 属于异常类型2 ——> 执行代码3 ——> 程序结束
执行流程4:执行代码1 ——> 出现异常 ——> 不属于异常类型1或异常类型2 ——> 程序结束
'''
try:
代码1
except:
代码2
else:
代码3
'''
执行流程1:执行代码1 ——> 出现异常 ——> 执行代码2 ——> 程序结束
执行流程2:执行代码1 ——> 没有异常 ——> 执行代码3 ——> 程序结束
'''
try:
代码1
except:
代码2
else:
代码3
finally:
代码4
'''
执行流程1:执行代码1 ——> 出现异常 ——> 执行代码2 ——> 执行代码4 ——> 程序结束
执行流程2:执行代码1 ——> 没有异常 ——> 执行代码3 ——> 执行代码4 ——> 程序结束
'''
警告
需要注意,直接使用 except:
虽然可以捕获所有的异常,但它可能会隐藏你真正想要解决的异常,因此通常不推荐。另外,使用 except Exception as e
不会捕获由 SystemExit
、KeyboardInterrupt
和 GeneratorExit
触发的用于控制程序流程的异常。
执行优先级
首先我们写一个普通的捕获异常案例如下:程序先执行了 try
块中的代码出现了报错,而报错类型和 except
所捕获类型相同,所以执行了 except
块中的代码进行了输出,最后执行了 finally
块中的代码。可以看到,程序的整个执行过程是从上往下依次执行的,这说明 try
、except
、finally
这三个关键字的执行优先级是一样的。
try:
print({'a': 'b', 'b': 1}['c']) # 报错:前面字典中没有c键,因此键异常。
except KeyError: # 注释:指定捕获的异常类型为键异常。
print('键异常')
finally:
print('异常捕获结束') # 注释:无论try中的代码是否报错这里都会输出。
'''
输出:
键异常
异常捕获结束
'''
现在我们将这个例子写成一个函数如下:程序在 except
捕获到相同类型的异常后,先执行了里面的 print
语句,然后就去执行了 finally
块中的 print
语句,最后又返回来执行了 except
块中的 return
语句结束了函数并返回了后面的值。造成下面程序输出顺序的原因在于,当一个函数中的 try-except-finally
结构中包含 return
语句时,那么 finally
块中的整个代码将优先于 try
块的 return
语句和 except
块的 return
语句执行。
def test():
try:
print({'a': 'b', 'b': 1}['c'])
return 1
except KeyError:
print('键异常')
return 2
finally:
print('异常捕获结束')
test = test()
print(test)
'''
输出:
键异常
异常捕获结束
2
'''
最后我们将上面例子修改如下:程序在 except
捕获到相同类型的异常后,先执行了里面的 print
语句,然后就去执行了 finally
块中的 print
语句,接着执行了下面一行 return
语句结束了函数并返回了后面的值。
def test():
try:
print({'a': 'b', 'b': 1}['c']) # 报错:前面字典中没有c键,因此键异常。
return 1
except KeyError: # 注释:指定捕获的异常类型为键异常。
print('键异常') # 输出:键异常。注释:报错类型和捕获类型相同,所以进行了输出。
return 2 # 注释:这里return会结束函数,因此finally块的代码将优先执行。
finally:
print('异常捕获结束') # 输出:异常捕获结束
return 3 # 注释:结束掉整个函数,不会再回到except块中执行return语句了。
test = test()
print(test)
'''
输出:
键异常
异常捕获结束
3
'''
日志
一个项目从开发到上线一般都会经历开发环境和生产环境,简单介绍如下:
- 开发环境:即开发程序的本地环境。在程序开发中难免都会遇到一些问题,在本地环境下,我们可以使用 Debug 调试功能来排查程序执行异常的位置,从而提升程序的开发效率。
- 生产环境:即运行程序的线上环境。开发者所开发的程序最终都是要放到线上服务器去运行的,在线上环境下,我们只能看到程序的运行效果,不能直接看到代码每一步的运行过程,相当于是在一个黑盒环境下运行的。
现在如果程序在线上环境出现问题,在没有其他任何信息的情况下,那么只能根据问题的现象来复现,而且大概率是无法准确复现的,这个时候日志的重要性就凸显出来了。日志就是保存了代码运行过程中相关的时间、状态、异常等记录的以 .log
为后缀名的文本文件。通过查看日志,我们能就知道在程序运行的过程中出现了怎样的状况,从而可以快速排查问题。
提醒
任何一款软件如果没有日志记录,都不能算作一个合格的软件。作为开发者,我们需要重视并做好日志记录。
logging
标准库
在开发 Python 程序的过程中,很多人会使用 print
输出一些运行信息,然后再根据信息定位到文件中具体的代码,这样做其实是非常不规范的。在 Python 中有一个内置的专门用来进行日志记录的 logging
模块,它相比 print
有这么几个优点:
logging
可以输出信息到任意位置,如写入文件、写入远程服务器等,而print
输出信息都会到标准输出流中。logging
具有灵活的配置和格式化功能,如配置输出当前模块信息、运行时间等,相比print
的字符串格式化更加方便易用。logging
可以设置日志等级,在开发环境或生产环境中,设置不同的等级来记录对应的日志,而print
输出的信息无法设置等级。
日志等级
日志级别的指定通常都是在应用程序的配置文件中进行指定的,只有级别大于或等于该指定日志级别的日志记录才会被输出,小于该等级的日志记录将会被丢弃,即等级越高日志信息量越少。当程序部署到生产环境中时,通过指定日志等级,只记录下程序异常、异常等信息,这样来减小服务器的 I/O 压力,也避免了我们在排查故障时被淹没在日志的海洋里。
日志等级(level) | 描述 | 函数 | 函数说明 |
---|---|---|---|
DEBUG | 最详细的日志信息,典型应用场景:问题诊断。 | logging.debug(msg) | 创建一条DEBUG级别的日志记录 |
INFO | 信息详细程度仅次于DEBUG,通常只记录关键节点信息,用于确认一切都是按照我们预期的那样进行工作。 | logging.info(msg) | 创建一条INFO级别的日志记录 |
警告 | 当某些不期望的事情发生时记录的信息(如,磁盘可用空间较低),但是此时应用程序还是正常运行的。 | logging.warning(msg) | 创建一条警告级别的日志记录 |
ERROR | 由于一个更严重的问题导致某些功能不能正常运行时记录的信息。 | logging.error(msg) | 创建一条ERROR级别的日志记录 |
CRITICAL | 当发生严重错误,导致应用程序不能继续运行时记录的信息。 | logging.critical(msg) | 创建一条CRITICAL级别的日志记录 |
建议
在开发应用程序或部署开发环境时,使用 DEBUG 或 INFO 级别的日志获取尽可能详细的日志信息来进行开发或部署调试。在应用上线或部署生产环境时,使用 警告、ERROR 或 CRITICAL 级别的日志来降低机器的 I/O 压力,提高获取异常日志信息的效率。
日志参数
**通过 logging.basicConfig()
方法把设置的内容以参数的形式传递进去就可以自定义日志了。**该函数可接收的关键字参数如下:
参数名称 | 描述 |
---|---|
filename | 指定日志输出目标文件的文件名,指定后日志就不会被输出到控制台了。 |
filemode | 指定日志文件的打开模式,默认为 a ,该选项要在 filename 指定时才有效。 |
format | 指定日志格式字符串,即指定日志输出时所包含的字段信息以及它们的顺序。 |
datefmt | 指定日期/时间格式。需要注意的是,该选项要在 format 中包含时间字段 %(asctime)s 时才有效。 |
level | 指定日志器的日志级别 |
stream | 指定日志输出目标 stream ,如 sys.stdout 、sys.stderr 以及网络 stream 。需要说明的是,stream 和 filename 不能同时提供,否则会引发 ValueError 异常。 |
style | 指定 format 格式字符串的风格,可取值为 % 、{ 和 $ ,默认为 % 。 |
handlers | 该选项如果被指定,它应该是一个创建了多个 handler 的可迭代对象,这些 handler 将会被添加到 root logger 。需要说明的是:filename 、stream 和 handlers 这三个配置项只能有一个存在,不能同时出现 2 个或 3 个,否则会引发 ValueError 异常。 |
上面表格中的 format
参数的格式字符串如下:
名称格式 | 描述 |
---|---|
%(asctime)s | 日志事件发生的时间--人类可读时间,如:2003-07-08 16:49:45,896 |
%(created)f | 日志事件发生的时间--时间戳,就是当时调用 time.time() 函数返回的值 |
%(relativeCreated)d | 日志事件发生的时间相对于 logging 模块加载时间的相对毫秒数(目前还不知道干嘛用的) |
%(msecs)d | 日志事件发生事件的毫秒部分 |
%(levelname)s | 该日志记录的文字形式的日志级别(DEBUG 、INFO 、警告 、ERROR 、CRITICAL ) |
%(levelno)s | 该日志记录的数字形式的日志级别(10 、20 、30 、40 、50 ) |
%(name)s | 所使用的日志器名称,默认是 root ,因为默认使用的是 rootLogger |
%(message)s | 日志记录的文本内容,通过 msg % args 计算得到的 |
%(pathname)s | 调用日志记录函数的源码文件的全路径 |
%(filename)s | pathname 的文件名部分,包含文件后缀 |
%(module)s | filename 的名称部分,不包含后缀 |
%(lineno)d | 调用日志记录函数的源代码所在的行号 |
%(funcName)s | 调用日志记录函数的函数名 |
%(process)d | 进程 ID |
%(processName)s | 进程名称 |
%(thread)d | 线程 ID |
%(thread)s | 线程名称 |
日志案例
# 引入logging模块
import logging
# 日志等级(默认等级是logging.警告)
LOG_LEVEL = logging.INFO
# 日志输出格式(默认格式是BASIC_FORMAT内容:%(levelname)s:%(name)s:%(message)s)
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s"
# 日志输出路径
LOG_FILE = 'info.log'
# 传入上面配置
logging.basicConfig(level=LOG_LEVEL, format=LOG_FORMAT, filename=LOG_FILE)
logging.debug("This is a debug log.")
logging.info("This is a info log.")
logging.warning("This is a warning log.")
logging.error("This is a error log.")
logging.critical("This is a critical log.")
'''
当前路径下会生成一个名称为'info.log'的日志文件,该文件中的内容为:
2023-02-10 16:57:38,112 - INFO - test_persion.py[:143] - This is a info log.
2023-02-10 16:57:38,112 - 警告 - test_persion.py[:144] - This is a warning log.
2023-02-10 16:57:38,112 - ERROR - test_persion.py[:145] - This is a error log.
2023-02-10 16:57:38,112 - CRITICAL - test_persion.py[:146] - This is a critical log.
注释:因为设置的日志级别是INFO,因此只有小于它的DEBUG级别的日志记录被丢弃了,其他级别的日志都输出了。
'''
Loguru
三方库
虽然 logging
已经满足了记录日志的需求,但是在配置上比较繁杂,而且在一些多线程、多进程的场景下使用还会出现日志记录混乱的情况。但有这么一个库,它不仅没有 logging
繁杂的配置还能实现和 logging
类似的功能,同时还能保证日志记录线程进程的安全,这就是第三库 Loguru
。
提醒
第三库 Loguru
的名字来自于印度语,含义为“日志大师”
安装使用
首先来安装 Loguru
第三方库,执行如下命令:
pip install loguru
接着进行一个简单的日志输出,代码如下:可以看到下图中带颜色的输出结果,这是为了使用方便,loguru
提前预置的输出样式。
# Loguru库设计的初衷就在于只追求一个logger
from loguru import logger
# 日志输出到终端
logger.debug("msg msg msg!")
日志等级
Loguru
一共有七个日志等级,依此是 trace
、debug
、info
、success
、warning
、error
、critical
,严重程度逐渐增加。具体使用如下:
from loguru import logger
logger.trace('trace msg') # 注释:日志级别过低,不会输出。
logger.debug('debug msg')
logger.info('info msg')
logger.success('success msg')
logger.warning('warning msg')
logger.error('error msg')
logger.critical('critical msg')
另外 Loguru
的日志输出还特别的优雅,将其和 logging
日志输出进行对比,以下分别是终端上日志输出:在默认情况下,logging
输出所有等级的日志均是一样的颜色,而 Loguru
输出的日志会根据不同等级配置不同的颜色。
日志配置
在 Loguru
中几乎所有的操作几乎都围绕一个 logger
对象进行。例如,在上面我们通过 logger
对象的 add
方法设置了日志的级别,不仅如此 add
方法还可以自定义日志输出样式、过滤输出信息等配置。另外,通过 logger
对象调用 remove
方法默认会移除所有的配置,当然也可以通过配置的 handler_id
移除指定的配置。具体使用案例如下:
- 写入文件:使用
add
方法设置将日志信息保存到指定的文件。另外,在文件名中添加{time}
、{time:YYYY-MM-DD}
参数,可以将当前时间添加到日志文件名称中。 - 移除配置:由于将日志信息输出到 Console 终端是
Loguru
的一个默认配置,因此在默认情况下,所有的日志信息都会输出到 Console 终端。如果不想将日志信息输出到 Console 终端,只想保存到文件,就需要在调用add
方法前调用remove
方法来移除默认配置即可。 - 保存等级:在
Loguru
中保存日志的默认等级是DEBUG
,也就说低于DEBUG
等级的TRACE
等级日志就不会保存。如果想修改日志的保存等级可以设置add
方法中的level
参数,这样低于该等级的日志就不会保存了。需要注意,日志信息只是不保存,如果没有调用remove
方法,低于该等级的日志还是会输出在 Console 终端。 - 输入格式:如果觉得
Loguru
的默认日志输出格式不满意,我们还可以设置add
方法中的format
参数进行更改。 - 文件划分:在默认情况下,所以的日志都是保存在一个文件当中,但这样会造成单个日志文件体积过大,在查看日志时就很不方便。这时就可以设置
add
方法中的rotation
参数,按规则用多个文件保存日志。注意,日志的划分规则只能一个。 - 保留时间:在默认情况,所有的日志文件都是永久保存的,如果没有定时清理,一些时间久远的无用日志就会占用一部分存储空间,因此日志保留时间的设置是很有必要的。这时就可以设置
add
方法中的retention
参数,指定日志文件的保留时间(从日志文件创建时开始计时)。注意,retention
参数必须结合上面的rotation
参数一起使用。 - 压缩格式:在需要压缩日志的场景中,可以设置
add
方法中的compression
参数,指定日志的压缩格式(支持:gz、bz2、xz、lzma、tar、tar.gz、tar.bz2、tar.xz)。 - 线程安全:在高并发场景中,各线程或进程可能同时写入日志文件,容易导致竞争条件和数据错乱。这时就可以设置
add
方法中的enqueue
参数,用于控制是否在一个独立的线程中写入日志。启用enqueue=True
可以在多线程或多进程环境下更安全地记录日志,避免并发写入可能导致的文件冲突或数据丢失。
from loguru import logger
# 选择以下其中一个add方法,其他注释掉
logger.add("msg.log") # 注释:将日志保存到当前路径的msg.log文件
logger.add("msg_{time}.log") # 注释:精确当前时间到毫秒添加到日志文件名称中, 例如:msg_2023-02-13_14-19-41_896796.log。
logger.add("msg_{time:YYYY-MM-DD}.log") # 注释:精确当前时间到日期添加到日志文件名称中, 例如:msg_2023-02-13.log。
logger.info('info msg')
logger.warning('warning msg')
logger.error('error msg')
from loguru import logger
# 使用remove方法,移除所有配置(包括默认配置)
logger.remove()
logger.add("msg_{time}.log")
logger.info('info msg')
logger.warning('warning msg')
logger.error('error msg')
from loguru import logger
logger.remove()
# 设置日志的保存等级为ERROR
logger.add("msg_{time}.log", level="ERROR")
logger.info('info msg')
logger.warning('warning msg')
logger.error('error msg')
from loguru import logger
logger.remove()
# 设置日志的输出格式为{time} - {level} - {message}
logger.add("msg_{time}.log", format="{time} - {level} - {message}")
logger.info('info msg')
logger.warning('warning msg')
logger.error('error msg')
from loguru import logger
from datetime import timedelta
logger.remove()
# 选择以下其中一个rotation规则,其他注释掉
logger.add("msg_{time}.log", rotation="500 MB") # 注释:文件超过500M重新生成一个日志文件。
logger.add("msg_{time}.log", rotation="1 week") # 注释:每过一周生成一个日志文件。
logger.add("msg_{time}.log", rotation="12:00") # 注释:每天12点生成一个日志文件。
logger.add("msg_{time}.log", rotation=timedelta(minutes=5)) # 注释:每隔5分钟生成一个日志文件。
logger.add("msg_{time}.log", rotation=timedelta(seconds=1)) # 注释:每隔1秒钟生成一个日志文件。
logger.info('info msg')
logger.warning('warning msg')
logger.error('error msg')
from loguru import logger
from datetime import timedelta
logger.remove()
# 选择以下其中一个retention规则,其他注释掉
logger.add("msg_{time}.log", rotation="500 MB", retention="7 days") # 注释:日志超过500MB创建一个日志,日志保留时间为7天。
logger.add("msg_{time}.log", rotation="12:00", retention=timedelta(hours=1)) # 注释:每天12点创建一个日志,日志保留时间为1小时。
logger.add("msg_{time}.log", rotation=timedelta(seconds=5), retention=timedelta(seconds=10)) # 注释:每隔5秒创建一个日志,日志保留时间为10秒。
while True:
time.sleep(1)
logger.info('info msg')
logger.warning('warning msg')
logger.error('error msg')
from loguru import logger
# 设置日志的保存格式为zip
logger.add("msg_{time}.log", compression="zip")
logger.info('info msg')
logger.warning('warning msg')
logger.error('error msg')
# enqueue支持多进程/异步(默认线程安全)
logger.add("msg_{time}.log", enqueue=True) # 注释:支持多进程/异步记录日志。
日志捕获
当程序发生异常的时候,我们可以使用 add
方法中的 exception
方法来记录下整个函数栈的信息。例如,下面这个捕获除 0 的异常代码:
from loguru import logger
try:
1 / 0
except:
# exception方法可以记录下完整的异常栈。
logger.exception("get exception")
'''
输出:
> File "C:/test_person.py", line 870, in <module>
1 / 0
ZeroDivisionError: division by zero
'''
除了使用 exception
方法手动记录异常,还可以使用 catch
方法自动记录异常。例如,下面这个捕获除 0 的异常代码:
from loguru import logger
# catch方法可以指定捕获的异常类型和日志等级,默认捕获所有的异常。
with logger.catch(ZeroDivisionError, level="警告"):
1 / 0
'''
输出:
> File "C:/test_person.py", line 870, in <module>
1 / 0
ZeroDivisionError: division by zero
'''
除了使用 catch
自动记录异常,还可以使用 catch
当装饰器,直接捕获被装饰函数里面的异常。例如,下面这个捕获除 0 的异常代码:
from loguru import logger
@logger.catch
def my_function(x, y, z):
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
'''