misc: add save_json and load_json to DotDict, logging: complete rework

This commit is contained in:
Izalia Mae 2021-06-04 11:31:05 -04:00
parent da85c268bb
commit 86a7737e6e
2 changed files with 86 additions and 162 deletions

View file

@ -1,198 +1,112 @@
'''Simple logging module'''
import sys import sys
from os import environ as env from IzzyLib.misc import DotDict
from os import getppid, environ as env
from datetime import datetime from datetime import datetime
from enum import Enum
stdout = sys.stdout class Levels(Enum):
CRITICAL = 60,
ERROR = 50
WARNING = 40
INFO = 30
VERBOSE = 20
DEBUG = 10
MERP = 0
class Log(): class Log:
def __init__(self, config=dict()): __slots__ = [
'''setup the logger''' 'name', 'level', 'date', 'format',
if not isinstance(config, dict): 'critical', 'error', 'warning',
raise TypeError(f'config is not a dict') 'info', 'verbose', 'debug', 'merp'
]
self.levels = { def __init__(self, name, **config):
'CRIT': 60, self.name = name
'ERROR': 50, self.level = Levels.INFO
'WARN': 40, self.date = True
'INFO': 30, self.format = '%Y-%m-%d %H:%M:%S'
'VERB': 20, self.update_config(**config)
'DEBUG': 10,
'MERP': 0
}
self.long_levels = { for level in Levels:
'CRITICAL': 'CRIT', self._set_log_function(level)
'ERROR': 'ERROR',
'WARNING': 'WARN',
'INFO': 'INFO',
'VERBOSE': 'VERB',
'DEBUG': 'DEBUG',
'MERP': 'MERP'
}
self.config = {'windows': sys.executable.endswith('pythonw.exe')}
self.setConfig(self._parseConfig(config))
def _lvlCheck(self, level): def _set_log_function(self, level):
'''make sure the minimum logging level is an int''' setattr(self, level.name.lower(), lambda *args: self.log(level, *args))
def print(self, *args):
sys.stdout.write(' '.join([str(arg) for arg in args]))
sys.stdout.flush()
def parse_level(self, level):
try: try:
value = int(level) return Levels(int(level))
except ValueError: except ValueError:
level = self.long_levels.get(level.upper(), level) return getattr(Levels, level.upper())
value = self.levels.get(level)
if value not in self.levels.values():
raise InvalidLevel(f'Invalid logging level: {level}')
return value
def _getLevelName(self, level): def update_config(self, **data):
for name, num in self.levels.items(): for key, value in data.items():
if level == num: self.set_config(key, value)
return name
raise InvalidLevel(f'Invalid logging level: {level}')
def _parseConfig(self, config): def set_config(self, key, value):
'''parse the new config and update the old values''' if key == 'level' and type(value) == str:
date = config.get('date', self.config.get('date',True)) value = self.parse_level(value)
systemd = config.get('systemd', self.config.get('systemd,', True))
windows = config.get('windows', self.config.get('windows', False))
if not isinstance(date, bool): setattr(self, key, value)
raise TypeError(f'value for "date" is not a boolean: {date}')
if not isinstance(systemd, bool):
raise TypeError(f'value for "systemd" is not a boolean: {date}')
level_num = self._lvlCheck(config.get('level', self.config.get('level', 'INFO')))
newconfig = {
'level': self._getLevelName(level_num),
'levelnum': level_num,
'datefmt': config.get('datefmt', self.config.get('datefmt', '%Y-%m-%d %H:%M:%S')),
'date': date,
'systemd': systemd,
'windows': windows,
'systemnotif': config.get('systemnotif', None)
}
return newconfig
def setConfig(self, config): def get_config(self, key):
'''set the config''' return self[key]
self.config = self._parseConfig(config)
def getConfig(self, key=None): def print_config(self):
'''return the current config''' self.print(*(f'{k}: {v}\n' for k,v in self.items()))
if key:
if self.config.get(key):
return self.config.get(key)
else:
raise ValueError(f'Invalid config option: {key}')
return self.config
def printConfig(self):
for k,v in self.config.items():
stdout.write(f'{k}: {v}\n')
stdout.flush()
def setLevel(self, level):
self.minimum = self._lvlCheck(level)
def log(self, level, *msg): def log(self, level, *msg):
if self.config['windows']: if level.value < self.level.value:
return return
'''log to the console''' default = self.name == 'Default'
levelNum = self._lvlCheck(level) options = [
level.name + ':',
' '.join([str(message) for message in msg]),
'\n'
]
if type(level) == int: if self.date and not getppid() == 1:
level = _getLevelName(level) options.insert(0, datetime.now().strftime(self.format))
if levelNum < self.config['levelnum']: if not default:
return options.insert(0 if not self.date else 1, f'[{self.name}]')
message = ' '.join([str(message) for message in msg]) self.print(*options)
output = f'{level}: {message}\n'
if self.config['systemnotif']:
self.config['systemnotif'].New(level, message)
if self.config['date'] and (self.config['systemd'] and not env.get('INVOCATION_ID')):
'''only show date when not running in systemd and date var is True'''
date = datetime.now().strftime(self.config['datefmt'])
output = f'{date} {output}'
stdout.write(output)
stdout.flush()
def critical(self, *msg): def get_logger(name, **config):
self.log('CRIT', *msg) try:
return logger[name.lower()]
def error(self, *msg): except KeyError:
self.log('ERROR', *msg) log = Log(name, **config)
logger[name.lower()] = log
def warning(self, *msg): return log
self.log('WARN', *msg)
def info(self, *msg):
self.log('INFO', *msg)
def verbose(self, *msg):
self.log('VERB', *msg)
def debug(self, *msg):
self.log('DEBUG', *msg)
def merp(self, *msg):
self.log('MERP', *msg)
def getLogger(loginst, config=None):
'''get a logging instance and create one if it doesn't exist'''
Logger = logger.get(loginst)
if not Logger:
if config:
logger[loginst] = Log(config)
else:
raise InvalidLogger(f'logger "{loginst}" doesn\'t exist')
return logger[loginst]
class InvalidLevel(Exception):
'''Raise when an invalid logging level was specified'''
class InvalidLogger(Exception):
'''Raise when the specified logger doesn't exist'''
'''create a default logger''' '''create a default logger'''
logger = { logger = {
'default': Log() 'default': Log('Default')
} }
DefaultLog = logger['default'] DefaultLog = logger['default']
'''aliases for default logger's log output functions''' '''aliases for default logger's log output functions'''
critical = DefaultLog.critical critical = DefaultLog.critical
error = DefaultLog.error error = DefaultLog.error
@ -203,7 +117,7 @@ debug = DefaultLog.debug
merp = DefaultLog.merp merp = DefaultLog.merp
'''aliases for the default logger's config functions''' '''aliases for the default logger's config functions'''
setConfig = DefaultLog.setConfig update_config = DefaultLog.update_config
getConfig = DefaultLog.getConfig set_config = DefaultLog.set_config
setLevel = DefaultLog.setLevel get_config = DefaultLog.get_config
printConfig = DefaultLog.printConfig print_config = DefaultLog.print_config

View file

@ -230,10 +230,6 @@ class DotDict(dict):
return (k, v) return (k, v)
def update(self, data):
super().update(data)
def get(self, key, default=None): def get(self, key, default=None):
value = dict.get(self, key, default) value = dict.get(self, key, default)
return DotDict(value) if type(value) == dict else value return DotDict(value) if type(value) == dict else value
@ -275,6 +271,20 @@ class DotDict(dict):
self.update(data) self.update(data)
def load_json(self, path):
path = Path(path)
with path.open('r') as fd:
self.update(json.load(fd))
def save_json(self, path, indent=None):
path = Path(path)
with path.open('w') as fd:
json.dump(self, fd, indent=indent)
class DefaultDict(DotDict): class DefaultDict(DotDict):
def __getattr__(self, key): def __getattr__(self, key):
try: try: