2024-06-27 13:40:20 +02:00

108 lines
3.8 KiB
Python

import json
import subprocess
import click
from pathlib import PurePath, Path
import configparser
import logging
from logging.handlers import RotatingFileHandler
import sys
APP_NAME = 'iottb'
DB_NAME = 'iottb.db'
CONSOLE_LOG_FORMATS = {
0: '%(levelname)s - %(message)s',
1: '%(levelname)s - %(module)s - %(message)s',
2: '%(levelname)s - %(asctime)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s'
}
LOGFILE_LOG_FORMAT = {
0: '%(levelname)s - %(asctime)s - %(module)s - %(message)s',
1: '%(levelname)s - %(asctime)s - %(module)s - %(funcName)s - %(message)s',
2: '%(levelname)s - %(asctime)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s'
}
MAX_VERBOSITY = len(CONSOLE_LOG_FORMATS) - 1
assert len(LOGFILE_LOG_FORMAT) == len(CONSOLE_LOG_FORMATS), 'Log formats must be same size'
logger = logging.getLogger(__name__)
def setup_logging(verbosity, debug):
""" Setup root logger for iottb """
log_level = logging.ERROR
handlers = []
date_format = '%Y-%m-%d %H:%M:%S'
if verbosity > 0:
log_level = logging.WARNING
if verbosity > MAX_VERBOSITY:
verbosity = MAX_VERBOSITY
log_level = logging.INFO
assert verbosity <= MAX_VERBOSITY, f'Verbosity must be <= {MAX_VERBOSITY}'
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(logging.Formatter(CONSOLE_LOG_FORMATS[verbosity], datefmt=date_format))
console_handler.setLevel(logging.DEBUG) # can keep at debug since it depends on global level?
handlers.append(console_handler)
if debug:
log_level = logging.DEBUG
# Logfile logs INFO+, no debugs though
file_handler = RotatingFileHandler(f'{APP_NAME}.log', maxBytes=10240, backupCount=5)
file_handler.setFormatter(logging.Formatter(LOGFILE_LOG_FORMAT[verbosity], datefmt=date_format))
file_handler.setLevel(logging.INFO)
# finnish root logger setup
handlers.append(file_handler)
# Force this config to be applied to root logger
logging.basicConfig(level=log_level, handlers=handlers, force=True)
def create_default_config(cfg_file):
"""Create default iottb config file."""
assert logger is not None, 'Logger must be initialized'
logger.info(f'Creating default config file at {cfg_file}')
# By default, create iottb.db in user home
defaults = {
'DefaultDatabase': DB_NAME,
'DatabaseLocations': {
DB_NAME: str(Path.home() / DB_NAME)
}
}
with open(cfg_file, 'w') as config_file:
json.dump(defaults, config_file)
return defaults
def load_config(ctx, param, value):
""" Try to load iottb config file.
If the file does not exist, it will be created with default values.`
Try to load the iottb config file from the given path.
If the file does not exist, it will be created with default values.
The only value set is the path to the iottb.db file.
"""
logger.info(f'Loading config from {value}')
cfg_file = value
if not cfg_file.is_file():
defaults = create_default_config(cfg_file)
else:
defaults = json.load(cfg_file)
ctx.obj['path']['cfg'] = cfg_file
ctx.obj['path']['db'] = defaults['DatabaseLocations'][defaults['DefaultDatabase']]
@click.group()
@click.option('-v', '--verbosity', type=click.IntRange(0, MAX_VERBOSITY), default=0,
help='Set verbosity')
@click.option('-d', '--debug', is_flag=True, default=False, help='Enable debug mode')
@click.option('--cfg-file', type=click.Path(), default=Path(click.get_app_dir(APP_NAME)).joinpath('iottb.cfg'),
envvar='IOTTB_CONF_HOME', is_eager=True, callback=load_config, help='Path to iottb config file')
@click.pass_context
def main(verbosity, debug):
setup_logging(verbosity, debug)
if __name__ == '__main__':
main(auto_envvar_prefix='IOTTB')