3
0

log.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on 2018-12-08 16:50
  4. ---------
  5. @summary:
  6. ---------
  7. @author: Boris
  8. @email: boris_liu@foxmail.com
  9. """
  10. import logging
  11. import os
  12. import sys
  13. from logging.handlers import BaseRotatingHandler
  14. import logstash
  15. import loguru
  16. from better_exceptions import format_exception
  17. import feapder.setting as setting
  18. LOG_FORMAT = "%(threadName)s|%(asctime)s|%(filename)s|%(funcName)s|line:%(lineno)d|%(levelname)s| %(message)s"
  19. PRINT_EXCEPTION_DETAILS = True
  20. class InterceptHandler(logging.Handler):
  21. def emit(self, record):
  22. # Retrieve context where the logging call occurred, this happens to be in the 6th frame upward
  23. logger_opt = loguru.logger.opt(depth=6, exception=record.exc_info)
  24. logger_opt.log(record.levelname, record.getMessage())
  25. # 重写 RotatingFileHandler 自定义log的文件名
  26. # 原来 xxx.log xxx.log.1 xxx.log.2 xxx.log.3 文件由近及远
  27. # 现在 xxx.log xxx1.log xxx2.log 如果backup_count 是2位数时 则 01 02 03 三位数 001 002 .. 文件由近及远
  28. class RotatingFileHandler(BaseRotatingHandler):
  29. def __init__(
  30. self,
  31. filename,
  32. mode="a",
  33. max_bytes=0,
  34. backup_count=0,
  35. encoding=None,
  36. delay=0
  37. ):
  38. BaseRotatingHandler.__init__(self, filename, mode, encoding, delay)
  39. self.max_bytes = max_bytes
  40. self.backup_count = backup_count
  41. self.placeholder = str(len(str(backup_count)))
  42. def doRollover(self):
  43. if self.stream:
  44. self.stream.close()
  45. self.stream = None
  46. if self.backup_count > 0:
  47. for i in range(self.backup_count - 1, 0, -1):
  48. # sfn = "%d_%s" % (i, self.baseFilename)
  49. # dfn = "%d_%s" % (i + 1, self.baseFilename)
  50. sfn = ("%0" + self.placeholder + "d.") % i # '%2d.'%i -> 02
  51. sfn = sfn.join(self.baseFilename.split("."))
  52. dfn = ("%0" + self.placeholder + "d.") % (i + 1)
  53. dfn = dfn.join(self.baseFilename.split("."))
  54. if os.path.exists(sfn):
  55. # print "%s -> %s" % (sfn, dfn)
  56. if os.path.exists(dfn):
  57. os.remove(dfn)
  58. os.rename(sfn, dfn)
  59. dfn = (("%0" + self.placeholder + "d.") % 1).join(self.baseFilename.split("."))
  60. if os.path.exists(dfn):
  61. os.remove(dfn)
  62. # Issue 18940: A file may not have been created if delay is True.
  63. if os.path.exists(self.baseFilename):
  64. os.rename(self.baseFilename, dfn)
  65. if not self.delay:
  66. self.stream = self._open()
  67. def shouldRollover(self, record):
  68. if self.stream is None: # delay was set...
  69. self.stream = self._open()
  70. if self.max_bytes > 0: # are we rolling over?
  71. # print('record >>>> ', record)
  72. msg = "%s\n" % self.format(record)
  73. self.stream.seek(0, 2) # due to non-posix-compliant Windows feature
  74. if self.stream.tell() + len(msg) >= self.max_bytes:
  75. return 1
  76. return 0
  77. def get_logger(
  78. name=None,
  79. path=None,
  80. log_level=None,
  81. is_write_to_console=None,
  82. is_write_to_file=None,
  83. is_send_to_logstash = None,
  84. color=None,
  85. mode=None,
  86. max_bytes=None,
  87. backup_count=None,
  88. encoding=None,
  89. ):
  90. """
  91. @summary: 获取log
  92. ---------
  93. @param name: log名
  94. @param path: log文件存储路径 如 D://xxx.log
  95. @param log_level: log等级 CRITICAL/ERROR/WARNING/INFO/DEBUG
  96. @param is_write_to_console: 是否输出到控制台
  97. @param is_write_to_file: 是否写入到文件 默认否
  98. @param color:是否有颜色
  99. @param mode:写文件模式
  100. @param max_bytes: 每个日志文件的最大字节数
  101. @param backup_count:日志文件保留数量
  102. @param encoding:日志文件编码
  103. ---------
  104. @result:
  105. """
  106. # 加载setting里最新的值
  107. name = name or setting.LOG_NAME
  108. path = path or setting.LOG_PATH
  109. log_level = log_level or setting.LOG_LEVEL
  110. is_write_to_console = (
  111. is_write_to_console
  112. if is_write_to_console is not None
  113. else setting.LOG_IS_WRITE_TO_CONSOLE
  114. )
  115. is_write_to_file = (
  116. is_write_to_file
  117. if is_write_to_file is not None
  118. else setting.LOG_IS_WRITE_TO_FILE
  119. )
  120. is_send_to_logstash = (
  121. is_send_to_logstash
  122. if is_send_to_logstash is not None
  123. else setting.LOG_IS_SEND_TO_LOGSTASH
  124. )
  125. color = color if color is not None else setting.LOG_COLOR
  126. mode = mode or setting.LOG_MODE
  127. max_bytes = max_bytes or setting.LOG_MAX_BYTES
  128. backup_count = backup_count or setting.LOG_BACKUP_COUNT
  129. encoding = encoding or setting.LOG_ENCODING
  130. # logger 配置
  131. name = name.split(os.sep)[-1].split(".")[0] # 取文件名
  132. logger = logging.getLogger(name)
  133. logger.setLevel(log_level)
  134. formatter = logging.Formatter(LOG_FORMAT)
  135. if PRINT_EXCEPTION_DETAILS:
  136. formatter.formatException = lambda exc_info: format_exception(*exc_info)
  137. # 定义一个RotatingFileHandler,最多备份5个日志文件,每个日志文件最大10M
  138. if is_write_to_file:
  139. if path and not os.path.exists(os.path.dirname(path)):
  140. os.makedirs(os.path.dirname(path), exist_ok=True)
  141. rf_handler = RotatingFileHandler(
  142. path,
  143. mode=mode,
  144. max_bytes=max_bytes,
  145. backup_count=backup_count,
  146. encoding=encoding,
  147. )
  148. rf_handler.setFormatter(formatter)
  149. logger.addHandler(rf_handler)
  150. if is_send_to_logstash:
  151. stash_handler = logstash.TCPLogstashHandler(
  152. setting.LOG_STASH_IP, setting.LOG_STASH_PORT, version=1)
  153. logger.addHandler(stash_handler)
  154. if color and is_write_to_console:
  155. loguru_handler = InterceptHandler()
  156. loguru_handler.setFormatter(formatter)
  157. # logging.basicConfig(handlers=[loguru_handler], level=0)
  158. logger.addHandler(loguru_handler)
  159. elif is_write_to_console:
  160. stream_handler = logging.StreamHandler()
  161. stream_handler.stream = sys.stdout
  162. stream_handler.setFormatter(formatter)
  163. logger.addHandler(stream_handler)
  164. _handler_list = []
  165. _handler_name_list = []
  166. # 检查是否存在重复handler
  167. for _handler in logger.handlers:
  168. if str(_handler) not in _handler_name_list:
  169. _handler_name_list.append(str(_handler))
  170. _handler_list.append(_handler)
  171. logger.handlers = _handler_list
  172. return logger
  173. # logging.disable(logging.DEBUG) # 关闭所有log
  174. # 不让打印log的配置
  175. STOP_LOGS = [
  176. # ES
  177. "urllib3.response",
  178. "urllib3.connection",
  179. "elasticsearch.trace",
  180. "requests.packages.urllib3.util",
  181. "requests.packages.urllib3.util.retry",
  182. "urllib3.util",
  183. "requests.packages.urllib3.response",
  184. "requests.packages.urllib3.contrib.pyopenssl",
  185. "requests.packages",
  186. "urllib3.util.retry",
  187. "requests.packages.urllib3.contrib",
  188. "requests.packages.urllib3.connectionpool",
  189. "requests.packages.urllib3.poolmanager",
  190. "urllib3.connectionpool",
  191. "requests.packages.urllib3.connection",
  192. "elasticsearch",
  193. "log_request_fail",
  194. # requests
  195. "requests",
  196. "selenium.webdriver.remote.remote_connection",
  197. "selenium.webdriver.remote",
  198. "selenium.webdriver",
  199. "selenium",
  200. # markdown
  201. "MARKDOWN",
  202. "build_extension",
  203. # newspaper
  204. "calculate_area",
  205. "largest_image_url",
  206. "newspaper.images",
  207. "newspaper",
  208. "Importing",
  209. "PIL",
  210. ]
  211. # 关闭日志打印
  212. for STOP_LOG in STOP_LOGS:
  213. log_level = eval("logging." + setting.OTHERS_LOG_LEVAL)
  214. logging.getLogger(STOP_LOG).setLevel(log_level)
  215. # print(logging.Logger.manager.loggerDict) # 取使用debug模块的name
  216. # 日志级别大小关系为:CRITICAL > ERROR > WARNING > INFO > DEBUG
  217. class Log:
  218. log = None
  219. def __getattr__(self, name):
  220. # 调用log时再初始化,为了加载最新的setting
  221. if self.__class__.log is None:
  222. self.__class__.log = get_logger()
  223. return getattr(self.__class__.log, name)
  224. @property
  225. def debug(self):
  226. return self.__class__.log.debug
  227. @property
  228. def info(self):
  229. return self.__class__.log.info
  230. @property
  231. def warning(self):
  232. return self.__class__.log.warning
  233. @property
  234. def exception(self):
  235. return self.__class__.log.exception
  236. @property
  237. def error(self):
  238. return self.__class__.log.error
  239. @property
  240. def critical(self):
  241. return self.__class__.log.critical
  242. log = Log()