feat: update logging

This commit is contained in:
2023-03-14 23:59:58 +03:00
parent ccdbb00eff
commit 9345b27aef
9 changed files with 180 additions and 31 deletions
Generated
+16 -1
View File
@@ -129,6 +129,21 @@ files = [
docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
[[package]]
name = "termcolor"
version = "2.2.0"
description = "ANSI color formatting for output in terminal"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "termcolor-2.2.0-py3-none-any.whl", hash = "sha256:91ddd848e7251200eac969846cbae2dacd7d71c2871e92733289e7e3666f48e7"},
{file = "termcolor-2.2.0.tar.gz", hash = "sha256:dfc8ac3f350788f23b2947b3e6cfa5a53b630b612e6cd8965a015a776020b99a"},
]
[package.extras]
tests = ["pytest", "pytest-cov"]
[[package]] [[package]]
name = "tomli" name = "tomli"
version = "2.0.1" version = "2.0.1"
@@ -156,4 +171,4 @@ files = [
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "b6fb61746a881b469139cbab13045cce95037f63ba5474b4a170facd83bb6053" content-hash = "95dd14e87750e4830d773cac5ea2f5b810d406fb34b1913243c8ba6a2145db54"
+3 -3
View File
@@ -5,7 +5,7 @@ import logging
import subprocess import subprocess
from pathlib import Path from pathlib import Path
logger = logging.getLogger("command") logger = logging.getLogger(">")
class Command: class Command:
@@ -17,14 +17,14 @@ class Command:
def run(self, basepath: Path): def run(self, basepath: Path):
args = [self._executor, *self._args] args = [self._executor, *self._args]
self._logger.info(" ".join(args)) self._logger.debug(" ".join(args))
result = subprocess.run( result = subprocess.run(
args, args,
cwd=basepath, cwd=basepath,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
) )
self._logstd(result.stdout, logging.DEBUG) self._logstd(result.stdout, logging.TRACE)
if result.returncode != 0: if result.returncode != 0:
self._logstd(result.stderr, logging.ERROR) self._logstd(result.stderr, logging.ERROR)
raise RuntimeError(result.stderr) raise RuntimeError(result.stderr)
+21 -2
View File
@@ -1,4 +1,7 @@
__all__ = ("Entry",) __all__ = (
"EntryCommandNotImplementedError",
"Entry",
)
import logging import logging
@@ -8,6 +11,10 @@ from typing import Optional
logger = logging.getLogger("entry") logger = logging.getLogger("entry")
class EntryCommandNotImplementedError(NotImplementedError):
pass
class Entry: class Entry:
_logger = logger _logger = logger
@@ -16,7 +23,19 @@ class Entry:
self._basepath = basepath self._basepath = basepath
def setup(self): def setup(self):
raise NotImplemented raise EntryCommandNotImplementedError("setup")
def lint(self):
raise EntryCommandNotImplementedError("lint")
def format(self):
raise EntryCommandNotImplementedError("format")
def test(self):
raise EntryCommandNotImplementedError("test")
def show(self):
self._logger.info(f"{self}")
def __repr__(self): def __repr__(self):
return f"{self._type} {str(self._basepath)}" return f"{self._type} {str(self._basepath)}"
+81
View File
@@ -0,0 +1,81 @@
import logging
from termcolor import colored
__all__ = (
"addLoggingLevel",
"CustomFormatter",
)
def addLoggingLevel(levelName, levelNum, methodName=None):
"""
Comprehensively adds a new logging level to the `logging` module and the
currently configured logging class.
`levelName` becomes an attribute of the `logging` module with the value
`levelNum`. `methodName` becomes a convenience method for both `logging`
itself and the class returned by `logging.getLoggerClass()` (usually just
`logging.Logger`). If `methodName` is not specified, `levelName.lower()` is
used.
To avoid accidental clobberings of existing attributes, this method will
raise an `AttributeError` if the level name is already an attribute of the
`logging` module or if the method name is already present
Example
-------
>>> addLoggingLevel('TRACE', logging.DEBUG - 5)
>>> logging.getLogger(__name__).setLevel("TRACE")
>>> logging.getLogger(__name__).trace('that worked')
>>> logging.trace('so did this')
>>> logging.TRACE
5
"""
if not methodName:
methodName = levelName.lower()
if hasattr(logging, levelName):
raise AttributeError("{} already defined in logging module".format(levelName))
if hasattr(logging, methodName):
raise AttributeError("{} already defined in logging module".format(methodName))
if hasattr(logging.getLoggerClass(), methodName):
raise AttributeError("{} already defined in logger class".format(methodName))
# This method was inspired by the answers to Stack Overflow post
# http://stackoverflow.com/q/2183233/2988730, especially
# http://stackoverflow.com/a/13638084/2988730
def logForLevel(self, message, *args, **kwargs):
if self.isEnabledFor(levelNum):
self._log(levelNum, message, args, **kwargs)
def logToRoot(message, *args, **kwargs):
logging.log(levelNum, message, *args, **kwargs)
logging.addLevelName(levelNum, levelName)
setattr(logging, levelName, levelNum)
setattr(logging.getLoggerClass(), methodName, logForLevel)
setattr(logging, methodName, logToRoot)
addLoggingLevel("TRACE", logging.DEBUG - 5)
class CustomFormatter(logging.Formatter):
def __init__(self, fmt):
super().__init__()
self.fmt = fmt
self.FORMATS = {
logging.TRACE: colored(self.fmt, "dark_grey"),
logging.DEBUG: colored(self.fmt, "grey"),
logging.INFO: colored(self.fmt, "blue"),
logging.WARNING: colored(self.fmt, "yellow"),
logging.ERROR: colored(self.fmt, "red"),
logging.CRITICAL: colored(self.fmt, "red", attrs=["bold"]),
}
def format(self, record: logging.LogRecord):
log_fmt = colored(record.name, "green") + " " + self.FORMATS.get(record.levelno)
formatter = logging.Formatter(log_fmt)
return formatter.format(record)
+24 -10
View File
@@ -1,37 +1,51 @@
import logging import logging
import os import os
from argparse import ArgumentParser from argparse import ArgumentParser
from ast import arg
from pathlib import Path from pathlib import Path
from project_manager.logging import CustomFormatter
from project_manager.manager import ProjectManager from project_manager.manager import ProjectManager
def run(): def run():
parser = ArgumentParser() parser = ArgumentParser()
parser.add_argument("task", nargs="?", default="show", choices=("show", "setup")) parser.add_argument(
"task",
nargs="?",
default="show",
choices=("show", "setup", "lint", "format", "test"),
)
parser.add_argument("-p", "--path", default=os.getcwd()) parser.add_argument("-p", "--path", default=os.getcwd())
parser.add_argument("-i", "--ignore-errors", action="store_true")
parser.add_argument("-v", "--verbose", action="count", default=1) parser.add_argument("-v", "--verbose", action="count", default=1)
parser.add_argument("-q", "--quiet", action="store_true") parser.add_argument("-q", "--quiet", action="store_true")
args = parser.parse_args() args = parser.parse_args()
logging_level = logging.WARNING logging_level = logging.WARNING
if args.verbose > 1: if args.verbose > 2:
logging_level = logging.TRACE
elif args.verbose > 1:
logging_level = logging.DEBUG logging_level = logging.DEBUG
elif args.verbose > 0: elif args.verbose > 0:
logging_level = logging.INFO logging_level = logging.INFO
if args.quiet: if args.quiet:
logging_level = logging.CRITICAL logging_level = logging.CRITICAL
logging.basicConfig(level=logging_level) if logging_level <= logging.INFO:
logging.getLogger(">").setLevel(logging.CRITICAL)
stdout_handler = logging.StreamHandler()
stdout_handler.setFormatter(CustomFormatter("%(message)s"))
logging.basicConfig(
level=logging_level,
format="%(message)s",
handlers=[stdout_handler],
)
basepath = Path(args.path) basepath = Path(args.path)
manager = ProjectManager(basepath, args.ignore_errors) manager = ProjectManager(basepath)
manager.run_command(args.task)
if args.task == "show":
manager.show()
elif args.task == "setup":
manager.setup()
if __name__ == "__main__": if __name__ == "__main__":
+20 -13
View File
@@ -1,32 +1,39 @@
import logging import logging
from collections import Counter
from pathlib import Path from pathlib import Path
from typing import Iterable from typing import Dict, Iterable
from .entry import Entry from .entry import Entry, EntryCommandNotImplementedError
from .nodejs.entry import NodejsEntry from .nodejs.entry import NodejsEntry
from .python.entry import PythonEntry from .python.entry import PythonEntry
from .resolver import EntryDetector, EntryResolver from .resolver import EntryDetector, EntryResolver
__all__ = ("ProjectManager",) __all__ = ("ProjectManager",)
logger = logging.getLogger("project")
class ProjectManager: class ProjectManager:
def __init__(self, basepath: Path, ignore_entry_error: bool = False): _logger = logger
def __init__(self, basepath: Path):
self._basepaht = basepath self._basepaht = basepath
self._resolver = EntryResolver(EntryDetector(PythonEntry, NodejsEntry)) self._resolver = EntryResolver(EntryDetector(PythonEntry, NodejsEntry))
self._ignore_entry_error = ignore_entry_error
def _iter_entries(self) -> Iterable[Entry]: def _iter_entries(self) -> Iterable[Entry]:
return self._resolver.resolve(self._basepaht) return self._resolver.resolve(self._basepaht)
def setup(self): def run_command(self, command: str) -> Dict[str, int]:
counter = Counter()
for entry in self._iter_entries(): for entry in self._iter_entries():
try: try:
entry.setup() getattr(entry, command)()
except Exception as exc: counter["success"] += 1
if not self._ignore_entry_error: except EntryCommandNotImplementedError:
raise exc self._logger.warn(f"{command} {entry}")
counter["skipped"] += 1
def show(self): except Exception:
for entry in self._iter_entries(): self._logger.exception(entry)
print(entry) counter["failed"] += 1
self._logger.info(f"result {dict(counter)}")
return counter
+1 -1
View File
@@ -13,7 +13,7 @@ class NodejsEntry(Entry):
super().__init__(self.TYPE, basepath) super().__init__(self.TYPE, basepath)
def setup(self): def setup(self):
self._logger.info(f"setup:{self}") self._logger.info(f"setup {self}")
NpmCommand.install_clean().run(self._basepath) NpmCommand.install_clean().run(self._basepath)
@classmethod @classmethod
+13 -1
View File
@@ -12,10 +12,22 @@ class PythonEntry(Entry):
super().__init__(self.TYPE, basepath) super().__init__(self.TYPE, basepath)
def setup(self): def setup(self):
self._logger.info(f"setup:{self}") self._logger.info(f"setup {self}")
PoetryCommand.env_use("3.8").run(self._basepath) PoetryCommand.env_use("3.8").run(self._basepath)
PoetryCommand.install().run(self._basepath) PoetryCommand.install().run(self._basepath)
def lint(self):
self._logger.info(f"lint {self}")
PoetryCommand("run", "black", "--check", "--diff", "./").run(self._basepath)
PoetryCommand("run", "isort", "--profile", "black", "--check-only", "./").run(
self._basepath
)
def format(self):
self._logger.info(f"format {self}")
PoetryCommand("run", "isort", "./").run(self._basepath)
PoetryCommand("run", "black", "./").run(self._basepath)
@classmethod @classmethod
def detect(cls, basepath: Path) -> Optional["PythonEntry"]: def detect(cls, basepath: Path) -> Optional["PythonEntry"]:
if (basepath / "pyproject.toml").exists(): if (basepath / "pyproject.toml").exists():
+1
View File
@@ -8,6 +8,7 @@ packages = [{ include = "project_manager" }]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.8" python = "^3.8"
termcolor = "^2.2.0"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
black = "^23.1.0" black = "^23.1.0"