# If threeml is not installed, we create our own log,
# otherwise, just print to the 3ML one!
import logging
import logging.handlers as handlers
import sys
from hashlib import sha256
from pathlib import Path
from astromodels.utils.configuration import astromodels_config
from .sys_tools import colored, supports_color
try:
from threeML.config.config import threeML_config
has_threeml = True
except:
has_threeml = False
[docs]def get_path_of_log_dir():
# we use the 3ML log path to simplify things
# a more clever solution could be found
if has_threeml:
user_log: Path = Path(threeML_config.logging.path).expanduser()
else:
user_log: Path = Path(astromodels_config.logging.path).expanduser()
# Create it if doesn't exist
if not user_log.exists():
user_log.mkdir(parents=True)
return user_log
_log_file_names = ["usr.log", "dev.log"]
[docs]def get_path_of_log_file(log_file: str) -> Path:
"""
returns the path of the log files
"""
assert log_file in _log_file_names, f"{log_file} is not one of {_log_file_names}"
return get_path_of_log_dir() / log_file
DEFAULT_LOG_COLORS = {
'DEBUG': 'blue',
'INFO': 'green',
'WARNING': 'purple',
'ERROR': 'red',
'CRITICAL': 'bold_red',
}
[docs]def esc(*x):
"""Create escaped code from format code"""
return '\033[' + ';'.join(x) + 'm'
# The following coloured log logic is from
# https://github.com/borntyping/python-colorlog
# I dropped some features and removed the Python 2.7 compatibility
ESCAPE_CODES = {'reset': esc('0'), 'bold': esc('01'), 'thin': esc('02')}
COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'purple', 'cyan', 'white']
PREFIXES = [
# Foreground without prefix
('3', ''),
('01;3', 'bold_'),
('02;3', 'thin_'),
# Foreground with fg_ prefix
('3', 'fg_'),
('01;3', 'fg_bold_'),
('02;3', 'fg_thin_'),
# Background with bg_ prefix - bold/light works differently
('4', 'bg_'),
('10', 'bg_bold_'),
]
for _prefix, _prefix_name in PREFIXES:
for _code, _name in enumerate(COLORS):
ESCAPE_CODES[_prefix_name + _name] = esc(_prefix + str(_code))
[docs]def parse_colors(sequence):
"""Return escape codes from a color sequence."""
return ''.join(ESCAPE_CODES[n] for n in sequence.split(',') if n)
[docs]class ColoredRecord:
"""
Wraps a LogRecord, adding named escape codes to the internal dict.
The internal dict is used when formatting the message (by the PercentStyle,
StrFormatStyle, and StringTemplateStyle classes).
"""
def __init__(self, record):
"""Add attributes from the escape_codes dict and the record."""
self.__dict__.update(ESCAPE_CODES)
self.__dict__.update(record.__dict__)
self.__record = record
def __getattr__(self, name):
return getattr(self.__record, name)
[docs]def escape_codes(log_colors, level_name):
"""Return escape codes from a ``log_colors`` dict."""
return parse_colors(log_colors.get(level_name, ""))
[docs]def hash_coloured(text):
"""Return a ANSI coloured text based on its hash"""
ansi_code = int(sha256(text.encode('utf-8')).hexdigest(), 16) % 230
return colored(text, ansi_code=ansi_code)
[docs]def hash_coloured_escapes(text):
"""Return the ANSI hash colour prefix and suffix for a given text"""
ansi_code = int(sha256(text.encode('utf-8')).hexdigest(), 16) % 230
prefix, suffix = colored('SPLIT', ansi_code=ansi_code).split('SPLIT')
return prefix, suffix
[docs]class LogFilter(object):
def __init__(self, level):
self.__level = level
[docs] def filter(self, logRecord):
return logRecord.levelno != self.__level
# now create the developer handler that rotates every day and keeps
# 10 days worth of backup
astromodels_dev_log_handler = handlers.TimedRotatingFileHandler(
get_path_of_log_file("dev.log"), when="D", interval=1, backupCount=10
)
# lots of info written out
_dev_formatter = logging.Formatter(
"%(asctime)s | %(name)s | %(levelname)s| %(funcName)s | %(lineno)d | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
astromodels_dev_log_handler.setFormatter(_dev_formatter)
astromodels_dev_log_handler.setLevel(logging.DEBUG)
# now set up the usr log which will save the info
astromodels_usr_log_handler = handlers.TimedRotatingFileHandler(
get_path_of_log_file("usr.log"), when="D", interval=1, backupCount=10
)
astromodels_usr_log_handler.setLevel(logging.INFO)
# lots of info written out
_usr_formatter = logging.Formatter(
"%(asctime)s | %(levelname)s | %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
)
astromodels_usr_log_handler.setFormatter(_usr_formatter)
# now set up the console logger
name = "test"
if supports_color():
prefix_1, suffix = hash_coloured_escapes(name)
prefix_2, _ = hash_coloured_escapes(name + 'salt')
else:
prefix_1, prefix_2, suffix = ('', '', '')
date_str = ''
_console_formatter = ColoredFormatter('[%(log_color)s%(levelname)-8s%(reset)s]'
'%(log_color)s %(message)s',
datefmt="%H:%M:%S",
)
astromodels_console_log_handler = logging.StreamHandler(sys.stdout)
astromodels_console_log_handler.setFormatter(_console_formatter)
astromodels_console_log_handler.setLevel("INFO")
warning_filter = LogFilter(logging.WARNING)
[docs]def silence_warnings():
"""
supress warning messages in console and file usr logs
"""
astromodels_usr_log_handler.addFilter(warning_filter)
astromodels_console_log_handler.addFilter(warning_filter)
[docs]def activate_warnings():
"""
supress warning messages in console and file usr logs
"""
astromodels_usr_log_handler.removeFilter(warning_filter)
astromodels_console_log_handler.removeFilter(warning_filter)
[docs]def update_logging_level(level):
astromodels_console_log_handler.setLevel(level)
[docs]def setup_logger(name):
# A logger with name name will be created
# and then add it to the print stream
log = logging.getLogger(name)
# this must be set to allow debug messages through
log.setLevel(logging.DEBUG)
# add the handlers
log.addHandler(astromodels_dev_log_handler)
log.addHandler(astromodels_console_log_handler)
log.addHandler(astromodels_usr_log_handler)
# we do not want to duplicate teh messages in the parents
log.propagate = False
return log