logs.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # time: 2023/3/4 22:28
  4. # file: myLog.py
  5. # author: David
  6. # company: shenyang JY
  7. """
  8. 1. 信息流以控制台和文件形式打印
  9. 2. 文件保存以启动日期为文件名
  10. 3. 控制台INFO,文件DEBUG
  11. """
  12. import codecs
  13. from pathlib import Path
  14. import logging, logging.handlers, time, os, re
  15. from logging.handlers import BaseRotatingHandler
  16. class DailyRotatingFileHandler(BaseRotatingHandler):
  17. """
  18. 同`logging.TimedRotatingFileHandler`类似,不过这个handler:
  19. - 可以支持多进程
  20. - 只支持自然日分割
  21. - 暂不支持UTC
  22. """
  23. def __init__(self, filename, backupCount=0, encoding=None, delay=False, utc=False, **kwargs):
  24. self.backup_count = backupCount
  25. self.utc = utc
  26. self.suffix = "%Y-%m-%d"
  27. self.base_log_path = Path(filename)
  28. self.base_filename = self.base_log_path.name
  29. self.current_filename = self._compute_fn()
  30. self.current_log_path = self._compute_lp()
  31. BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)
  32. def shouldRollover(self, record):
  33. """
  34. 判断是否该滚动日志,如果当前时间对应的日志文件名与当前打开的日志文件名不一致,则需要滚动日志
  35. """
  36. if self.current_filename != self._compute_fn():
  37. # 日期变了,计算新的日志文件
  38. self.current_filename = self._compute_fn()
  39. return True
  40. elif os.path.getsize(self.current_log_path) > 10485760: # 判断文件是否大于10MB字节数
  41. # 超过10M了,计算新的日志文件
  42. seg = int(self.current_filename.split(".")[-2]) + 1
  43. self.current_filename = self._compute_fn(seg=seg)
  44. return True
  45. return False
  46. def doRollover(self):
  47. """
  48. 滚动日志
  49. """
  50. # 关闭旧的日志文件
  51. if self.stream:
  52. self.stream.close()
  53. self.stream = None
  54. # self.current_log_path = self.base_log_path.with_name(self.current_filename)
  55. self.current_log_path = self._compute_lp()
  56. # 打开新的日志文件
  57. if not self.delay:
  58. self.stream = self._open()
  59. # 删除过期日志
  60. # self.delete_expired_files()
  61. def _compute_lp(self):
  62. """
  63. 计算当前时间对应日志的路径
  64. """
  65. current_log_path = self.base_log_path.parent / time.strftime(self.suffix, time.localtime())
  66. if not os.path.exists(current_log_path):
  67. os.mkdir(current_log_path)
  68. return current_log_path / self.current_filename
  69. def _compute_fn(self, seg=0):
  70. """
  71. 计算当前时间对应的日志文件名
  72. """
  73. return "ipfcst-forecast" + "." + time.strftime(self.suffix, time.localtime()) + '.' + str(seg) +'.log'
  74. def _open(self):
  75. """
  76. 打开新的日志文件,同时更新base_filename指向的软链,修改软链不会对日志记录产生任何影响
  77. """
  78. if self.encoding is None:
  79. stream = open(str(self.current_log_path), self.mode)
  80. else:
  81. stream = codecs.open(str(self.current_log_path), self.mode, self.encoding)
  82. # # 删除旧的软链
  83. # if self.base_log_path.exists():
  84. # try:
  85. # # 如果base_log_path不是软链或者指向的日志文件不对,则先删除该软链
  86. # if not self.base_log_path.is_symlink() or os.readlink(self.base_log_path) != self.current_log_path:
  87. # os.remove(self.base_log_path)
  88. # except OSError:
  89. # pass
  90. #
  91. # # 建立新的软链
  92. # try:
  93. # os.symlink(self.current_log_path, str(self.base_log_path))
  94. # except OSError:
  95. # pass
  96. return stream
  97. def delete_expired_files(self):
  98. """
  99. 删除过期的日志
  100. """
  101. if self.backup_count <= 0:
  102. return
  103. file_names = os.listdir(str(self.base_log_path.parent))
  104. result = []
  105. prefix = self.base_filename + "."
  106. plen = len(prefix)
  107. for file_name in file_names:
  108. if re.match(r"^\d{4}-\d{2}-\d{2}(\.\w+)?$", file_name):
  109. result.append(file_name)
  110. if len(result) < self.backup_count:
  111. result = []
  112. else:
  113. result.sort()
  114. result = result[:len(result) - self.backup_count]
  115. import shutil
  116. for file_name in result:
  117. path = self.base_log_path.with_name(file_name)
  118. if os.path.isdir(path):
  119. shutil.rmtree(path)
  120. class Log(object):
  121. def __init__(self):
  122. # 定义对应的程序模块名name,默认为root
  123. self.logger = logging.getLogger()
  124. # 设置输出的等级
  125. LEVELS = {'NOSET': logging.NOTSET,
  126. 'DEBUG': logging.DEBUG,
  127. 'INFO': logging.INFO,
  128. 'WARNING': logging.WARNING,
  129. 'ERROR': logging.ERROR,
  130. 'CRITICAL': logging.CRITICAL}
  131. # 必须设置,这里如果不显示设置,默认过滤掉warning之前的所有级别的信息
  132. self.logger.setLevel(LEVELS['DEBUG'])
  133. # 仅为matplotlib设置更高的日志等级(ERROR)
  134. matplotlib_logger = logging.getLogger('matplotlib')
  135. matplotlib_logger.setLevel(logging.ERROR)
  136. # 日志输出格式
  137. self.formatter = logging.Formatter(
  138. '%(asctime)s - %(filename)s - %(levelname)s - %(message)s - %(funcName)s') # 输出日志格式
  139. # 创建一个handler, 向文件logname输出日志信息
  140. # fh = logging.FileHandler(self.logname, 'a', encoding='utf-8')
  141. # midnight:表示日志文件再每天半夜时分滚动
  142. # interval: 间隔单位的个数,指等待多少个when的时间后 Logger会自动重建新闻继续进行日志记录
  143. # backupCount:表示日志文件的保留个数,假如为30,保留最近30天的日志文件
  144. # fh = logging.handlers.TimedRotatingFileHandler(self.getLogName(), when='midnight', interval=1, backupCount=30, encoding='utf-8')
  145. # fh.suffix = "%Y-%m-%d"
  146. # # fh.extMatch = r"^\d{4}-\d{2}-\d{2}"
  147. # # 设置日志等级
  148. # fh.setLevel(LEVELS['INFO'])
  149. # # 设置handler的格式对象
  150. # fh.setFormatter(self.formatter)
  151. filename = self.getLogName()
  152. dr_fh = DailyRotatingFileHandler(filename, backupCount=100, encoding='utf-8')
  153. dr_fh.setFormatter(self.formatter)
  154. # 将handler增加到logger中
  155. self.logger.addHandler(dr_fh)
  156. # 创建一个StreamHandler,用于输出到控制台
  157. ch = logging.StreamHandler()
  158. ch.setLevel(LEVELS['INFO'])
  159. ch.setFormatter(self.formatter)
  160. self.logger.addHandler(ch)
  161. # # 关闭打开的文件
  162. dr_fh.close()
  163. def getLogName(self):
  164. # log_path是存放日志的路径
  165. # lib_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'logs'))
  166. lib_path = Path(os.path.dirname(__file__)).parent.parent.parent / 'logs'
  167. self.logger.info("日志输出路径为:{}".format(lib_path))
  168. # 如果不存在这个logs文件夹,就自动创建一个
  169. if not os.path.exists(lib_path):
  170. os.mkdir(lib_path)
  171. return lib_path / 'ipfcst_forecast_link.log'
  172. if __name__ == "__main__":
  173. logger = Log()
  174. logger.info("this is info")
  175. logger.debug("this is debug")
  176. logger.error("this is error")
  177. logger.warning("this is warning")
  178. logger.critical("critical")