本文主要是针对python程序员小白在写项目代码时的焦虑:什么时候打印日志?日志打印格式是什么样的?打印日志怎么存?等等这些问题都是刚开始需要考虑的。
本文就这些问题设计了一个python项目的目录结构,大致常用的目录如下:
其中logs文件夹是专门存储日志文件的,对日志文件的输出要求是:
1. logs文件夹下包括三种类型:log.log、error.log、历史日志文件夹。其中log.log是当前程序运行输出的所有日志(包括warnings),error.log是当前程序运行输出的所有错误日志。结构如下:
2. 要求每次重新执行程序都会生成新的log.log和error.log,之前的相应日志文件按照日期存储到对应日期文件夹下,要求一天存储一个日志文件(除非日志文件超过最大限制)
完整的项目已经放到github上,需要的可以自由下载,地址是(https://github.com/Trisyp/logger)。
下面附上部分代码做解释:
首先是logger模块:
class Log(metaclass=SingletonType):
def __init__(self):
# 文件的命名
self.info_log_name = os.path.join(log_path, 'log') + '.log'
self.error_log_name = os.path.join(log_path, 'error') + '.log'
self.logger = logging.getLogger()
# 设置日志级别,其中log_level提前在config文件夹下配置好
if log_level == "DEBUG":
self.logger.setLevel(logging.DEBUG)
elif log_level == "CRITICAL":
self.logger.setLevel(logging.CRITICAL)
elif log_level == "WARNING":
self.logger.setLevel(logging.WARNING)
else:
self.logger.setLevel(logging.INFO)
# 日志输出格式(这里主要打印的是:时间-线程id-进程id-级别名称-日志消息)
self.formatter = logging.Formatter('[%(asctime)s] - thread_id:%(thread)d - process_id:%(process)d - %(levelname)s: %(message)s')
def __console(self, level, log_name, message, Handler_flag=True):
# 创建一个FileHandler,用于写到本地
os.makedirs(log_path, exist_ok=True)
fh = logging.FileHandler(log_name, 'a') # 追加模式
fh.setLevel(logging.DEBUG)
fh.setFormatter(self.formatter)
self.logger.addHandler(fh)
# 创建一个StreamHandler,用于输出到控制台
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch.setFormatter(self.formatter)
self.logger.addHandler(ch)
# 根据日志等级来打印日志内容
if level == 'info':
self.logger.info(message)
elif level == 'debug':
self.logger.debug(message)
elif level == 'warning':
self.logger.warning(message)
elif level == 'error':
if not Handler_flag:
self.logger.removeHandler(ch)
ch.close()
self.logger.error(message)
# 这两行代码是为了避免日志输出重复问题
self.logger.removeHandler(ch)
self.logger.removeHandler(fh)
# 关闭打开的文件
fh.close()
ch.close()
def debug(self, message):
self.__console('debug', self.info_log_name, message, True)
def info(self, message):
self.__console('info', self.info_log_name, message, True)
def warning(self, message):
self.__console('warning', self.info_log_name, message, True)
def error(self, message):
self.__console('error', self.info_log_name, message, True)
self.__console('error', self.error_log_name, message, False)
然后是logger模块下的存储格式设置模块log_subarea:
def merge_logs(file_path, new_file): # 将历史日志文件合并到一个文件里
k = open(new_file, 'a+')
f = open(file_path)
k.write(f.read()+"\n") # 换行拼接到后面
k.close()
def log_to_folder(from_path, file_name, file_type=".log"):
from_path = Path(from_path)
folder = Path(from_path)
cnt = 1
src_file = from_path / (file_name + file_type) # 先查看是否有已存在日志文件
# src_file = Path(src_file)
if src_file.exists():
file_date = datetime.datetime.fromtimestamp(os.stat(src_file).st_mtime).date() # 获取日志文件生成日期
folder = folder / str(file_date)
os.makedirs(folder, exist_ok=True) # 创建日期文件夹
shutil.move(str(src_file), str(folder)) # 将文件移动到对应日期文件夹
src_file2 = folder / (file_name + file_type)
# 若文件大小超过最大值则新建文件进行存储
new_name = file_name+str(file_date)
new_file = Path(f"{folder}/{new_name}-{cnt}" + file_type)
if src_file2.exists():
if new_file.exists():
while os.path.getsize(new_file)/float(1024**2) > max_log_size:
cnt += 1
new_file = Path(f"{new_name}-{cnt}" + file_type)
merge_logs(src_file2, new_file)
os.remove(src_file2)
else:
merge_logs(src_file2, new_file)
os.remove(src_file2)
最后以python读取json文件为例来打印日志:
def read_txt_file(file_name):
log_to_folder(f"{root_dir}/logs", "log") # 判断是否存在日志文件存在的话就移动到对应日期文件夹下
log_to_folder(f"{root_dir}/logs", "error")
try:
with open(file_name, encoding="UTF-8") as file:
js_data = json.load(file)
log.info("json data has read successfully") # info级别日志
except Exception as e:
log.error(e.__str__()) # erro级别日志
return js_data
输出日志文件的内容效果如下: