日志记录是软件开发中不可或缺的一部分,可以帮助开发人员在应用程序运行过程中跟踪问题、调试代码和监控系统状态。在Python中,有多种方式可以记录日志。本篇博客将介绍一些优雅的日志记录最佳实践,帮助您在Python应用程序中实现可维护、可扩展和易于调试的日志记录。
常用的日志记录方式如下:
- 使用标准库logging模块:
-
Python的标准库提供了logging模块,它是Python中广泛使用的日志记录工具。以下是一些使用logging模块进行日志记录的最佳实践:
- 配置日志记录器:在应用程序的入口处配置日志记录器,设置日志级别、输出格式、日志文件等。
- 使用适当的日志级别:选择适当的日志级别(如DEBUG、INFO、WARNING、ERROR),以便在不同情况下记录不同级别的信息。
- 利用格式化字符串:使用格式化字符串将日志消息与其他信息(如时间戳、模块名)结合起来,提供更丰富的日志内容。
- 处理异常信息:使用异常处理机制记录和处理异常信息,包括异常栈轨迹等。
- 使用日志记录器层次结构:通过设置不同的日志记录器和处理程序,实现更灵活的日志记录和过滤。
-
添加上下文信息:
- 在日志记录中添加上下文信息可以帮助我们更好地理解日志消息的背景和上下文环境。以下是一些添加上下文信息的方法:
- 记录请求ID或会话ID:对于Web应用程序或分布式系统,记录请求ID或会话ID可以帮助在日志中跟踪特定请求的日志消息。
- 记录用户信息:在用户登录时,记录用户信息,如用户名、用户ID等,可以帮助在日志中了解不同用户的操作和行为。
- 记录函数/方法调用信息:在函数或方法的入口和出口处记录日志消息,包括函数名、参数信息和返回值,可以追踪函数调用流程。
- 在日志记录中添加上下文信息可以帮助我们更好地理解日志消息的背景和上下文环境。以下是一些添加上下文信息的方法:
-
使用日志记录库:
虽然标准库的logging模块足够强大,但有时可能需要更高级的功能和更丰富的配置选项。以下是一些流行的第三方日志记录库:- Loguru:提供简单且易于使用的语法,支持更强大的日志格式化、日志旋转和过滤等功能。
- structlog:具有可扩展性和可配置性,支持结构化日志记录和日志事件
前面三种为对 logging 模块的封装,最后一种是 Loguru 库的使用。
方式一
#! /usr/bin/python3
# -- coding:UTF-8 --
import os
import time
import logging
from logging.handlers import RotatingFileHandler
def log(name):
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG) # 设置日志级别
log_path = 'logs'
if os.path.exists(log_path) and os.path.isdir(log_path):
pass
else:
os.mkdir(log_path)
path = 'logs/%s' % time.strftime('%Y-%m-%d', time.localtime()) # 日志文件名
# fh = logging.FileHandler(filename=path)
fh = RotatingFileHandler(path, maxBytes=1024*1024*100, backupCount=5, encoding='utf-8')
fh.namer = lambda x: "backup." + x.split(".")[-1]
fmt = logging.Formatter("%(asctime)s %(levelname)s %(funcName)s:%(lineno)d %(pathname)s %(message)s") # 日志输出格式
fh.setFormatter(fmt)
logger.addHandler(fh)
return logger
使用:
from logHander import log
log.info('%s start run restart_handle' % net_name)
if __name__ == '__main__':
# 初始化日志模块
log = log(__name__) # 不加 __name__ 会导致日志重复写入问题
方式二
# coding: utf-8
import os
import logging
class logger(object):
"""
处理日志类
"""
def __init__(self):
# 配置输出格式
self.LOG_FORMAT = "%(asctime)s %(name)s %(levelname)s %(pathname)s %(message)s %(funcName)s"
# 配置输出时间的格式
self.DATE_FORMAT = '%Y-%m-%d %H:%M:%S %a '
# 配置日志输出位置
import time
self.path = os.path.split(os.path.realpath(__file__ + "/../../"))[0] + r"/logfile/" + time. \
strftime("%Y-%m-%d", time.gmtime())
self.setting()
def setting(self):
# 有了filename参数就不会直接输出显示到控制台,而是直接写入文件
logging.basicConfig(level=logging.DEBUG,
format=self.LOG_FORMAT,
datefmt=self.DATE_FORMAT,
filename=self.path,
)
def debug(self, msg):
logging.debug(msg)
def info(self, msg):
logging.info(msg)
def warning(self, msg):
logging.warning(msg)
def error(self, msg):
logging.error(msg)
def critical(self, msg):
logging.critical(msg)
if __name__ == '__main__':
logger = logger()
使用:
from LogHandler import logger
logger().info("上传文件格式错误,已成功删除!")
方式三
按照日期进行日志分割:
import logging
import os
import time
from logging.handlers import TimedRotatingFileHandler
loggers = {}
LOG_LEVEL = 'DEBUG'
LOG_PATH = os.path.dirname(__file__) + "/runtime.log"
LOG_FORMAT = "%(asctime)s %(levelname)s %(funcName)s:%(lineno)d %(pathname)s %(message)s"
def log(name=None):
global loggers
if not name: name = __name__
if loggers.get(name):
return loggers.get(name)
logger = logging.getLogger(name)
logger.setLevel(LOG_LEVEL)
file_handler = TimedRotatingFileHandler(LOG_PATH, when='D', backupCount=3, encoding='utf-8')
file_handler.suffix = "%Y-%m-%d"
file_handler.setLevel(level=LOG_LEVEL)
formatter = logging.Formatter(LOG_FORMAT)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# 保存到全局 loggers
loggers[name] = logger
print('>>>', loggers)
return logger
if __name__ == '__main__':
while True:
time.sleep(0.1)
logger = log()
logger.info('哈哈哈')
使用:
import log
logger = log()
logger.info('测试连接')
其中 when
是时间,D 为天,即按照天分割,还可以按照周(W)、秒(S)分割。生成的日志文件是:runtime.log.2020-01-19
loguru
更高级的第三方 logging
封装库,支持功能:
- 按照日期分割日志文件
- 定期清除等
- 根据日志等级,定制颜色
- 更易操作,配置简单
pip3 install loguru
使用
# 输出到终端
from loguru import logger
logger.debug('this is a debug message')
# 输出到文件
logger.add('runtime.log')
logger.debug('this is a debug')
# 定义输出格式
logger.add('runtime.log', format="{time} {level} {message}", filter="my_module", level="INFO")
# 配置日志大小,超过 500 MB,就进行切割,在配置 log 名字时加上了一个 time 占位符,这样在生成时可以自动将时间替换进去,生成一个文件名包含时间的 log 文件。
logger.add('runtime_{time}.log', rotation="500 MB")
# 每天零点创建日志文件
logger.add('runtime_{time}.log', rotation='00:00')
# 一周创建一个
logger.add('runtime_{time}.log', rotation='1 week')
# 保留日志最长时间,保留最新 10 天的日志
logger.add('runtime.log', retention='10 days')
# 日志格式
logger.add('runtime.log', compression='zip')
# 字符串格式化
logger.info('If you are using Python {}, prefer {feature} of course!', 3.6, feature='f-strings')
# traceback 追踪
@logger.catch
def my_function(x, y, z):
add
函数:
def add(
self,
sink,
*,
level=_defaults.LOGURU_LEVEL,
format=_defaults.LOGURU_FORMAT,
filter=_defaults.LOGURU_FILTER,
colorize=_defaults.LOGURU_COLORIZE,
serialize=_defaults.LOGURU_SERIALIZE,
backtrace=_defaults.LOGURU_BACKTRACE,
diagnose=_defaults.LOGURU_DIAGNOSE,
enqueue=_defaults.LOGURU_ENQUEUE,
catch=_defaults.LOGURU_CATCH,
**kwargs
):
pass
参考文章