168 lines
4.4 KiB
Python
168 lines
4.4 KiB
Python
"""WizardKit: Log Functions"""
|
|
# vim: sts=2 sw=2 ts=2
|
|
|
|
import atexit
|
|
import logging
|
|
import os
|
|
import pathlib
|
|
import shutil
|
|
import time
|
|
|
|
from wk import cfg
|
|
from wk.io import non_clobber_path
|
|
|
|
|
|
# STATIC VARIABLES
|
|
if os.name == 'nt':
|
|
# Example: "C:\WK\1955-11-05\WizardKit"
|
|
DEFAULT_LOG_DIR = (
|
|
f'{os.environ.get("SYSTEMDRIVE", "C:")}/'
|
|
f'{cfg.main.KIT_NAME_SHORT}/Logs/'
|
|
)
|
|
else:
|
|
# Example: "/home/tech/Logs"
|
|
DEFAULT_LOG_DIR = f'{os.path.expanduser("~")}/Logs'
|
|
DEFAULT_LOG_NAME = cfg.main.KIT_NAME_FULL
|
|
|
|
|
|
# Functions
|
|
def enable_debug_mode():
|
|
"""Configures logging for better debugging."""
|
|
root_logger = logging.getLogger()
|
|
for handler in root_logger.handlers:
|
|
formatter = logging.Formatter(
|
|
datefmt=cfg.log.DEBUG['datefmt'],
|
|
fmt=cfg.log.DEBUG['format'],
|
|
)
|
|
handler.setFormatter(formatter)
|
|
root_logger.setLevel('DEBUG')
|
|
|
|
|
|
def format_log_path(
|
|
log_dir=None, log_name=None, timestamp=False,
|
|
kit=False, tool=False, append=False):
|
|
"""Format path based on args passed, returns pathlib.Path obj."""
|
|
log_path = pathlib.Path(
|
|
f'{log_dir if log_dir else DEFAULT_LOG_DIR}/'
|
|
f'{cfg.main.KIT_NAME_FULL+"/" if kit else ""}'
|
|
f'{"Tools/" if tool else ""}'
|
|
f'{log_name if log_name else DEFAULT_LOG_NAME}'
|
|
f'{"_" if timestamp else ""}'
|
|
f'{time.strftime("%Y-%m-%d_%H%M%S%z") if timestamp else ""}'
|
|
'.log'
|
|
)
|
|
log_path = log_path.resolve()
|
|
|
|
# Avoid clobbering
|
|
if not append:
|
|
log_path = non_clobber_path(log_path)
|
|
|
|
# Done
|
|
return log_path
|
|
|
|
|
|
def get_root_logger_path():
|
|
"""Get the log filepath from the root logger, returns pathlib.Path obj.
|
|
|
|
NOTE: This will use the first handler baseFilename it finds (if any).
|
|
"""
|
|
root_logger = logging.getLogger()
|
|
|
|
# Check handlers
|
|
for handler in root_logger.handlers:
|
|
if hasattr(handler, 'baseFilename'):
|
|
log_file = handler.baseFilename # type: ignore[reportGeneralTypeIssues]
|
|
return pathlib.Path(log_file).resolve()
|
|
|
|
# No log file found
|
|
raise RuntimeError('Log path not found.')
|
|
|
|
|
|
def remove_empty_log(log_path=None):
|
|
"""Remove log if empty.
|
|
|
|
NOTE: Under Windows an empty log is 2 bytes long.
|
|
"""
|
|
is_empty = False
|
|
|
|
# Get log path
|
|
if not log_path:
|
|
log_path = get_root_logger_path()
|
|
|
|
# Check if log is empty
|
|
try:
|
|
is_empty = log_path and log_path.exists() and log_path.stat().st_size <= 2
|
|
except (FileNotFoundError, AttributeError):
|
|
# File doesn't exist or couldn't verify it's empty
|
|
pass
|
|
|
|
# Delete log
|
|
if is_empty:
|
|
log_path.unlink()
|
|
|
|
|
|
def start(config=None):
|
|
"""Configure and start logging using safe defaults."""
|
|
log_path = format_log_path(timestamp=os.name != 'nt')
|
|
root_logger = logging.getLogger()
|
|
|
|
# Safety checks
|
|
if not config:
|
|
config = cfg.log.DEFAULT
|
|
if root_logger.hasHandlers():
|
|
raise UserWarning('Logging already started, results may be unpredictable.')
|
|
|
|
# Create log_dir
|
|
os.makedirs(log_path.parent, exist_ok=True)
|
|
|
|
# Config logger
|
|
logging.basicConfig(filename=log_path, **config)
|
|
|
|
# Register shutdown to run atexit
|
|
atexit.register(remove_empty_log)
|
|
atexit.register(logging.shutdown)
|
|
|
|
|
|
def update_log_path(
|
|
dest_dir=None, dest_name=None, keep_history=True, timestamp=True, append=False):
|
|
"""Moves current log file to new path and updates the root logger."""
|
|
root_logger = logging.getLogger()
|
|
new_path = format_log_path(dest_dir, dest_name, timestamp=timestamp, append=append)
|
|
old_handler = None
|
|
old_path = get_root_logger_path()
|
|
os.makedirs(new_path.parent, exist_ok=True)
|
|
|
|
# Get current logging file handler
|
|
for handler in root_logger.handlers:
|
|
if isinstance(handler, logging.FileHandler):
|
|
old_handler = handler
|
|
break
|
|
if not old_handler:
|
|
raise RuntimeError('Logging FileHandler not found')
|
|
|
|
# Copy original log to new location
|
|
if keep_history:
|
|
if new_path.exists():
|
|
raise FileExistsError(f'Refusing to clobber: {new_path}')
|
|
shutil.copy(old_path, new_path)
|
|
|
|
# Create new handler (preserving formatter settings)
|
|
new_handler = logging.FileHandler(new_path, mode='a')
|
|
new_handler.setFormatter(old_handler.formatter)
|
|
|
|
# Remove old_handler
|
|
root_logger.removeHandler(old_handler)
|
|
old_handler.close()
|
|
|
|
# Delete orignal log if needed
|
|
if keep_history:
|
|
remove_empty_log(old_path)
|
|
else:
|
|
old_path.unlink()
|
|
|
|
# Add new handler
|
|
root_logger.addHandler(new_handler)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print("This file is not meant to be called directly.")
|