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

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