You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
521 lines
17 KiB
521 lines
17 KiB
# Copyright 2020 Huawei Technologies Co., Ltd
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ============================================================================
|
|
"""
|
|
log module
|
|
"""
|
|
import sys
|
|
import os
|
|
import stat
|
|
import time
|
|
import logging
|
|
from logging.handlers import RotatingFileHandler
|
|
import traceback
|
|
import threading
|
|
import platform
|
|
if platform.system() != "Windows":
|
|
import fcntl
|
|
|
|
__all__ = ['get_level', 'get_log_config']
|
|
|
|
# The lock for setting up the logger
|
|
_setup_logger_lock = threading.Lock()
|
|
|
|
# When getting the logger, Used to check whether
|
|
# the logger already exists
|
|
_global_logger = None
|
|
|
|
# The flag for enable console output
|
|
_std_on = '1'
|
|
# The flag for disable console output
|
|
_std_off = '0'
|
|
# Rotating max bytes, default is 50M
|
|
_logger_def_max_bytes = '52428800'
|
|
# Rotating backup count, default is 30
|
|
_logger_def_backup_count = '30'
|
|
# The default log level
|
|
_logger_def_level = '2'
|
|
|
|
# Log level name and level mapping
|
|
_name_to_level = {
|
|
'ERROR': 40,
|
|
'WARNING': 30,
|
|
'INFO': 20,
|
|
'DEBUG': 10,
|
|
}
|
|
|
|
# GLog level and level name
|
|
_gloglevel_to_name = {
|
|
'3': 'ERROR',
|
|
'2': 'WARNING',
|
|
'1': 'INFO',
|
|
'0': 'DEBUG',
|
|
}
|
|
|
|
# The mapping of logger configurations to glog configurations
|
|
_confmap_dict = {'level': 'GLOG_v', 'console': 'GLOG_logtostderr', 'filepath': 'GLOG_log_dir',
|
|
'maxBytes': 'logger_maxBytes', 'backupCount': 'logger_backupCount'}
|
|
|
|
|
|
class _MultiCompatibleRotatingFileHandler(RotatingFileHandler):
|
|
"""Inherit RotatingFileHandler for multiprocess compatibility."""
|
|
|
|
def rolling_rename(self):
|
|
"""Rolling rename log files and set permission of Log file"""
|
|
for i in range(self.backupCount - 1, 0, -1):
|
|
sfn = self.rotation_filename("%s.%d" % (self.baseFilename, i))
|
|
dfn = self.rotation_filename("%s.%d" % (self.baseFilename, i + 1))
|
|
if os.path.exists(sfn):
|
|
if os.path.exists(dfn):
|
|
os.remove(dfn)
|
|
# Modify the permission of Log file
|
|
os.chmod(sfn, stat.S_IREAD)
|
|
os.rename(sfn, dfn)
|
|
|
|
def doRollover(self):
|
|
"""Override doRollover for multiprocess compatibility
|
|
and setting permission of Log file"""
|
|
if self.stream:
|
|
self.stream.close()
|
|
self.stream = None
|
|
|
|
# Attain an exclusive lock with bloking mode by `fcntl` module.
|
|
with open(self.baseFilename, 'a') as file_pointer:
|
|
if platform.system() != "Windows":
|
|
fcntl.lockf(file_pointer.fileno(), fcntl.LOCK_EX)
|
|
|
|
if self.backupCount > 0:
|
|
self.rolling_rename()
|
|
|
|
dfn = self.rotation_filename(self.baseFilename + ".1")
|
|
if os.path.exists(dfn):
|
|
os.remove(dfn)
|
|
# Modify the permission of Log file
|
|
os.chmod(self.baseFilename, stat.S_IREAD)
|
|
self.rotate(self.baseFilename, dfn)
|
|
|
|
with open(self.baseFilename, 'a'):
|
|
# Modify the permission of Log file
|
|
os.chmod(self.baseFilename, stat.S_IREAD | stat.S_IWRITE)
|
|
|
|
if not self.delay:
|
|
self.stream = self._open()
|
|
|
|
|
|
class _DataFormatter(logging.Formatter):
|
|
"""Log formatter"""
|
|
|
|
def __init__(self, sub_module, fmt=None, **kwargs):
|
|
"""
|
|
Initialization of logFormatter.
|
|
|
|
Args:
|
|
sub_module (str): The submodule name.
|
|
fmt (str): Specified format pattern. Default: None.
|
|
"""
|
|
super(_DataFormatter, self).__init__(fmt=fmt, **kwargs)
|
|
self.sub_module = sub_module.upper()
|
|
|
|
def formatTime(self, record, datefmt=None):
|
|
"""
|
|
Override formatTime for uniform format %Y-%m-%d-%H:%M:%S.SSS.SSS
|
|
|
|
Args:
|
|
record (str): Log record.
|
|
datefmt (str): Date format.
|
|
|
|
Returns:
|
|
str, formatted timestamp.
|
|
"""
|
|
created_time = self.converter(record.created)
|
|
if datefmt:
|
|
return time.strftime(datefmt, created_time)
|
|
|
|
timestamp = time.strftime('%Y-%m-%d-%H:%M:%S', created_time)
|
|
msecs = str(round(record.msecs * 1000))
|
|
# Format the time stamp
|
|
return f'{timestamp}.{msecs[:3]}.{msecs[3:]}'
|
|
|
|
def format(self, record):
|
|
"""
|
|
Apply log format with specified pattern.
|
|
|
|
Args:
|
|
record (str): Format pattern.
|
|
|
|
Returns:
|
|
str, formatted log content according to format pattern.
|
|
"""
|
|
# NOTICE: when the Installation directory of mindspore changed,
|
|
# ms_home_path must be changed
|
|
ms_install_home_path = 'mindspore'
|
|
idx = record.pathname.rfind(ms_install_home_path)
|
|
if idx >= 0:
|
|
# Get the relative path of the file
|
|
record.filepath = record.pathname[idx:]
|
|
else:
|
|
record.filepath = record.pathname
|
|
record.sub_module = self.sub_module
|
|
return super().format(record)
|
|
|
|
|
|
def _get_logger():
|
|
"""
|
|
Get logger instance.
|
|
|
|
Returns:
|
|
Logger, a logger.
|
|
"""
|
|
if _global_logger:
|
|
return _global_logger
|
|
|
|
kwargs = _get_env_config()
|
|
_verify_config(kwargs)
|
|
logger = _setup_logger(_adapt_cfg(kwargs))
|
|
return logger
|
|
|
|
|
|
def _adapt_cfg(kwargs):
|
|
"""
|
|
Glog configurations converted to logger configurations.
|
|
|
|
Args:
|
|
kwargs (dict): The dictionary of log configurations.
|
|
|
|
- console (str): Whether to output log to stdout.
|
|
- level (str): Log level.
|
|
- filepath (str): The path for saving logs, if console is false, a file path must be assigned.
|
|
- maxBytes (str): The Maximum value of a log file for rotating, only valid if console is false.
|
|
- backupCount (str): The count of rotating backup log files, only valid if console is false.
|
|
|
|
Returns:
|
|
Dict, the input parameter dictionary.
|
|
"""
|
|
kwargs['level'] = _gloglevel_to_name.get(kwargs.get('level', _logger_def_level))
|
|
kwargs['console'] = not kwargs.get('console') == _std_off
|
|
kwargs['maxBytes'] = int(kwargs.get('maxBytes', _logger_def_max_bytes))
|
|
kwargs['backupCount'] = int(kwargs.get('backupCount', _logger_def_backup_count))
|
|
return kwargs
|
|
|
|
|
|
def info(msg, *args, **kwargs):
|
|
"""
|
|
Log a message with severity 'INFO' on the MindSpore logger.
|
|
|
|
Examples:
|
|
>>> from mindspore import log as logger
|
|
>>> logger.info("The arg(%s) is: %r", name, arg)
|
|
"""
|
|
_get_logger().info(msg, *args, **kwargs)
|
|
|
|
|
|
def debug(msg, *args, **kwargs):
|
|
"""
|
|
Log a message with severity 'DEBUG' on the MindSpore logger.
|
|
|
|
Examples:
|
|
>>> from mindspore import log as logger
|
|
>>> logger.debug("The arg(%s) is: %r", name, arg)
|
|
"""
|
|
_get_logger().debug(msg, *args, **kwargs)
|
|
|
|
|
|
def error(msg, *args, **kwargs):
|
|
"""Log a message with severity 'ERROR' on the MindSpore logger."""
|
|
_get_logger().error(msg, *args, **kwargs)
|
|
|
|
|
|
def warning(msg, *args, **kwargs):
|
|
"""Log a message with severity 'WARNING' on the MindSpore logger."""
|
|
_get_logger().warning(msg, *args, **kwargs)
|
|
|
|
|
|
def get_level():
|
|
"""
|
|
Get the logger level.
|
|
|
|
Returns:
|
|
str, the Log level includes 3(ERROR), 2(WARNING), 1(INFO), 0(DEBUG).
|
|
|
|
Examples:
|
|
>>> import os
|
|
>>> os.environ['GLOG_v'] = '0'
|
|
>>> from mindspore import log as logger
|
|
>>> logger.get_level()
|
|
"""
|
|
# level and glog level mapping dictionary
|
|
level_to_glog_level = dict(zip(_name_to_level.values(), _gloglevel_to_name.keys()))
|
|
|
|
return level_to_glog_level.get(_get_logger().getEffectiveLevel())
|
|
|
|
|
|
def _get_formatter():
|
|
"""
|
|
Get the string of log formatter.
|
|
|
|
Returns:
|
|
str, the string of log formatter.
|
|
"""
|
|
formatter = '[%(levelname)s] %(sub_module)s(%(process)d:' \
|
|
'%(thread)d,%(processName)s):%(asctime)s ' \
|
|
'[%(filepath)s:%(lineno)d] %(message)s'
|
|
return formatter
|
|
|
|
|
|
def _get_env_config():
|
|
"""
|
|
Get configurations from environment variables.
|
|
|
|
Returns:
|
|
Dict, the dictionary of configurations.
|
|
"""
|
|
config_dict = {}
|
|
for key, env_value in _confmap_dict.items():
|
|
value = os.environ.get(env_value)
|
|
if value:
|
|
config_dict[key] = value.strip()
|
|
return config_dict
|
|
|
|
|
|
def _verify_config(kwargs):
|
|
"""
|
|
Verify log configurations.
|
|
|
|
Args:
|
|
kwargs (dict): The dictionary of log configurations.
|
|
|
|
- console (str): Whether to output log to stdout.
|
|
- level (str): Log level.
|
|
- filepath (str): The path for saving logs, if console is false, a file path must be assigned.
|
|
- maxBytes (str): The Maximum value of a log file for rotating, only valid if console is false.
|
|
- backupCount (str): The count of rotating backup log files, only valid if console is false.
|
|
"""
|
|
# Check the input value of level
|
|
level = kwargs.get('level', None)
|
|
if level is not None:
|
|
_verify_level(level)
|
|
|
|
# Check the input value of console
|
|
console = kwargs.get('console', None)
|
|
file_path = kwargs.get('filepath', None)
|
|
|
|
if console is not None:
|
|
if not console.isdigit() or console not in (_std_off, _std_on):
|
|
raise ValueError(f'Incorrect value, The value of {_confmap_dict["console"]} must be 0 or 1,'
|
|
f' Output log to console, configure to 1.')
|
|
|
|
if console == _std_off and not file_path:
|
|
raise ValueError(f'When {_confmap_dict["console"]} is set to 0, The directory of '
|
|
f'saving log must be set, {_confmap_dict["filepath"]} cannot be empty.')
|
|
|
|
# Check the input value of filepath
|
|
if console == _std_off and file_path is not None:
|
|
file_real_path = os.path.realpath(file_path)
|
|
if not os.path.exists(file_real_path):
|
|
raise ValueError(f'The file path does not exist. '
|
|
f'{_confmap_dict["filepath"]}:{file_path}')
|
|
|
|
# Check the input value of maxBytes
|
|
max_bytes = kwargs.get('maxBytes', None)
|
|
if console == _std_off and max_bytes is not None:
|
|
if not max_bytes.isdigit():
|
|
raise ValueError(f'Incorrect value, The value of {_confmap_dict["maxBytes"]} must be positive integer. '
|
|
f'{_confmap_dict["maxBytes"]}:{max_bytes}')
|
|
|
|
# Check the input value of backupCount
|
|
backup_count = kwargs.get('backupCount', None)
|
|
if console == _std_off and backup_count is not None:
|
|
if not backup_count.isdigit():
|
|
raise ValueError(f'Incorrect value, The value of {_confmap_dict["backupCount"]} must be positive '
|
|
f'integer. {_confmap_dict["backupCount"]}:{backup_count}')
|
|
|
|
|
|
def _verify_level(level):
|
|
"""
|
|
Verify log level.
|
|
|
|
Args:
|
|
level (str): The log level.
|
|
"""
|
|
level_name = _gloglevel_to_name.get(level, None)
|
|
|
|
# Check the value of input level
|
|
if level_name not in _name_to_level:
|
|
raise ValueError(f'Incorrect log level:{level}, Please check the log level configuration, '
|
|
f'desired log level :{_gloglevel_to_name}')
|
|
|
|
|
|
def get_log_config():
|
|
"""
|
|
Get logger configurations.
|
|
|
|
Returns:
|
|
Dict, the dictionary of logger configurations.
|
|
|
|
Examples:
|
|
>>> import os
|
|
>>> os.environ['GLOG_v'] = '1'
|
|
>>> os.environ['GLOG_logtostderr'] = '0'
|
|
>>> os.environ['GLOG_log_dir'] = '/var/log/mindspore'
|
|
>>> os.environ['logger_maxBytes'] = '5242880'
|
|
>>> os.environ['logger_backupCount'] = '10'
|
|
>>> from mindspore import log as logger
|
|
>>> logger.get_log_config()
|
|
"""
|
|
logger = _get_logger()
|
|
handler = logger.handlers[0]
|
|
config_dict = {}
|
|
config_dict['GLOG_v'] = get_level()
|
|
config_dict['GLOG_logtostderr'] = _std_on
|
|
|
|
if handler.name == 'FileHandler':
|
|
config_dict['GLOG_logtostderr'] = _std_off
|
|
# Separating file path and name
|
|
file_path_and_name = os.path.split(handler.baseFilename)
|
|
config_dict['GLOG_log_dir'] = file_path_and_name[0]
|
|
config_dict['logger_maxBytes'] = handler.maxBytes
|
|
config_dict['logger_backupCount'] = handler.backupCount
|
|
return config_dict
|
|
|
|
|
|
def _clear_handler(logger):
|
|
"""Clear the handlers that has been set, avoid repeated loading"""
|
|
for handler in logger.handlers:
|
|
logger.removeHandler(handler)
|
|
|
|
|
|
def _find_caller(stack_info=False):
|
|
"""
|
|
Find the stack frame of the caller.
|
|
|
|
Override findCaller on the logger, Support for getting log record.
|
|
Find the stack frame of the caller so that we can note the source
|
|
file name, function name and line number.
|
|
|
|
Args:
|
|
stack_info (bool): If the value is true, print stack information to the log. Default: False.
|
|
|
|
Returns:
|
|
tuple, the tuple of the frame data.
|
|
"""
|
|
f = sys._getframe(3)
|
|
sinfo = None
|
|
# log_file is used to check caller stack frame
|
|
log_file = os.path.normcase(f.f_code.co_filename)
|
|
f = f.f_back
|
|
rv = "(unknown file)", 0, "(unknown function)", None
|
|
while f:
|
|
co = f.f_code
|
|
filename = os.path.normcase(co.co_filename)
|
|
if log_file == filename:
|
|
f = f.f_back
|
|
continue
|
|
if stack_info:
|
|
sinfo = _get_stack_info(f)
|
|
rv = (co.co_filename, f.f_lineno, co.co_name, sinfo)
|
|
break
|
|
return rv
|
|
|
|
|
|
def _get_stack_info(frame):
|
|
"""
|
|
Get the stack informations.
|
|
|
|
Args:
|
|
frame(frame): the frame requiring informations.
|
|
|
|
Returns:
|
|
str, the string of the stack informations.
|
|
"""
|
|
sinfo = None
|
|
stack_prefix = 'Stack (most recent call last):\n'
|
|
sinfo = stack_prefix + "".join(traceback.format_stack(frame))
|
|
return sinfo
|
|
|
|
|
|
def _setup_logger(kwargs):
|
|
"""
|
|
Set up the logger.
|
|
|
|
Args:
|
|
kwargs (dict): The dictionary of log configurations.
|
|
|
|
- console (bool): Whether to output log to stdout. Default: True.
|
|
- level (str): Log level. Default: WARNING.
|
|
- filepath (str): The path for saving logs, if console is false, a file path must be assigned.
|
|
- maxBytes (int): The Maximum value of a log file for rotating, only valid if console is false.
|
|
Default: 52428800.
|
|
- backupCount (int): The count of rotating backup log files, only valid if console is false. Default: 30.
|
|
|
|
Returns:
|
|
Logger, well-configured logger.
|
|
"""
|
|
|
|
# The name of Submodule
|
|
sub_module = 'ME'
|
|
# The name of Base log file
|
|
log_name = 'mindspore.log'
|
|
|
|
global _global_logger
|
|
|
|
_setup_logger_lock.acquire()
|
|
try:
|
|
if _global_logger:
|
|
return _global_logger
|
|
|
|
logger = logging.getLogger(name=f'{sub_module}.{log_name}')
|
|
# Override findCaller on the logger, Support for getting log record
|
|
logger.findCaller = _find_caller
|
|
console = kwargs.get('console', True)
|
|
# Set log level
|
|
logger.setLevel(kwargs.get('level', logging.WARNING))
|
|
# Set "propagate" attribute to False, stop searching up the hierarchy,
|
|
# avoid to load the handler of the root logger
|
|
logger.propagate = False
|
|
# Get the formatter for handler
|
|
formatter = _get_formatter()
|
|
|
|
# Clean up handle to avoid repeated loading
|
|
_clear_handler(logger)
|
|
|
|
# Set streamhandler for the console appender
|
|
if console:
|
|
console_handler = logging.StreamHandler(sys.stderr)
|
|
console_handler.name = 'StreamHandler'
|
|
console_handler.formatter = _DataFormatter(sub_module, formatter)
|
|
logger.addHandler(console_handler)
|
|
|
|
# Set rotatingFileHandler for the file appender
|
|
else:
|
|
# filepath cannot be null, checked in function _verify_config ()
|
|
logfile_dir = os.path.realpath(kwargs.get('filepath'))
|
|
file_name = f'{logfile_dir}/{log_name}'
|
|
logfile_handler = _MultiCompatibleRotatingFileHandler(
|
|
filename=file_name,
|
|
# Rotating max bytes, default is 50M
|
|
maxBytes=kwargs.get('maxBytes', _logger_def_max_bytes),
|
|
# Rotating backup count, default is 30
|
|
backupCount=kwargs.get('backupCount', _logger_def_backup_count),
|
|
encoding='utf8'
|
|
)
|
|
logfile_handler.name = 'FileHandler'
|
|
logfile_handler.formatter = _DataFormatter(sub_module, formatter)
|
|
logger.addHandler(logfile_handler)
|
|
|
|
_global_logger = logger
|
|
|
|
finally:
|
|
_setup_logger_lock.release()
|
|
return _global_logger
|