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

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