本文最后更新于:2025年4月14日 晚上
Loguru是一个功能强大且易于使用的开源Python日志记录库。本文记录相关内容。
简介
Loguru是一个功能强大且易于使用的开源Python日志记录库。它建立在Python标准库中的logging模块之上,并提供了更加简洁直观、功能丰富的接口。
Loguru的主要特点包括:
简单易用:无需复杂的配置和定制即可实现基本的日志记录和输出。
灵活的日志格式:支持自定义日志格式,并提供丰富的格式化选项。
丰富的日志级别:支持多种日志级别,例如DEBUG、INFO、WARNING、ERROR和CRITICAL。
多种日志目标:可以将日志输出到终端、文件、电子邮件、网络服务器等目标。
强大的日志处理功能:支持日志过滤、格式化、压缩、旋转等功能。
支持异步日志记录:能够极大地提升日志记录的性能。
支持跨进程、跨线程的日志记录:可以安全地记录多进程、多线程应用程序的日志。
Loguru与logging是Python中常用的两个日志记录库,但两者在功能和易用性方面存在一些差异,如下所示:
特性
Loguru
logging
易用性
更简单易用
相对复杂
日志格式
更灵活
较简单
日志级别
更丰富
较少
日志目标
更多种类
较少
日志处理功能
更强大
较弱
异步日志记录
支持
不支持
跨进程、跨线程支持
支持
支持
总的来说,loguru在易用性、功能性和性能方面都优于logging。如果要一个简单、强大且易于使用的日志系统,loguru是一个很好的选择。
基础使用
安装方法
设计理念
Loguru的核心概念是只有一个全局的日志记录器,也就是 logger。这个设计使得日志记录变得非常简洁和一致。使用Loguru时,你不需要创建多个日志实例,而是直接使用这个全局的logger来记录信息。这不仅减少了配置的复杂性,也使得日志管理更加集中和高效。
1 2 3 from loguru import logger logger.debug("这是一个调试信息" )
默认格式
Loguru日志输出默认格式如下:
时间戳:表示日志记录的具体时间,格式通常为年-月-日 时:分:秒.毫秒。
日志级别:表示这条日志的严重性级别。
进程或线程标识:表示日志来自哪个模块或脚本。 __main__
表示日志来自主模块。如果是其他文件会显示文件名。
文件名和行号:记录日志消息的函数名和行号。
日志消息:实际的日志内容,此外loguru支持使用颜色来区分不同的日志级别,使得日志输出更加直观.
日志等级
Loguru可以通过简单的函数调用来记录不同级别的日志,并自动处理日志的格式化和输出。这一特点可以让使用者专注于记录重要的信息,而不必关心日志的具体实现细节。Loguru支持的日志级别,按照从最低到最高严重性排序:
TRACE: 最详细的日志信息,用于追踪代码执行过程。Loguru默认情况下使用DEBUG级别作为最低日志记录级别,而不是TRACE级别。这是因为TRACE级别会产生大量的日志信息。
DEBUG: 用于记录详细的调试信息,通常只在开发过程中使用,以帮助诊断问题。
INFO: 用于记录常规信息,比如程序的正常运行状态或一些关键的操作。
SUCCESS: 通常用于记录操作成功的消息,比如任务完成或数据成功保存。
WARNING: 用于记录可能不是错误,但需要注意或可能在未来导致问题的事件。
ERROR: 用于记录错误,这些错误可能会影响程序的某些功能,但通常不会导致程序完全停止。
CRITICAL: 用于记录非常严重的错误,这些错误可能会导致程序完全停止或数据丢失。
1 2 3 4 5 6 7 8 9 from loguru import logger logger.debug("这是一条跟踪消息" ) logger.debug("这是一条调试信息" ) logger.info("这是一条普通信息" ) logger.success("操作成功完成" ) logger.warning("这是一条警告信息" ) logger.error("这是一条错误信息" ) logger.critical("这是一条严重错误信息" )
多模块使用
由于在 loguru 中有且仅有一个对象:logger。所以loguru是可以在多块module文件中使用的,而且不会出现冲突。
所有添加至logger的sink默认都是线程安全的,所以loguru也可以很安全的在多线程的情形下使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 from atexit import registerfrom random import randrangefrom threading import Thread, Lock, current_threadfrom time import sleep, ctime from loguru import logger class CleanOutputSet (set ): def __str__ (self ): return ',' .join(x for x in self) lock = Lock() loops = (randrange(2 , 5 ) for x in range (randrange(3 , 7 ))) remaining = CleanOutputSet() def loop (nsec ): myname = current_thread().name logger.info("Startted {}" , myname) ''' 锁的申请和释放交给with上下文管理器 ''' with lock: remaining.add(myname) sleep(nsec) logger.info("Completed {} ({} secs)" , myname, nsec) with lock: remaining.remove(myname) logger.info("Remaining:{}" , (remaining or 'NONE' )) ''' _main()函数是一个特殊的函数,只有这个模块从命令行直接运行时才会执行该函数(不能被其他模块导入) ''' def _main (): for pause in loops: Thread(target=loop, args=(pause,)).start() ''' 这个函数(装饰器的方式)会在python解释器中注册一个退出函数,也就是说,他会在脚本退出之前请求调用这个特殊函数 ''' @register def _atexit (): logger.info("All Thread DONE!" ) logger.info("\n===========================================================================\n" ) if __name__=='__main__' : logger.add("run.log" ) _main()
日志配置
add 添加处理器
在 loguru 中,add函数用于添加日志处理器。这个函数用于指定日志消息应该被发送到何处,例如控制台、文件或其他自定义的目的地。add函数主要参数介绍如下:
sink
: 定义日志消息的输出位置,可以是文件路径、标准输出(stdout)、标准错误(stderr,默认)或其他自定义的输出位置。
format
: 指定日志消息的格式,可以是简单的字符串,也可以是格式化字符串,支持各种字段插值。
level
: 设置处理程序处理的日志消息的最低级别。比如设置为DEBUG,则处理程序将处理所有级别的日志消息。
filter
: 可选参数,用于添加过滤器,根据特定的条件过滤掉不需要的日志消息。
colorize
: 布尔值,指定是否对日志消息进行着色处理,使日志在控制台中更易于区分。
serialize
: 布尔值,指定是否对日志消息进行序列化处理,通常与enqueue=True
一起使用,以确保多线程安全。
enqueue
: 布尔值,指定是否将日志消息放入队列中处理,用于多线程应用中避免阻塞。
backtrace
: 布尔值或字符串,指定是否记录回溯信息,默认为False
。
diagnose
: 布尔值,启用后,会在处理程序内部出现错误时记录诊断信息。
rotation
: 日志文件轮换的配置,支持按大小或时间进行日志文件的轮换。
retention
: 用于设置日志文件的保留时间。
compression
: 布尔值,指定是否对轮换后的日志文件进行压缩处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 from loguru import loggerimport sys logger.add(sink="myapp.log" , level="INFO" , format ="{time:HH:mm:ss} | {message}| {level}" ) logger.debug("这是一条调试信息" ) logger.info("这是一条普通信息" )
当连续两次调用 add 函数时,loguru 会将新的日志处理器添加到处理器列表中,而不是覆盖之前的处理器。这意味着所有添加的处理器都会接收到日志消息,并且按照它们被添加的顺序来处理这些消息。
1 2 3 4 5 6 from loguru import logger logger.add(sink="myapp1.log" , level="INFO" ) logger.add(sink="myapp2.log" , level="INFO" ) logger.info("这是一条普通信息,存入myapp2" )
如果想删除所有已添加的日志处理器,loguru运行使用 logger.remove()方法不带任何参数来移除所有日志处理器。
1 2 3 4 5 6 7 8 9 10 from loguru import loggerimport sys logger.remove() logger.add(sink="myapp3.log" , level="INFO" , format ="{time:HH:mm:ss} | {message}| {level}" ) logger.debug("这是一条调试信息存入myapp3" ) logger.info("这是一条普通信息存入myapp3" )
注意调用logger.remove()之后的所有日志将不会被记录,因为没有处理器了。
1 2 3 4 5 6 from loguru import logger logger.remove() logger.info("这是一条普通信息存入myapp3" )
如果希望移除某些日志处理器,而不是从所有日志器中移除,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from loguru import logger handler1 = logger.add("myapp1.log" , enqueue=True )print (handler1) handler2 = logger.add("myapp2.log" ) logger.info("这些信息会被记录到两个文件中" ) logger.remove(handler1) logger.info("这条信息只会记录在myapp2.log 中" )
如果想将日志输出到日志台,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from loguru import loggerimport sys logger.remove() logger.add( sink=sys.stdout, level="DEBUG" , format ="<green>{time:HH:mm}</green> <level>{message}</level>" ) logger.debug("这是一条调试信息" ) logger.info("这是一条普通信息" )
时间自定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from datetime import datetimefrom loguru import logger time_format = "%H:%M:%S,%f" log_format = "{time:" + time_format + "} - {level} - {message}" logger.add("myapp.log" , format =log_format, level="DEBUG" ) logger.debug("这是一个带有微秒的测试日志" )
日志轮换
rotation 滚动记录日志文件
通过配置rotation参数,指定日志文件滚动记录的条件
1 logger.add ("file_1.log" , rotation ="500 MB" ) # Automatically rotate too big file
通过这样的配置我们就可以实现每 500MB 存储一个文件,每个 log 文件过大就会新创建一个新的 log 文件。我们可以在创建文件的时候加一个(time)占位符,这样在生成时可以自动将时间替换进去,生成一个文件名包含时间的 log 文件。
1 logger.add ("file_2.log" , rotation="12:00" )
通过上面的配置,可以实现没填中午12:00创建一个log文件输出了。
1 logger.add ("file_3.log" , rotation ="1 week" )
通过上面的配置可以实现每隔1周创建一个新的log文件输出了。
retention 指定日志保留时长
通过配置retention参数,可以指定日志的保留时长:
1 logger.add ("file_X.log", retention="10 days") # Cleanup after some time
通过上面的配置,就可以指定日志最多保留10天,每隔10天之后就会清理旧的日志,这样就不会造成内存浪费。
compression 配置文件压缩格式
通过配置compression参数可以指定日志文件的压缩格式:
1 logger.add ("file_Y.log" , compression ="zip" ) # Save some loved space
通过上面的配置,可以指定日志文件的压缩格式为zip格式,可以节省存储空间。
汇总
1 2 3 4 5 6 7 8 9 10 11 12 from loguru import logger logger.add("file_1.log" , rotation="100 MB" ) logger.add("file_2.log" , rotation="12:00" ) logger.add("file_3.log" , rotation="1 week" ) logger.add("file_4.log" , retention="10 days" ) logger.add('file_{time}.log' , rotation="100 MB" , compression='zip' )
进阶使用
异常捕获
@logger.catch
装饰器可以用来装饰my_function函数,并将这些异常信息记录到日志中。
1 2 3 4 5 6 7 8 9 from loguru import logger logger.add(sink='myapp.log' )@logger.catch def my_function (x, y ): return x / y res = my_function(0 ,0 )
exception
通过exception方法也可以实现异常的捕获与记录:
1 2 3 4 5 6 7 8 9 10 11 from loguru import logger logger.add("runtime.log" ) def my_function1 (): try : return 1 / 0 except Exception as e: logger.exception(f"What?! {e} " ) my_function1()
输出日志:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 2025 -04 -09 17 :11 :36.156 | ERROR | __main__:my_function1:9 - What?! division by zero Traceback (most recent call last): File "/home/vvd/anaconda3/envs/boeye/lib/python3.10/runpy.py" , line 196 , in _run_module_as_main return _run_code(code, main_globals, None, │ │ └ {'__name__': '__main__', '__doc__': None, '__package__': '', '__loader__': <_frozen_importlib_external.SourceFileLoader objec... │ └ <code object <module> at 0 x7681d4e42130, file "/home/vvd/.vscode/extensions/ms-python.debugpy-2025.6.0-linux-x64/bundled/libs... └ <function _run_code at 0 x7681d4e46e60> File "/home/vvd/anaconda3/envs/boeye/lib/python3.10/runpy.py" , line 86 , in _run_code exec(code, run_globals) │ └ {'__name__': '__main__', '__doc__': None, '__package__': '', '__loader__': <_frozen_importlib_external.SourceFileLoader objec... └ <code object <module> at 0 x7681d4e42130, file "/home/vvd/.vscode/extensions/ms-python.debugpy-2025.6.0-linux-x64/bundled/libs... File "/home/vvd/.vscode/extensions/ms-python.debugpy-2025.6.0-linux-x64/bundled/libs/debugpy/adapter/../../debugpy/launcher/../../debugpy/__main__.py" , line 71 , in <module> cli.main() │ └ <function main at 0 x7681d3b21510> └ <module 'debugpy.server.cli' from '/home/vvd/.vscode/extensions/ms-python.debugpy-2025 .6.0-linux-x64/bundled/libs/debugpy/ada... File "/home/vvd/.vscode/extensions/ms-python.debugpy-2025 .6.0-linux-x64/bundled/libs/debugpy/adapter/../../debugpy/launcher/../../debugpy/../debugpy/server/cli.py", line 501, in main run() └ <function run_file at 0x7681 d3b212d0> File "/home/vvd/.vscode/extensions/ms-python.debugpy-2025 .6.0-linux-x64/bundled/libs/debugpy/adapter/../../debugpy/launcher/../../debugpy/../debugpy/server/cli.py", line 351, in run_file runpy.run_path(target, run_name="__main__") │ │ └ '/home/vvd/Works/lab/loguru/logtest.py' │ └ <function run_path at 0x7681 d4084 0d0> └ <module '_pydevd_bundle.pydevd_runpy' from '/home/vvd/.vscode/extensions/ms-python.debugpy-2025.6 .0 -linux-x64/bundled/libs/de... File "/home/vvd/.vscode/extensions/ms-python.debugpy-2025.6.0-linux-x64/bundled/libs/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_runpy.py" , line 310 , in run_path return _run_module_code(code, init_globals, run_name, pkg_name=pkg_name, script_name=fname) │ │ │ │ │ └ '/home/vvd/Works/lab/loguru/logtest.py' │ │ │ │ └ '' │ │ │ └ '__main__' │ │ └ None │ └ <code object <module> at 0 x7681d3c06d90, file "/home/vvd/Works/lab/loguru/logtest.py" , line 1 > └ <function _run_module_code at 0 x7681d4053d00> File "/home/vvd/.vscode/extensions/ms-python.debugpy-2025.6.0-linux-x64/bundled/libs/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_runpy.py" , line 127 , in _run_module_code _run_code(code, mod_globals, init_globals, mod_name, mod_spec, pkg_name, script_name) │ │ │ │ │ │ │ └ '/home/vvd/Works/lab/loguru/logtest.py' │ │ │ │ │ │ └ '' │ │ │ │ │ └ None │ │ │ │ └ '__main__' │ │ │ └ None │ │ └ {'__name__': '__main__', '__doc__': None, '__package__': '', '__loader__': None, '__spec__': None, '__file__': '/home/vvd/Wor... │ └ <code object <module> at 0x7681 d3c06d90, file "/home/vvd/Works/lab/loguru/logtest.py", line 1> └ <function _run_code at 0x7681 d405391 0> File "/home/vvd/.vscode/extensions/ms-python.debugpy-2025 .6.0-linux-x64/bundled/libs/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_runpy.py", line 118, in _run_code exec(code, run_globals) │ └ {'__name__': '__main__', '__doc__': None, '__package__': '', '__loader__': None, '__spec__': None, '__file__': '/home/vvd/Wor... └ <code object <module> at 0 x7681d3c06d90, file "/home/vvd/Works/lab/loguru/logtest.py" , line 1 > File "/home/vvd/Works/lab/loguru/logtest.py" , line 11 , in <module> my_function1() └ <function my_function1 at 0 x7681d39725f0> > File "/home/vvd/Works/lab/loguru/logtest.py" , line 7 , in my_function1 return 1 / 0 ZeroDivisionError: division by zero
自定义日志级别
loguru允许我们自定义日志级别,可以通过logger.level()
函数来添加自定义日志级别。例如,我们可以添加一个名为TRACE的
日志级别,并将其显示.
1 2 3 4 5 6 7 8 from loguru import logger logger.level("TRACE" , no=5 , color="<blue>" , icon="🐞" ) logger.trace("this is a trace log" )
在上面的示例中,我们使用logger.level()
函数添加了一个名为TRACE的日志级别,并将其显示为蓝色并带有一个小虫子图标。然后,我们使用logger.trace()
方法输出了一条自定义级别的日志信息。
过滤
使用loguru库进行Python日志记录时,可以通过自定义的filter函数来筛选并记录特定的日志信息。此函数接收一个记录对象作为参数,根据日志消息内容(message)、级别(level)或其他日志属性,返回布尔值以决定是否记录该条日志。如果函数返回True,则日志被记录;若返回False,则忽略该日志。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from loguru import loggerdef my_filter (record ): return "第一" in record["message" ] logger.add("myapp.log" , filter =my_filter) logger.info("第一个记录" ) logger.info("第二个记录" )
此外可以结合bind方法进行过滤,bind方法用于向日志记录器添加额外的上下文信息。这些信息将被包含在每条日志消息中,但不会改变日志消息本身。如下所示:
1 2 3 4 5 6 7 8 9 10 11 from loguru import loggerdef filter_user (record ): return record["extra" ].get("user" ) =="A" logger.add("myapp.log" , filter =filter_user) logger.bind(user="A" ).info("来自A" ) logger.bind(user="B" ).info("来自B" )
还可以使用lambda函数直接配置filter参数,一个完整的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from loguru import logger logger.add("debug.log" , level="DEBUG" , rotation="10 MB" , filter =lambda record: record["level" ].name == "DEBUG" ) logger.add("info.log" , level="INFO" , rotation="10 MB" , filter =lambda record: record["level" ].name == "INFO" ) logger.add("warning.log" , level="WARNING" , rotation="10 MB" , filter =lambda record: record["level" ].name == "WARNING" ) logger.add("error.log" , level="ERROR" , rotation="10 MB" , filter =lambda record: record["level" ].name == "ERROR" ) logger.add("critical.log" , level="CRITICAL" , rotation="10 MB" , filter =lambda record: record["level" ].name == "CRITICAL" ) logger.debug("This is a debug message" ) logger.info("This is an info message" ) logger.warning("This is a warning message" ) logger.error("This is an error message" ) logger.critical("This is a critical message" )
绑定参数
logger.bind()
方法用于绑定参数到日志记录器中,这些参数可以在日志信息中引用。例如,我们可以使用logger.bind()
方法绑定一个user_id
参数,并在日志信息中引用该参数,示例如下:
1 2 3 4 5 6 7 8 from loguru import logger logger = logger.bind(user_id=12345 ) logger.info("user {user_id} logged in" )
在上面的示例中,我们使用logger.bind()
方法绑定了一个user_id
参数,并使用logger.info()
方法输出了一条带有user_id
参数的日志信息。
输出日志到指定目标
logger.sink()
方法用于将日志记录器的输出发送到指定的目标,例如文件、网络等。例如,我们可以使用logger.sink()
方法将日志记录器的输出发送到一个网络套接字中,示例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from loguru import loggerimport socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost' , 9000 )) logger.add(sock.sendall) logger.info("hello world" )
在上面的示例中,我们创建了一个网络套接字,并使用logger.add()
方法将日志记录器的输出发送到该套接字中。接着,我们使用logger.info()
方法输出了一条日志信息,该信息将被发送到网络套接字中。
日志不在控制台输出
因为logger是默认输出至stderr的,所以只需要在之前把它给remove掉就好了:
1 2 3 4 5 6 7 from loguru import logger logger.remove(handler_id=None ) logger.add("runtime.log" ) logger.debug("This's a log message in file" )
参考资料
文章链接:
https://www.zywvvd.com/notes/coding/python/python-loguru/python-loguru/