import sys if sys.version_info[0] != 3: print("This script requires Python 3") exit() import logging from logging import Logger import time import datetime import inspect # import traceback # from typing import Optional from pprint import pprint # pformat from .strutil import nott # iss class Logg: """ This method presently defines various log "levels" but does not yet express ability to log "areas" or "keywords". TODO: - LOG BUFFER a list that only holds last 100 lines logged to it. This is useful for emitting when an exception happens in a loop and you are not interested in the first 10e6 log entries - KEYWORD LOGGING: pair a --debug_kw=keyword,keyword set on the command line to only recieve log output from log statements matching those keywords - CLASS/METHOD/FUNCTION logging: --debug_fn=class.method,module.func set on the command line that activates logging in the method or function listed. See inspection techniques listed near this SO question https://stackoverflow.com/a/5104943/11014343 - BITWISE LOG LEVELS: --log_level=DEBUG|FILEIO|JSON|HTTP a maskable combination of enum_bitmask names that combine to a value that can trigger logging. These reserved words may not be used as tags: debug, debugging, debug_log, digest, file, gui, http, json, log, method, tag Protocol logging levels: * always: X-Errors( stops script on halt_on_errors) * timeouts: can be configured as halt level errors - digest (POST set_port / GET /ports ) - url (POST /cli-json/set_port / GET /port/1/2/3/?fields) - json (POST /cli-json/set_port { a:b } ; GET /port/1/2/3?fields {results interfaces[]} - http that plus X-Warnings and ALL headers - gui Xtra debugging messages generated by LANforgeGUI Please also consider how log messages can be formatted: https://stackoverflow.com/a/20112491/11014343: logging.basicConfig(format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s") """ DEFAULT_LEVEL = logging.WARNING DefaultLogger = logging.getLogger(__name__) method_name_list: list = [] # list[str] tag_list: list = [] # list[str] reserved_tags: list = [ # list[str] "debug", "debugging", "debug_log", "digest", "file", "gui", "http", "json", "log", "method", "tag" ] def __init__(self, log_level: int = DEFAULT_LEVEL, name: str = None, filename: str = None, debug: bool = False): """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- Base class that can be used to send logging messages elsewhere. extend this in order to send log messages from this framework elsewhere. ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" self.level = log_level self.logger: Logger # self.start_time = datetime.now() # py 3.9 maybe? self.start_time = datetime.datetime.now() # py 3.9 maybe? self.start_time_str = time.strftime("%Y%m%d-%I:%M%:%S") if name: self.name = name if "@" in name: self.name = name.replace('@', self.start_time_str) else: self.name = "started-" + self.start_time_str self.logger = Logger(name, level=log_level) if filename: logging.basicConfig(filename=filename, filemode="a") if debug: self.logg(level=logging.WARNING, msg="Logger {name} begun to {filename}".format(name=name, filename=filename)) @classmethod def logg(cls, level: int = logging.WARNING, tag: str = None, msg: str = None) -> None: """ Use this *class method* to send logs to the DefaultLogger instance created when this class was created :param level: :param msg: :return: """ if nott(msg): return if level == logging.CRITICAL: cls.DefaultLogger.critical(msg) return if level == logging.ERROR: cls.DefaultLogger.error(msg) return if level == logging.WARNING: cls.DefaultLogger.warning(msg) return if level == logging.INFO: cls.DefaultLogger.info(msg) return if level == logging.DEBUG: cls.DefaultLogger.debug(msg) return def by_level(self, level: int = logging.WARNING, msg: str = None): """ Use this *instance* version of the method for logging when you have a specific logger customized for a purpose. Otherwise please use Logg.logg(). :param level: python logging priority :param msg: text to send to logging channel :return: None """ if nott(msg): return if level == logging.CRITICAL: self.logger.critical(msg) return if level == logging.ERROR: self.logger.error(msg) return if level == logging.WARNING: self.logger.warning(msg) return if level == logging.INFO: self.logger.info(msg) return if level == logging.DEBUG: self.logger.debug(msg) return print("UNKNOWN: " + msg) def error(self, message: str = None): if not message: return self.logg(level=logging.ERROR, msg=message) def warning(self, message: str = None): if not message: return self.logg(level=logging.WARNING, msg=message) def info(self, message: str = None): if not message: return self.logg(level=logging.INFO, msg=message) def debug(self, message: str = None): if not message: return self.logg(level=logging.DEBUG, msg=message) @classmethod def register_method_name(cls, methodname: str = None) -> None: """ Use this method to register names of functions you want to allow logging from :param methodname: :return: """ if not methodname: return cls.method_name_list.append(methodname) if methodname not in cls.tag_list: cls.tag_list.append(methodname) @classmethod def register_tag(cls, tag: str = None) -> None: """ Use this method to register keywords you want to allow logging from. There are a list of reserved tags which will not be accepted. :return: """ if not tag: return if tag in cls.tag_list: return if tag in cls.reserved_tags: cls.logg(level=logging.ERROR, msg=f"tag [{tag}] is reserved, ignoring") # note: add directly to tag_list to append a reserved tag cls.tag_list.append(tag) @classmethod def by_method(cls, msg: str = None) -> None: """ should only log if we're in the method_list reminder: https://stackoverflow.com/a/13514318/11014343 import inspect import types from typing import cast this_fn_name = cat(types.FrameType, inspect.currentframe()).f_code.co_name :return: None """ try: caller = inspect.currentframe().f_back.f_code.co_name if caller in cls.method_name_list: cls.logg(level=cls.DEFAULT_LEVEL, msg=f"[{caller}] {msg}") except Exception as e: pprint(e) pass @classmethod def by_tag(cls, tag: str = None, msg: str = None) -> None: """ should only log if we're in the method_list reminder: https://stackoverflow.com/a/13514318/11014343 import inspect import types from typing import cast this_fn_name = cat(types.FrameType, inspect.currentframe()).f_code.co_name :return: """ if (not cls.tag_list) or (tag not in cls.tag_list): return cls.logg(level=cls.DEFAULT_LEVEL, msg=f"[{tag}] {msg}") def enable(self, reserved_tag: str = None) -> None: if (not reserved_tag) or (reserved_tag not in self.reserved_tags): return if reserved_tag in self.tag_list: return self.tag_list.append(reserved_tag)