From f50a955f636f316c16f8231434461724543b29cc Mon Sep 17 00:00:00 2001 From: Jerry Date: Fri, 31 May 2024 20:48:40 -0700 Subject: [PATCH] State logger (#352) * State logger Logs the state of an instance, e.g. txbuilder, when an exception is raised. Optionally, the state could be logged every time when the function is called if log level of the parent logger is set to DEBUG. State logger is added to `TransactionBuilder.build()`, `TransactionBuilder.build_and_sign()`, `ChainContext.submit_tx()`. * Remove redundant pytest log level --- Makefile | 2 +- poetry.lock | 16 ++++++++-------- pycardano/backend/base.py | 2 ++ pycardano/logging.py | 25 ++++++++++++++++++++++--- pycardano/txbuilder.py | 4 +++- pyproject.toml | 3 ++- test/pycardano/test_txbuilder.py | 25 +++++++++++++++++++++++++ 7 files changed, 63 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index e2195782..ff2c1ceb 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ clean-test: ## remove test and coverage artifacts rm -fr .pytest_cache test: ## runs tests - poetry run pytest -s -vv -n 4 + poetry run pytest -vv -n 4 test-single: ## runs tests with "single" markers poetry run pytest -s -vv -m single diff --git a/poetry.lock b/poetry.lock index f1b923b7..3638f9f9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1221,13 +1221,13 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "pytest" -version = "7.4.4" +version = "8.2.1" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"}, + {file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"}, ] [package.dependencies] @@ -1235,11 +1235,11 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +pluggy = ">=1.5,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" @@ -1681,4 +1681,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "d2358097a9b58857f96af603a807c51687d355e45f9cab28e157afb545eff67d" +content-hash = "1c0a4c2cc913d45e5e7af3b44dec5b9aadd9ba77f4582a50d11a01a02e98bb00" diff --git a/pycardano/backend/base.py b/pycardano/backend/base.py index 04fe0eff..aec6948a 100644 --- a/pycardano/backend/base.py +++ b/pycardano/backend/base.py @@ -6,6 +6,7 @@ from pycardano.address import Address from pycardano.exception import InvalidArgumentException +from pycardano.logging import log_state from pycardano.network import Network from pycardano.plutus import ExecutionUnits from pycardano.transaction import Transaction, UTxO @@ -160,6 +161,7 @@ def _utxos(self, address: str) -> List[UTxO]: """ raise NotImplementedError() + @log_state def submit_tx(self, tx: Union[Transaction, bytes, str]): """Submit a transaction to the blockchain. diff --git a/pycardano/logging.py b/pycardano/logging.py index 349939bd..2cd369e5 100644 --- a/pycardano/logging.py +++ b/pycardano/logging.py @@ -1,14 +1,14 @@ import logging -__all__ = ["logger"] +from pprintpp import pformat + +__all__ = ["logger", "log_state"] # create logger logger = logging.getLogger("PyCardano") -logger.setLevel(logging.WARNING) # create console handler and set level to debug ch = logging.StreamHandler() -ch.setLevel(logging.WARNING) # create formatter formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") @@ -18,3 +18,22 @@ # add ch to logger logger.addHandler(ch) + + +def log_state(func): + """Decorator to log the state of an object after its function call.""" + + def wrapper(obj, *args, **kwargs): + try: + output = func(obj, *args, **kwargs) + logger.debug( + f"Class: {obj.__class__}, method: {func}, state:\n {pformat(vars(obj), indent=2)}" + ) + return output + except Exception as e: + logger.warning( + f"Class: {obj.__class__}, method: {func}, state:\n {pformat(vars(obj), indent=2)}" + ) + raise e + + return wrapper diff --git a/pycardano/txbuilder.py b/pycardano/txbuilder.py index 2457c687..76c2168c 100644 --- a/pycardano/txbuilder.py +++ b/pycardano/txbuilder.py @@ -29,7 +29,7 @@ ) from pycardano.hash import DatumHash, ScriptDataHash, ScriptHash, VerificationKeyHash from pycardano.key import ExtendedSigningKey, SigningKey, VerificationKey -from pycardano.logging import logger +from pycardano.logging import log_state, logger from pycardano.metadata import AuxiliaryData from pycardano.nativescript import NativeScript, ScriptAll, ScriptAny, ScriptPubkey from pycardano.plutus import ( @@ -988,6 +988,7 @@ def _estimate_fee(self): return estimated_fee + @log_state def build( self, change_address: Optional[Address] = None, @@ -1357,6 +1358,7 @@ def _estimate_execution_units( return self.context.evaluate_tx(tx) + @log_state def build_and_sign( self, signing_keys: List[Union[SigningKey, ExtendedSigningKey]], diff --git a/pyproject.toml b/pyproject.toml index 1e8ccc4b..e0c6c38e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ docker = "^6.1.3" [tool.poetry.dev-dependencies] Sphinx = "^4.3.2" sphinx-rtd-theme = "^1.1.1" -pytest = "^7.4.4" +pytest = "^8.2.0" pytest-cov = "^4.0.0" flake8 = "^5.0.4" isort = "^5.11.4" @@ -67,6 +67,7 @@ markers = [ "single" ] + [tool.isort] profile = "black" diff --git a/test/pycardano/test_txbuilder.py b/test/pycardano/test_txbuilder.py index b09db3ca..373b57c5 100644 --- a/test/pycardano/test_txbuilder.py +++ b/test/pycardano/test_txbuilder.py @@ -1,4 +1,5 @@ import copy +import logging from dataclasses import replace from fractions import Fraction from test.pycardano.test_key import SK @@ -220,6 +221,30 @@ def test_tx_builder_raises_utxo_selection(chain_context): assert "{AssetName(b'NewToken'): 1}" in e.value.args[0] +def test_tx_builder_state_logger_warning_level(chain_context, caplog): + with caplog.at_level(logging.WARNING): + test_tx_builder_raises_utxo_selection(chain_context) + assert "WARNING" in caplog.text + + +def test_tx_builder_state_logger_error_level(chain_context, caplog): + with caplog.at_level(logging.ERROR): + test_tx_builder_raises_utxo_selection(chain_context) + assert "WARNING" not in caplog.text + + +def test_tx_builder_state_logger_info_level(chain_context, caplog): + with caplog.at_level(logging.INFO): + test_tx_builder_multi_asset(chain_context) + assert "DEBUG" not in caplog.text + + +def test_tx_builder_state_logger_debug_level(chain_context, caplog): + with caplog.at_level(logging.DEBUG): + test_tx_builder_multi_asset(chain_context) + assert "DEBUG" in caplog.text + + def test_tx_too_big_exception(chain_context): tx_builder = TransactionBuilder(chain_context) sender = "addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x"