diff --git a/panaetius/__init__.py b/panaetius/__init__.py index 866a529..500f8d6 100644 --- a/panaetius/__init__.py +++ b/panaetius/__init__.py @@ -1,2 +1,3 @@ from panaetius.config import Config from panaetius.library import set_config +from panaetius.logging import set_logger, SimpleLogger, AdvancedLogger, CustomLogger diff --git a/panaetius/config.py b/panaetius/config.py index e2a147d..14fd6a6 100644 --- a/panaetius/config.py +++ b/panaetius/config.py @@ -17,7 +17,11 @@ class Config: else pathlib.Path.home() / ".config" ) self._missing_config = False + + # default logging options self.logging_path: str | None = None + self.logging_rotate_bytes: int = 0 + self.logging_backup_count: int = 0 @property def config(self) -> dict[str, Any]: @@ -82,7 +86,7 @@ class Config: return self.__load_default_value(default) return self.__load_value(value, coerce) - def __load_value(self, value: str, coerce: bool) -> Any: + def __load_value(self, value: str, coerce: bool) -> Any: # noqa value = str(value).lower() if isinstance(value, bool) else value return ( toml.loads(f"value = {value}")["value"] @@ -90,7 +94,7 @@ class Config: else toml.loads(f'value = "{value}"')["value"] ) - def __load_default_value(self, default: Any) -> Any: + def __load_default_value(self, default: Any) -> Any: # noqa if isinstance(default, str): return toml.loads(f'value = "{default}"')["value"] # if default is bool convert to lower case toml syntax diff --git a/panaetius/logging.py b/panaetius/logging.py index 181fe78..574db26 100644 --- a/panaetius/logging.py +++ b/panaetius/logging.py @@ -1,53 +1,91 @@ from __future__ import annotations -from abc import ABC, abstractmethod +from abc import ABCMeta, abstractmethod import logging +from logging.handlers import RotatingFileHandler import pathlib import sys from panaetius import Config +from panaetius.library import set_config -def set_logger(config_inst: Config, logging_format: Logger): +def set_logger(config_inst: Config, logging_format_inst: LoggingData) -> logging.Logger: logger = logging.getLogger(config_inst.header_variable) - loghandler_sys = logging.StreamHandler(sys.stdout) + log_handler_sys = logging.StreamHandler(sys.stdout) - # check if log path is set + # configure file handler if config_inst.logging_path is not None: - logging_file = pathlib.Path(config_inst.logging_path) + logging_file = ( + pathlib.Path(config_inst.logging_path) + / config_inst.header_variable + / f"{config_inst.header_variable}.log" + ).expanduser() + + if config_inst.logging_rotate_bytes == 0: + set_config(config_inst, "logging.rotate_bytes", 512000) + if config_inst.logging_backup_count == 0: + set_config(config_inst, "logging.backup_count", 3) + + log_handler_file = RotatingFileHandler( + str(logging_file), + "a", + config_inst.logging_rotate_bytes, + config_inst.logging_backup_count, + ) + + log_handler_file.setFormatter(logging.Formatter(logging_format_inst.format)) + logger.addHandler(log_handler_file) + + # configure stdout handler + log_handler_sys.setFormatter(logging.Formatter(logging_format_inst.format)) + logger.addHandler(log_handler_sys) + logger.setLevel(logging_format_inst.logging_level) + return logger -class Logger(ABC): +class LoggingData(metaclass=ABCMeta): @property @abstractmethod - def format(self): + def format(self) -> str: pass + @abstractmethod + def __init__(self, logging_level: str): + self.logging_level = logging_level -class SimpleLogger(Logger): + +class SimpleLogger(LoggingData): @property - def format(self): - return ( + def format(self) -> str: + return str( '{\n\t"time": "%(asctime)s",\n\t"logging_level":' '"%(levelname)s",\n\t"message": "%(message)s"\n}', ) + def __init__(self, logging_level: str = "INFO"): + self.logging_level = logging_level -class AdvancedLogger(Logger): + +class AdvancedLogger(LoggingData): @property - def format(self): - return ( + def format(self) -> str: + return str( '{\n\t"time": "%(asctime)s",\n\t"file_name": "%(filename)s",' '\n\t"module": "%(module)s",\n\t"function":"%(funcName)s",\n\t' '"line_number": "%(lineno)s",\n\t"logging_level":' '"%(levelname)s",\n\t"message": "%(message)s"\n}', ) + def __init__(self, logging_level: str = "INFO"): + self.logging_level = logging_level -class CustomLogger(Logger): + +class CustomLogger(LoggingData): @property - def format(self): - return self._format + def format(self) -> str: + return str(self._format) - def __init__(self, logging_format: str): + def __init__(self, logging_format: str, logging_level: str = "INFO"): + self.logging_level = logging_level self._format = logging_format diff --git a/rewrite.todo b/rewrite.todo index 69d7948..b8e5604 100644 --- a/rewrite.todo +++ b/rewrite.todo @@ -11,10 +11,33 @@ Coding: ✔ Create SimpleLogger, AdvancedLogger, CustomLogger classes @done(21-10-16 16:22) should simply have the different logging strings to output should both specify whether to save to file or not - ☐ Logging path should take by default the config path unless overwritten? + ✔ Logging path should take by default the config path unless overwritten? @done(21-10-16 23:49) + + Errors: + ☐ Check logging path + config path are valid, if not raise error. + ☐ Add tests for these. + + Linting: + ☐ Check all functions and annotations. + + Docstrings: + ☐ Write the docstrings for public functions/methods. Documentation: ☐ Rewrite documentation using `mkdocs` and using `.md`. @2h Tests: + Config File: + ☐ + + Environment Variable: + ☐ + + __init__: + ☐ Test default config path set to "~/.config" + ☐ Test config path is set when passed in + + config property: + ☐ Check testing config file is returned as dict + ☐ Check _self.missing_config and empty dict is returned