You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

log.py 8.2 kB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. # Copyright 2019 Huawei Technologies Co., Ltd
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. # ============================================================================
  15. """Log module."""
  16. import sys
  17. import os
  18. import stat
  19. import time
  20. import fcntl
  21. import logging
  22. from logging.handlers import RotatingFileHandler
  23. from mindinsight.conf import settings
  24. from mindinsight.utils.exceptions import MindInsightException
  25. from mindinsight.utils.constant import GeneralErrors
  26. class MultiCompatibleRotatingFileHandler(RotatingFileHandler):
  27. """Inherit RotatingFileHandler for multiprocess compatibility."""
  28. def rolling_rename(self):
  29. """Rolling rename log files."""
  30. for i in range(self.backupCount - 1, 0, -1):
  31. sfn = self.rotation_filename("%s.%d" % (self.baseFilename, i))
  32. dfn = self.rotation_filename("%s.%d" % (self.baseFilename, i + 1))
  33. if os.path.exists(sfn):
  34. if os.path.exists(dfn):
  35. os.remove(dfn)
  36. os.chmod(sfn, stat.S_IRUSR)
  37. os.rename(sfn, dfn)
  38. def doRollover(self):
  39. """Do a rollover, as described in __init__()."""
  40. if self.stream:
  41. self.stream.close()
  42. self.stream = None
  43. # Attain an exclusive lock with blocking mode by `fcntl` module.
  44. with os.open(self.baseFilename, os.O_APPEND | os.O_CREAT, mode=stat.S_IRUSR | stat.S_IWUSR) as fd:
  45. fcntl.lockf(fd, fcntl.LOCK_EX)
  46. try:
  47. if self.backupCount > 0:
  48. self.rolling_rename()
  49. # dfn stands for destinated file name according to source codes in RotatingFileHandler.doRollover()
  50. dfn = self.rotation_filename(self.baseFilename + ".1")
  51. if os.path.exists(dfn):
  52. os.remove(dfn)
  53. os.chmod(self.baseFilename, stat.S_IRUSR)
  54. self.rotate(self.baseFilename, dfn)
  55. fd = os.open(self.baseFilename, os.O_APPEND | os.O_CREAT, mode=stat.S_IRUSR | stat.S_IWUSR)
  56. os.close(fd)
  57. if not self.delay:
  58. self.stream = self._open()
  59. except FileNotFoundError:
  60. # Suppress this exception for concurrency.
  61. pass
  62. def _open(self):
  63. """Open the current base file with the (original) mode and encoding."""
  64. new_log = super()._open()
  65. os.chmod(self.baseFilename, stat.S_IREAD | stat.S_IWRITE)
  66. return new_log
  67. class MindInsightFormatter(logging.Formatter):
  68. """
  69. MindInsight formatter.
  70. """
  71. def __init__(self, sub_module, fmt=None, **kwargs):
  72. """
  73. Initialization of SlogFormatter.
  74. Args:
  75. sub_module (str): Sub module name, type is string.
  76. fmt (str): Specified format pattern, type is string.
  77. Returns:
  78. Formatter, instance of SlogFormatter.
  79. """
  80. super(MindInsightFormatter, self).__init__(fmt=fmt, **kwargs)
  81. self.sub_module = sub_module.upper()
  82. def formatTime(self, record, datefmt=None):
  83. """
  84. Overwrite for uniform format %Y-%m-%d-%H:%M:%S.SSS.SSS
  85. Args:
  86. record (LogRecord): Log record.
  87. datefmt (str): Date format, type is string.
  88. Returns:
  89. str, formatted timestamp, type is string.
  90. """
  91. created_time = self.converter(record.created)
  92. if datefmt:
  93. return time.strftime(datefmt, created_time)
  94. timestamp = time.strftime('%Y-%m-%d-%H:%M:%S', created_time)
  95. msecs = str(round(record.msecs * 1000)).zfill(6)
  96. return '{}.{}.{}'.format(timestamp, msecs[:3], msecs[3:])
  97. def formatMessage(self, record):
  98. """Escape the message before format."""
  99. record.message = ' '.join(record.message.split())
  100. return super().formatMessage(record)
  101. def format(self, record):
  102. """
  103. Apply log format with specified pattern.
  104. Args:
  105. record (str): Format pattern, type is string.
  106. Returns:
  107. str, formatted log content according to format pattern, type if string.
  108. """
  109. record.filepath = record.pathname[__file__.rfind('mindinsight'):]
  110. record.sub_module = self.sub_module
  111. return super().format(record)
  112. def get_logger(sub_module, log_name):
  113. """
  114. Get logger by name and sub module.
  115. Args:
  116. sub_module (str): Sub module name, type is string.
  117. log_name (str): Log file name, type is string.
  118. Returns:
  119. Logger, logger instance named by sub_module and log_name.
  120. """
  121. return logging.getLogger(name='{}.{}'.format(sub_module, log_name))
  122. def setup_logger(sub_module, log_name, **kwargs):
  123. """
  124. Setup logger with sub module name and log file name.
  125. Args:
  126. sub_module (str): Sub module name, also for sub directory under logroot.
  127. log_name (str): Log name, also for log filename.
  128. console (bool): Whether to output log to stdout. Default: False.
  129. logfile (bool): Whether to output log to disk. Default: True.
  130. level (Enum): Log level. Default: INFO.
  131. formatter (str): Log format.
  132. propagate (bool): Whether to enable propagate feature. Default: False.
  133. maxBytes (int): Rotating max bytes. Default: 50M.
  134. backupCount (int): Rotating backup count. Default: 30.
  135. Returns:
  136. Logger, well-configured logger instance.
  137. Examples:
  138. >>> from mindinsight.utils.log import setup_logger
  139. >>> logger = setup_logger('datavisual', 'flask.request', level=logging.DEBUG)
  140. >>> from mindinsight.utils.log import get_logger
  141. >>> logger = get_logger('datavisual', 'flask.request')
  142. >>> import logging
  143. >>> logger = logging.getLogger('datavisual.flask.request')
  144. """
  145. if kwargs.get('sub_log_name', False):
  146. logger = get_logger(sub_module, kwargs['sub_log_name'])
  147. else:
  148. logger = get_logger(sub_module, log_name)
  149. if logger.hasHandlers():
  150. return logger
  151. level = kwargs.get('level', settings.LOG_LEVEL)
  152. formatter = kwargs.get('formatter', None)
  153. propagate = kwargs.get('propagate', False)
  154. logger.setLevel(level)
  155. logger.propagate = propagate
  156. if not formatter:
  157. formatter = settings.LOG_FORMAT
  158. if kwargs.get('console', False):
  159. console_handler = logging.StreamHandler(sys.stdout)
  160. console_handler.formatter = MindInsightFormatter(sub_module, formatter)
  161. logger.addHandler(console_handler)
  162. if kwargs.get('logfile', True):
  163. max_bytes = kwargs.get('maxBytes', settings.LOG_ROTATING_MAXBYTES)
  164. if not isinstance(max_bytes, int) or not max_bytes > 0:
  165. raise MindInsightException(GeneralErrors.PARAM_VALUE_ERROR,
  166. 'maxBytes should be int type and > 0.')
  167. backup_count = kwargs.get('backupCount',
  168. settings.LOG_ROTATING_BACKUPCOUNT)
  169. if not isinstance(backup_count, int) or not backup_count > 0:
  170. raise MindInsightException(GeneralErrors.PARAM_VALUE_ERROR,
  171. 'backupCount should be int type and > 0.')
  172. logfile_dir = os.path.join(settings.WORKSPACE, 'log', sub_module)
  173. os.makedirs(logfile_dir, mode=stat.S_IRWXU, exist_ok=True)
  174. logfile_handler = MultiCompatibleRotatingFileHandler(
  175. filename=os.path.join(logfile_dir, '{}.{}.log'.format(log_name, settings.PORT)),
  176. maxBytes=max_bytes,
  177. backupCount=backup_count,
  178. encoding='utf8'
  179. )
  180. logfile_handler.formatter = MindInsightFormatter(sub_module, formatter)
  181. logger.addHandler(logfile_handler)
  182. return logger