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 18 kB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. # Copyright 2020 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. """
  16. log module
  17. """
  18. import sys
  19. import os
  20. import stat
  21. import time
  22. import logging
  23. from logging.handlers import RotatingFileHandler
  24. import traceback
  25. import threading
  26. import platform
  27. if platform.system() != "Windows":
  28. import fcntl
  29. __all__ = ['get_level', 'get_log_config']
  30. # The lock for setting up the logger
  31. _setup_logger_lock = threading.Lock()
  32. # When getting the logger, Used to check whether
  33. # the logger already exists
  34. _global_logger = None
  35. # The flag for enable console output
  36. _std_on = '1'
  37. # The flag for disable console output
  38. _std_off = '0'
  39. # Rotating max bytes, default is 50M
  40. _logger_def_max_bytes = '52428800'
  41. # Rotating backup count, default is 30
  42. _logger_def_backup_count = '30'
  43. # The default log level
  44. _logger_def_level = '2'
  45. # Log level name and level mapping
  46. _name_to_level = {
  47. 'ERROR': 40,
  48. 'WARNING': 30,
  49. 'INFO': 20,
  50. 'DEBUG': 10,
  51. }
  52. # GLog level and level name
  53. _gloglevel_to_name = {
  54. '3': 'ERROR',
  55. '2': 'WARNING',
  56. '1': 'INFO',
  57. '0': 'DEBUG',
  58. }
  59. # The mapping of logger configurations to glog configurations
  60. _confmap_dict = {'level': 'GLOG_v', 'console': 'GLOG_logtostderr', 'filepath': 'GLOG_log_dir',
  61. 'maxBytes': 'logger_maxBytes', 'backupCount': 'logger_backupCount',
  62. 'stderr_level': 'GLOG_stderrthreshold'}
  63. class _MultiCompatibleRotatingFileHandler(RotatingFileHandler):
  64. """Inherit RotatingFileHandler for multiprocess compatibility."""
  65. def rolling_rename(self):
  66. """Rolling rename log files and set permission of Log file"""
  67. for i in range(self.backupCount - 1, 0, -1):
  68. sfn = self.rotation_filename("%s.%d" % (self.baseFilename, i))
  69. dfn = self.rotation_filename("%s.%d" % (self.baseFilename, i + 1))
  70. if os.path.exists(sfn):
  71. if os.path.exists(dfn):
  72. os.remove(dfn)
  73. # Modify the permission of Log file
  74. os.chmod(sfn, stat.S_IREAD)
  75. os.rename(sfn, dfn)
  76. def doRollover(self):
  77. """Override doRollover for multiprocess compatibility
  78. and setting permission of Log file"""
  79. if self.stream:
  80. self.stream.close()
  81. self.stream = None
  82. # Attain an exclusive lock with blocking mode by `fcntl` module.
  83. with open(self.baseFilename, 'a') as file_pointer:
  84. if platform.system() != "Windows":
  85. fcntl.lockf(file_pointer.fileno(), fcntl.LOCK_EX)
  86. if self.backupCount > 0:
  87. self.rolling_rename()
  88. dfn = self.rotation_filename(self.baseFilename + ".1")
  89. if os.path.exists(dfn):
  90. os.remove(dfn)
  91. # Modify the permission of Log file
  92. os.chmod(self.baseFilename, stat.S_IREAD)
  93. self.rotate(self.baseFilename, dfn)
  94. with open(self.baseFilename, 'a'):
  95. # Modify the permission of Log file
  96. os.chmod(self.baseFilename, stat.S_IREAD | stat.S_IWRITE)
  97. if not self.delay:
  98. self.stream = self._open()
  99. class _DataFormatter(logging.Formatter):
  100. """Log formatter"""
  101. def __init__(self, sub_module, fmt=None, **kwargs):
  102. """
  103. Initialization of logFormatter.
  104. Args:
  105. sub_module (str): The submodule name.
  106. fmt (str): Specified format pattern. Default: None.
  107. """
  108. super(_DataFormatter, self).__init__(fmt=fmt, **kwargs)
  109. self.sub_module = sub_module.upper()
  110. def formatTime(self, record, datefmt=None):
  111. """
  112. Override formatTime for uniform format %Y-%m-%d-%H:%M:%S.SSS.SSS
  113. Args:
  114. record (str): Log record.
  115. datefmt (str): Date format.
  116. Returns:
  117. str, formatted timestamp.
  118. """
  119. created_time = self.converter(record.created)
  120. if datefmt:
  121. return time.strftime(datefmt, created_time)
  122. timestamp = time.strftime('%Y-%m-%d-%H:%M:%S', created_time)
  123. msecs = str(round(record.msecs * 1000))
  124. # Format the time stamp
  125. return f'{timestamp}.{msecs[:3]}.{msecs[3:]}'
  126. def format(self, record):
  127. """
  128. Apply log format with specified pattern.
  129. Args:
  130. record (str): Format pattern.
  131. Returns:
  132. str, formatted log content according to format pattern.
  133. """
  134. # NOTICE: when the Installation directory of mindspore changed,
  135. # ms_home_path must be changed
  136. ms_install_home_path = 'mindspore'
  137. idx = record.pathname.rfind(ms_install_home_path)
  138. if idx >= 0:
  139. # Get the relative path of the file
  140. record.filepath = record.pathname[idx:]
  141. else:
  142. record.filepath = record.pathname
  143. record.sub_module = self.sub_module
  144. return super().format(record)
  145. def _get_logger():
  146. """
  147. Get logger instance.
  148. Returns:
  149. Logger, a logger.
  150. """
  151. if _global_logger:
  152. return _global_logger
  153. kwargs = _get_env_config()
  154. _verify_config(kwargs)
  155. logger = _setup_logger(_adapt_cfg(kwargs))
  156. return logger
  157. def _adapt_cfg(kwargs):
  158. """
  159. Glog configurations converted to logger configurations.
  160. Args:
  161. kwargs (dict): The dictionary of log configurations.
  162. - console (str): Whether to output log to stdout.
  163. - level (str): Log level.
  164. - filepath (str): The path for saving logs, if console is false, a file path must be assigned.
  165. - maxBytes (str): The Maximum value of a log file for rotating, only valid if console is false.
  166. - backupCount (str): The count of rotating backup log files, only valid if console is false.
  167. Returns:
  168. Dict, the input parameter dictionary.
  169. """
  170. kwargs['level'] = _gloglevel_to_name.get(kwargs.get('level', _logger_def_level))
  171. kwargs['stderr_level'] = _gloglevel_to_name.get(kwargs.get('stderr_level', _logger_def_level))
  172. kwargs['console'] = not kwargs.get('console') == _std_off
  173. kwargs['maxBytes'] = int(kwargs.get('maxBytes', _logger_def_max_bytes))
  174. kwargs['backupCount'] = int(kwargs.get('backupCount', _logger_def_backup_count))
  175. return kwargs
  176. def info(msg, *args, **kwargs):
  177. """
  178. Log a message with severity 'INFO' on the MindSpore logger.
  179. Examples:
  180. >>> from mindspore import log as logger
  181. >>> logger.info("The arg(%s) is: %r", name, arg)
  182. """
  183. _get_logger().info(msg, *args, **kwargs)
  184. def debug(msg, *args, **kwargs):
  185. """
  186. Log a message with severity 'DEBUG' on the MindSpore logger.
  187. Examples:
  188. >>> from mindspore import log as logger
  189. >>> logger.debug("The arg(%s) is: %r", name, arg)
  190. """
  191. _get_logger().debug(msg, *args, **kwargs)
  192. def error(msg, *args, **kwargs):
  193. """Log a message with severity 'ERROR' on the MindSpore logger."""
  194. _get_logger().error(msg, *args, **kwargs)
  195. def warning(msg, *args, **kwargs):
  196. """Log a message with severity 'WARNING' on the MindSpore logger."""
  197. _get_logger().warning(msg, *args, **kwargs)
  198. def get_level():
  199. """
  200. Get the logger level.
  201. Returns:
  202. str, the Log level includes 3(ERROR), 2(WARNING), 1(INFO), 0(DEBUG).
  203. Examples:
  204. >>> import os
  205. >>> os.environ['GLOG_v'] = '0'
  206. >>> from mindspore import log as logger
  207. >>> logger.get_level()
  208. """
  209. # level and glog level mapping dictionary
  210. level_to_glog_level = dict(zip(_name_to_level.values(), _gloglevel_to_name.keys()))
  211. return level_to_glog_level.get(_get_logger().getEffectiveLevel())
  212. def _get_formatter():
  213. """
  214. Get the string of log formatter.
  215. Returns:
  216. str, the string of log formatter.
  217. """
  218. formatter = '[%(levelname)s] %(sub_module)s(%(process)d:' \
  219. '%(thread)d,%(processName)s):%(asctime)s ' \
  220. '[%(filepath)s:%(lineno)d] %(message)s'
  221. return formatter
  222. def _get_env_config():
  223. """
  224. Get configurations from environment variables.
  225. Returns:
  226. Dict, the dictionary of configurations.
  227. """
  228. config_dict = {}
  229. for key, env_value in _confmap_dict.items():
  230. value = os.environ.get(env_value)
  231. if value:
  232. config_dict[key] = value.strip()
  233. return config_dict
  234. def _verify_config(kwargs):
  235. """
  236. Verify log configurations.
  237. Args:
  238. kwargs (dict): The dictionary of log configurations.
  239. - console (str): Whether to output log to stdout.
  240. - level (str): Log level.
  241. - filepath (str): The path for saving logs, if console is false, a file path must be assigned.
  242. - maxBytes (str): The Maximum value of a log file for rotating, only valid if console is false.
  243. - backupCount (str): The count of rotating backup log files, only valid if console is false.
  244. """
  245. # Check the input value of level
  246. level = kwargs.get('level', None)
  247. if level is not None:
  248. _verify_level(level)
  249. # Check the input value of stderr_level
  250. level = kwargs.get('stderr_level', None)
  251. if level is not None:
  252. _verify_level(level)
  253. # Check the input value of console
  254. console = kwargs.get('console', None)
  255. file_path = kwargs.get('filepath', None)
  256. if console is not None:
  257. if not console.isdigit() or console not in (_std_off, _std_on):
  258. raise ValueError(f'Incorrect value, The value of {_confmap_dict["console"]} must be 0 or 1,'
  259. f' Output log to console, configure to 1.')
  260. if console == _std_off and not file_path:
  261. raise ValueError(f'When {_confmap_dict["console"]} is set to 0, The directory of '
  262. f'saving log must be set, {_confmap_dict["filepath"]} cannot be empty.')
  263. # Check the input value of filepath
  264. if console == _std_off and file_path is not None:
  265. file_real_path = os.path.realpath(file_path)
  266. if not os.path.exists(file_real_path):
  267. raise ValueError(f'The file path does not exist. '
  268. f'{_confmap_dict["filepath"]}:{file_path}')
  269. # Check the input value of maxBytes
  270. max_bytes = kwargs.get('maxBytes', None)
  271. if console == _std_off and max_bytes is not None:
  272. if not max_bytes.isdigit():
  273. raise ValueError(f'Incorrect value, The value of {_confmap_dict["maxBytes"]} must be positive integer. '
  274. f'{_confmap_dict["maxBytes"]}:{max_bytes}')
  275. # Check the input value of backupCount
  276. backup_count = kwargs.get('backupCount', None)
  277. if console == _std_off and backup_count is not None:
  278. if not backup_count.isdigit():
  279. raise ValueError(f'Incorrect value, The value of {_confmap_dict["backupCount"]} must be positive '
  280. f'integer. {_confmap_dict["backupCount"]}:{backup_count}')
  281. def _verify_level(level):
  282. """
  283. Verify log level.
  284. Args:
  285. level (str): The log level.
  286. """
  287. level_name = _gloglevel_to_name.get(level, None)
  288. # Check the value of input level
  289. if level_name not in _name_to_level:
  290. raise ValueError(f'Incorrect log level:{level}, Please check the configuration of GLOG_v or '
  291. f'GLOG_stderrthreshold, desired log level :{_gloglevel_to_name}')
  292. def get_log_config():
  293. """
  294. Get logger configurations.
  295. Returns:
  296. Dict, the dictionary of logger configurations.
  297. Examples:
  298. >>> import os
  299. >>> os.environ['GLOG_v'] = '1'
  300. >>> os.environ['GLOG_logtostderr'] = '0'
  301. >>> os.environ['GLOG_log_dir'] = '/var/log'
  302. >>> os.environ['logger_maxBytes'] = '5242880'
  303. >>> os.environ['logger_backupCount'] = '10'
  304. >>> from mindspore import log as logger
  305. >>> logger.get_log_config()
  306. """
  307. logger = _get_logger()
  308. handler = logger.handlers[0]
  309. config_dict = {}
  310. config_dict['GLOG_v'] = get_level()
  311. config_dict['GLOG_logtostderr'] = _std_on
  312. if handler.name == 'FileHandler':
  313. config_dict['GLOG_logtostderr'] = _std_off
  314. # Separating file path and name
  315. file_path_and_name = os.path.split(handler.baseFilename)
  316. config_dict['GLOG_log_dir'] = file_path_and_name[0]
  317. config_dict['logger_maxBytes'] = handler.maxBytes
  318. config_dict['logger_backupCount'] = handler.backupCount
  319. handler_stderr = logger.handlers[1]
  320. # level and glog level mapping dictionary
  321. level_to_glog_level = dict(zip(_name_to_level.values(), _gloglevel_to_name.keys()))
  322. config_dict['GLOG_stderrthreshold'] = level_to_glog_level.get(handler_stderr.level)
  323. return config_dict
  324. def _clear_handler(logger):
  325. """Clear the handlers that has been set, avoid repeated loading"""
  326. for handler in logger.handlers:
  327. logger.removeHandler(handler)
  328. def _find_caller(stack_info=False, stacklevel=1):
  329. """
  330. Find the stack frame of the caller.
  331. Override findCaller on the logger, Support for getting log record.
  332. Find the stack frame of the caller so that we can note the source
  333. file name, function name and line number.
  334. Args:
  335. stack_info (bool): If the value is true, print stack information to the log. Default: False.
  336. Returns:
  337. tuple, the tuple of the frame data.
  338. """
  339. f = sys._getframe(3)
  340. sinfo = None
  341. # log_file is used to check caller stack frame
  342. log_file = os.path.normcase(f.f_code.co_filename)
  343. f = f.f_back
  344. rv = "(unknown file)", 0, "(unknown function)", None
  345. while f:
  346. co = f.f_code
  347. filename = os.path.normcase(co.co_filename)
  348. if log_file == filename:
  349. f = f.f_back
  350. continue
  351. if stack_info:
  352. sinfo = _get_stack_info(f)
  353. rv = (co.co_filename, f.f_lineno, co.co_name, sinfo)
  354. break
  355. return rv
  356. def _get_stack_info(frame):
  357. """
  358. Get the stack information.
  359. Args:
  360. frame(frame): the frame requiring information.
  361. Returns:
  362. str, the string of the stack information.
  363. """
  364. sinfo = None
  365. stack_prefix = 'Stack (most recent call last):\n'
  366. sinfo = stack_prefix + "".join(traceback.format_stack(frame))
  367. return sinfo
  368. def _setup_logger(kwargs):
  369. """
  370. Set up the logger.
  371. Args:
  372. kwargs (dict): The dictionary of log configurations.
  373. - console (bool): Whether to output log to stdout. Default: True.
  374. - level (str): Log level. Default: WARNING.
  375. - filepath (str): The path for saving logs, if console is false, a file path must be assigned.
  376. - maxBytes (int): The Maximum value of a log file for rotating, only valid if console is false.
  377. Default: 52428800.
  378. - backupCount (int): The count of rotating backup log files, only valid if console is false. Default: 30.
  379. Returns:
  380. Logger, well-configured logger.
  381. """
  382. # The name of Submodule
  383. sub_module = 'ME'
  384. # The name of Base log file
  385. pid = str(os.getpid())
  386. log_name = 'mindspore.log.' + pid
  387. global _global_logger
  388. _setup_logger_lock.acquire()
  389. try:
  390. if _global_logger:
  391. return _global_logger
  392. logger = logging.getLogger(name=f'{sub_module}.{log_name}')
  393. # Override findCaller on the logger, Support for getting log record
  394. logger.findCaller = _find_caller
  395. console = kwargs.get('console', True)
  396. # Set log level
  397. logger.setLevel(kwargs.get('level', logging.WARNING))
  398. # Set "propagate" attribute to False, stop searching up the hierarchy,
  399. # avoid to load the handler of the root logger
  400. logger.propagate = False
  401. # Get the formatter for handler
  402. formatter = _get_formatter()
  403. # Clean up handle to avoid repeated loading
  404. _clear_handler(logger)
  405. # Set streamhandler for the console appender
  406. if console:
  407. console_handler = logging.StreamHandler(sys.stderr)
  408. console_handler.name = 'StreamHandler'
  409. console_handler.formatter = _DataFormatter(sub_module, formatter)
  410. logger.addHandler(console_handler)
  411. # Set rotatingFileHandler for the file appender
  412. else:
  413. # filepath cannot be null, checked in function _verify_config ()
  414. logfile_dir = os.path.realpath(kwargs.get('filepath'))
  415. file_name = f'{logfile_dir}/{log_name}'
  416. logfile_handler = _MultiCompatibleRotatingFileHandler(
  417. filename=file_name,
  418. # Rotating max bytes, default is 50M
  419. maxBytes=kwargs.get('maxBytes', _logger_def_max_bytes),
  420. # Rotating backup count, default is 30
  421. backupCount=kwargs.get('backupCount', _logger_def_backup_count),
  422. encoding='utf8'
  423. )
  424. logfile_handler.name = 'FileHandler'
  425. logfile_handler.formatter = _DataFormatter(sub_module, formatter)
  426. logger.addHandler(logfile_handler)
  427. # Write the file and output warning and error logs to stderr
  428. console_handler = logging.StreamHandler(sys.stderr)
  429. console_handler.name = 'StreamHandler'
  430. console_handler.formatter = _DataFormatter(sub_module, formatter)
  431. console_handler.setLevel(kwargs.get('stderr_level', logging.WARNING))
  432. logger.addHandler(console_handler)
  433. _global_logger = logger
  434. finally:
  435. _setup_logger_lock.release()
  436. return _global_logger