From 67c080db683a79d1f39c765051b9a3710d84816a Mon Sep 17 00:00:00 2001 From: happy Date: Tue, 25 Apr 2023 19:58:56 -0700 Subject: [PATCH] adds logger utility --- .gitattributes | 2 + .gitignore | 125 +++++++++++++++++++++++++++ BUILD | 4 + WORKSPACE | 15 ++++ requirements.txt | 3 + utilities/BUILD | 11 +++ utilities/logger.py | 181 +++++++++++++++++++++++++++++++++++++++ utilities/logger_test.py | 35 ++++++++ 8 files changed, 376 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 BUILD create mode 100644 WORKSPACE create mode 100644 requirements.txt create mode 100644 utilities/BUILD create mode 100644 utilities/logger.py create mode 100644 utilities/logger_test.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7af88c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,125 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Bazel stuff +bazel-* + +# MacOS stuff +.DS_Store + +# Django +*.sqlite3 +local_settings.py +web/.env diff --git a/BUILD b/BUILD new file mode 100644 index 0000000..3d75f61 --- /dev/null +++ b/BUILD @@ -0,0 +1,4 @@ +load("@rules_python//python:defs.bzl", "py_binary") + +package(default_visibility = ["//visibility:public"]) + diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..0577dbb --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,15 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") + +http_archive( + name = "rules_python", + sha256 = "94750828b18044533e98a129003b6a68001204038dc4749f40b195b24c38f49f", + strip_prefix = "rules_python-0.21.0", + url = "https://github.com/bazelbuild/rules_python/archive/0.21.0.tar.gz", +) + +git_repository( + name = "subpar", + remote = "https://github.com/google/subpar", + tag = "2.0.0", +) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..31b026c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +colorlog +diffusers +torch diff --git a/utilities/BUILD b/utilities/BUILD new file mode 100644 index 0000000..c1a4fed --- /dev/null +++ b/utilities/BUILD @@ -0,0 +1,11 @@ + +py_library( + name = "logger", + srcs = ["logger.py"], +) + +py_test( + name = "logger_test", + srcs = ["logger_test.py"], + deps = [":logger"], +) diff --git a/utilities/logger.py b/utilities/logger.py new file mode 100644 index 0000000..0aa5d58 --- /dev/null +++ b/utilities/logger.py @@ -0,0 +1,181 @@ +import logging +import os +import sys +import traceback +from colorlog import ColoredFormatter + + +FORMATTER_STREAM = ColoredFormatter( + "%(asctime)s %(log_color)s%(levelname)-.1s[%(name)s] %(message)s%(reset)s") +FORMATTER_FILE = logging.Formatter("%(asctime)s %(levelname)-.1s. %(message)s") + +VERBOSITY_V = 10 +VERBOSITY_VV = 100 +VERBOSITY_VVV = 1000 + + +def touch(filepath): + ''' + Behaves similarly as `touch` command in Linux system. + Creates an empty file. + ''' + try: + os.makedirs(os.path.dirname(filepath), exist_ok=True) + open(filepath, 'a').close() + return True + except BaseException: + pass + return False + + +def get_func_name(offset=0): + """ + Returns the name of the caller function name. + Offset counts the recursion - for example, offset=1 means the caller of the caller. + """ + return sys._getframe(1+offset).f_code.co_name + + +class DummyLogger(): + ''' + DummyLogger does not do anything. + ''' + + def __init__(self): + pass + + def get_verbosity_value(self) -> int: + return 0 + + def set_verbosity(self, verbosity: int): + pass + + def set_log_output_filepath(self, filepath: str, level=logging.DEBUG): + pass + + def debugging_on(self): + pass + + def debugging_off(self): + pass + + def info(self, msg=""): + pass + + def error(self, msg=""): + pass + + def warn(self, msg=""): + pass + + def debug(self, msg="", verbosity=VERBOSITY_V): + pass + + def critical(self, msg=""): + pass + + +class Logger(DummyLogger): + ''' + Logger with specific format + ''' + + def __init__( + self, + name: str = "default", + filepath: str = "", + verbosity: int = VERBOSITY_V, + stream_lvl=logging.INFO, + file_lvl=logging.DEBUG + ): + self.verbosity = verbosity + self.logger = logging.getLogger(name) + self.logger.setLevel(logging.DEBUG) + + self.__streamHandler = logging.StreamHandler() + self.__streamHandler.setLevel(stream_lvl) + self.__streamHandler.setFormatter(FORMATTER_STREAM) + self.logger.addHandler(self.__streamHandler) + + self.__fileHandler = None + if filepath and touch(filepath): + file_handler = logging.FileHandler(filepath) + file_handler.setLevel(file_lvl) + file_handler.setFormatter(FORMATTER_FILE) + self.__fileHandler = file_handler + self.logger.addHandler(self.__fileHandler) + + self.logger.debug("log for {} started".format(name)) + self.logger.info("log output filepath: {}".format(filepath)) + + def get_verbosity_value(self) -> int: + return self.verbosity + + def set_verbosity(self, verbosity: int): + self.verbosity = verbosity + + def set_log_output_filepath(self, filepath: str, level=logging.DEBUG): + # remove prior one if any, only one handler would be allowed at a time + if self.__fileHandler is not None: + self.logger.removeHandler(self.__fileHandler) + self.__fileHandler.close() + if filepath and touch(filepath): + file_handler = logging.FileHandler(filepath) + file_handler.setLevel(level) + file_handler.setFormatter(FORMATTER_FILE) + self.__fileHandler = file_handler + self.logger.addHandler(self.__fileHandler) + + def debugging_on(self): + self.__streamHandler.setLevel(logging.DEBUG) + self.logger.info( + "debug msg printing is on, verbosity: {}".format(self.verbosity)) + + def debugging_off(self): + self.__streamHandler.setLevel(logging.INFO) + self.logger.info("debug msg printing is off") + + def info(self, msg="", verbosity: int = VERBOSITY_V): + ''' + Showing info message. + @param msg: the message + @param verbosity: the higher the number is, the less important it is. + ''' + if verbosity > self.verbosity: + return + self.logger.info("[{}] {}".format(get_func_name(offset=1), msg)) + + def warn(self, msg="", verbosity: int = VERBOSITY_V): + ''' + Showing warning message. + @param msg: the message + @param verbosity: the higher the number is, the less important it is. + ''' + if verbosity > self.verbosity: + return + self.logger.warning("[{}] {}".format(get_func_name(offset=1), msg)) + + def debug(self, msg: str = "", verbosity: int = VERBOSITY_V): + ''' + Showing debug message. + @param msg: the message + @param verbosity: the higher the number is, the less important it is. + ''' + if verbosity > self.verbosity: + return + self.logger.debug("[{}] {}".format(get_func_name(offset=1), msg)) + + def error(self, msg=""): + ''' + Showing error message. + @param msg: the message + ''' + self.logger.error("[{}] {}".format(get_func_name(offset=1), msg)) + self.logger.error(traceback.format_exc()) + + def critical(self, msg=""): + ''' + Showing critical message. + @param msg: the message + ''' + self.logger.critical("[{}] {}".format(get_func_name(offset=1), msg)) diff --git a/utilities/logger_test.py b/utilities/logger_test.py new file mode 100644 index 0000000..55d1e84 --- /dev/null +++ b/utilities/logger_test.py @@ -0,0 +1,35 @@ +import os +import unittest + +from utilities.logger import Logger + + +class TestLogger(unittest.TestCase): + + @classmethod + def setUpClass(self): + self.logger = Logger() + self.log_filepath = "/tmp/test/tmplog.log" + + def test_logger_print(self): + self.logger.set_log_output_filepath(self.log_filepath) + self.logger.debug("A quirky message only developers care about") + self.logger.info("Curious users might want to know this") + self.logger.warn("Something is wrong and any user should be informed") + self.logger.error("Serious stuff, this is red for a reason") + self.logger.critical("OH NO everything is on fire") + + self.logger.debugging_on() + + self.logger.debug("A quirky message only developers care about") + + self.assertTrue(os.path.isfile(self.log_filepath)) + + @classmethod + def tearDownClass(self): + if os.path.isfile(self.log_filepath): + os.remove(self.log_filepath) + + +if __name__ == '__main__': + unittest.main()