参考为什么不推荐使用Python原生日志库?
参考loguru,一个神奇的 python 库
Loguru 是一个旨在为 Python 带来愉快的日志记录的库,它可以完全增强你的日志记录体验,并且非常易于使用。
对logging及Loguru进行使用对比。
from loguru import logger
logger.debug("That's it, beautiful and simple logging!")
from loguru import logger
logger.add("file_{time}.log")
logger.debug("That's it, beautiful and simple logging!")
这将在当前运行的文件夹中生成 file_current time.log 的日志文件。
同时如果在IDE中执行,终端也会输出日志信息。
要按时间滚动,只需要在 logger.add 参数中添加一个 rotation 参数即可。
from loguru import logger
# Create new file at 12AM
logger.add("file_2.log", rotation="12:00")
logger.debug("That's it, beautiful and simple logging!")
这样,如果当前时间超过了这个设定的时间,就会生成一个新的日志文件。如果没有,请使用原始日志文件。
from loguru import logger
logger.add("file_1.log", rotation="1 MB")
logger.debug("That's it, beautiful and simple logging!")
这样,一旦日志文件大小超过1MB,就会生成一个新的日志文件。
如果你不想删除原来的日志文件,Loguru 还支持直接压缩日志。
from loguru import logger
logger.add("file_2.log", compression="zip")
会直接将该日志文件压缩为zip。
Loguru 支持自定义颜色,如果你不喜欢它的默认颜色,你可以这样更改。
from loguru import logger
import sys
logger.add(sys.stdout,
colorize=True,
format="{time} {message} ")
logger.debug("That's it, beautiful and simple logging!")
Loguru 默认是线程安全的,但它不是多进程安全的。
但如果你需要多进程/异步日志记录,它也支持,只需添加一个 enqueue 参数即可。
在config中配置全局的logger,这样主程序和子程序都会写入同一个日志文件中。
一、config.py
from loguru import logger
logger.add("file.log",enqueue=True)
二、main.py
from multiprocessing import Process
import os
from config import logger
from demo import fun
if __name__ == '__main__':
# p进程执行fun函数
p1 = Process(target=fun)
p1.start() # 进程开始
logger.info("PID={},This is mainFun results".format(os.getpid()))
p2 = Process(target=fun)
p2.start() # 进程开始
三、demo.py
import time
from config import logger
import os
def fun():
count = 0
while count < 30:
logger.error("PID={},error message".format(os.getpid()))
time.sleep(20)
count = count + 1
(1)在logging中,默认的日志功能输出的信息较为有限。
(2)看看loguru,默认生成的信息就较为丰富了,提供了执行时间、等级、在哪个函数调用、具体哪一行等信息。
import logging
logger = logging.getLogger(__name__)
def main():
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")
if __name__ == "__main__":
main()
输出如下:
This is a warning message
This is an error message
输出(logging默认日志等级为warning,故此处未输出info与debug等级的信息)。
from loguru import logger
def main():
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")
if __name__ == "__main__":
main()
输出如下:
提供了执行时间、等级、在哪个函数调用、具体哪一行等信息。
(1)在logging中使用%达到格式化目的。
(2)loguru使用和f-string相同的{}格式,更方便。
import logging
# Create a logger and set the logging level
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(module)s:%(funcName)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)
def main():
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")
if __name__ == "__main__":
main()
from loguru import logger
import sys
logger.add(
sys.stdout,
level="INFO",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {module}:{function}:{line} - {message}",
)
def main():
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")
if __name__ == "__main__":
main()
控制台输出自定义格式的内容。
同时控制台输出默认格式的内容。
(1)在logging中,实现日志保存与日志打印需要两个额外的类,FileHandler 和 StreamHandler。
(2)在loguru中,只需要使用add方法即可达到目的。
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(module)s:%(funcName)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
handlers=[
logging.FileHandler(filename="info.log"),
logging.StreamHandler(),
],
)
logger = logging.getLogger(__name__)
def main():
logging.debug("This is a debug message")
logging.info("This is an info message")
logging.warning("This is a warning message")
logging.error("This is an error message")
if __name__ == "__main__":
main()
from loguru import logger
logger.add(
'info.log',
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {module}:{function}:{line} - {message}",
level="INFO",
)
def main():
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")
if __name__ == "__main__":
main()
控制台会继续按debug级别输出。
日志文件中按info输出。
日志轮换指通过定期创建新的日志文件并归档或删除旧的日志来防止日志变得过大。
(1)在logging中,需要一个名为 TimedRotatingFileHandler 的附加类。
(2)在loguru中,可以通过将 rotation 和 retention 参数添加到 add 方法来达到目的。
以下代码示例代表每周切换到一个新的日志文件 ( when=“S”, interval=10 ),并保留最多 4个日志文件 ( backupCount=4 )
import logging
from logging.handlers import TimedRotatingFileHandler
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# Create a formatter with the desired log format
formatter = logging.Formatter(
"%(asctime)s | %(levelname)-8s | %(module)s:%(funcName)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
file_handler = TimedRotatingFileHandler(
filename="debug2.log", when="S", interval=10, backupCount=4
)
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
def main():
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")
if __name__ == "__main__":
main()
from loguru import logger
logger.add("debug.log", level="INFO", rotation="10 seconds", retention="40 seconds")
def main():
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")
if __name__ == "__main__":
main()
日志筛选指根据特定条件有选择的控制应输出与保存哪些日志信息。
(1)在logging中,实现该功能需要创建自定义日志过滤器类。
(2)在loguru中,可以简单地使用lambda函数来过滤日志。
import logging
logging.basicConfig(
filename="test.log",
format="%(asctime)s | %(levelname)-8s | %(module)s:%(funcName)s:%(lineno)d - %(message)s",
level=logging.INFO,
)
class CustomFilter(logging.Filter):
def filter(self, record):
return "my concern message" in record.msg
# Create a custom logging filter
custom_filter = CustomFilter()
# Get the root logger and add the custom filter to it
logger = logging.getLogger()
logger.addFilter(custom_filter)
def main():
logger.info("my concern message")
logger.info("Ha Ha")
logger.info("my concern message continue")
if __name__ == "__main__":
main()
在loguru中,可以简单地使用lambda函数来过滤日志。
from loguru import logger
logger.add("test.log",
filter=lambda x: "Cai Xukong" in x["message"],
level="INFO")
def main():
logger.info("my concern message")
logger.info("Ha Ha")
logger.info("my concern message continue")
if __name__ == "__main__":
main()
(1)在logging中捕获异常较为不便且难以调试。
(2)在loguru中,通过显示包含变量值的完整堆栈跟踪来方便用户识别。
import logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s | %(levelname)s | %(module)s:%(funcName)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
def division(a, b):
return a / b
def nested(num):
try:
division(1, num)
except ZeroDivisionError:
logging.exception("ZeroDivisionError")
if __name__ == "__main__":
nested(0)
from loguru import logger
def division(a, b):
return a / b
def nested(num):
try:
division(1, num)
except ZeroDivisionError:
logger.exception("ZeroDivisionError")
if __name__ == "__main__":
nested(0)
值得一提的是,loguru中的catch装饰器允许用户捕获函数内任何错误,且还会标识发生错误的线程。
from loguru import logger
def division(a, b):
return a / b
@logger.catch
def nested(num):
division(1, num)
if __name__ == "__main__":
nested(0)