diff --git a/CHANGELOG.rst b/CHANGELOG.rst deleted file mode 100644 index 88e5495..0000000 --- a/CHANGELOG.rst +++ /dev/null @@ -1,277 +0,0 @@ -CHANGELOG -========= -Version 3.3.1 -------------- -* Python 3.7 support validated - -Version 3.3.0 -------------- -* Type hints and stubs -* PEP0518 -* Deprecation of `*args` for logwrap -* Fix empty `*args` and `**kwargs` -* allow override for arguments repr processing (pre- and post-processing) - -Version 3.2.0 -------------- -* Return logwrap function back with 2 branches: with arguments and not due to - instances of classes are not converted to methods during class compilation. - -Unit tests for found issue created. - -Version 3.1.0 -------------- -* Drop logwrap function. Use class as-is. `logwrap = LogWrap` (No API changes). - -Version 3.0.2 -------------- -* Fix package README. - -Version 3.0.1 -------------- -* Fix documents. -* Drop useless arguments. - -Version 3.0.0 -------------- -* Drop formatters. - -Less magic (dict of methods). - -.. warning:: ``PrettyFormat`` API changed: formatters are set in subclasses. - -Version 2.7.3 -------------- -Return cythonize. Error reason failed, crashing code will not be compiled. - -Version 2.7.2 -------------- -Urgent fix: cythonized code crashes on processing async code. - -Version 2.7.0 -------------- -* Python 3.3 support is ended. EOL is coming, test was run rarely. -* Move data from setup.cfg to __init__.py and setup.py - -Version 2.6.2 -------------- -Fix no __init__ in wheel. - -Version 2.6.1 -------------- -Python 2.7 compile failed -> exclude compile. - -Version 2.6.0 -------------- -* Cytonize, if possible. - -Version 2.5.1 -------------- -Base class for decorator has been extracted. -Requirements is extracted to requirements.txt -Update setup.py from doctedsted get_simple_vars_from_src. - -Version 2.5.0 -------------- -* Typing is required for all python versions due to: - June 15, 2018: Python 3.7.0 final release, the typing module is no longer provisional -* Use slots due to usage of multiple instances per project - - -Version 2.4.2 -------------- -* Use `ast.literal_eval` instead of `eval` in isolated env for `__init__.py` parsing. -* Do not rely on `six` in `setup.py`. - -Version 2.4.1 -------------- -* Support `frozenset` expansion. -* Use ast for version extraction and do not use danger of if's in `__init__.py` - installed without dependencies package should crash on import. - -Version 2.4.0 -------------- -* PyPy3 correct support and python 3.3 external asyncio. -* Move the most metadata from `setup.py` to `setup.cfg`. -* Drop deprecated code. - -Version 2.3.5 -------------- -* Allow to run setup.py on not installed package (`__init__.py` imports fix). -* Simplify code - -Version 2.3.4 -------------- -* Revert setup from PEP0508 to PEP0426 due to external tools support limitation - -Version 2.3.3 -------------- -* API docs rewritten by hand due to automatic generation limitations. - -Version 2.3.2 -------------- -* Setup was updated in conform to PEP0508 - -* AsyncLogWrap/async_logwrap deletion moved on v 2.4 as destructive action. - -* Mark, that setup requires not pre-historic setuptools. - -Version 2.3.0 -------------- -* logwrap/LogWrap transparently supports coroutines on py3.4+. AsyncLogWrap/async_logwrap is deprecated and will be deleted in version 2.3.2. - -Version 2.2.1 -------------- -* Use six library for compatibility options (anyway, it's required by modern setuptools). - -Version 2.2.0 -------------- -* Async for python 3.4 - -* Object model - -* Ignore list for exceptions - -* Possibility to omit arguments log (call/exception/both) - -* Possibility to omit result object log - -Version 2.1.0 -------------- -* Async version can run sync code (but anyway, decorator returns coroutine) - -* Sync version produces warning on async code - -Version 2.0.0 -------------- -* Async version is added - -* pep8 tests is moved to Python 3.6 due to `async_logwrap` syntax incompatibility with python < 3.5 - -Version 1.3.0 -------------- -* Allowed to blacklist call arguments - -Version 1.2.0 -------------- -* Fix dict keys length calculation - -* Simplify API - -* Expand documentation - -Version 1.1.1 -------------- -* documentation fix - -Version 1.1.0 -------------- -* pretty_str has been implemented - -Version 1.0.6 -------------- -* Technical bump - -Version 1.0.5 -------------- -* Drop test-related code from package and setup.py - -Version 1.0.4 -------------- -* divide process and final result call - -* allow override behavior per type by magic __pretty_{}__ method - -* PrettyFormat class is public - -Note: No major bump until ready: - pretty_str implementation for usage in __str__ and print calls - -Version 1.0.3 -------------- -* Technical bump: was a false-negative ci results - -Version 1.0.2 -------------- -* Rework requirements: remove magic - -* Start extending pretty_repr: object model - -Version 1.0.1 -------------- -* Circle CI was disabled: all has been moved to the Travis - -* Covered several special cases by unit tests - -* ReadTheDocs now working correctly - -* Fixed legacy commentaries at docstring - -Version 1.0.0 -------------- -* Drop historical code and update documentation - -Development was started with re-using of historic code, -but now it's clean package with minimal requirements -(funcsigs looks like copy-paste from inspect.signature + adoption to use on python 2.7 -(Enum is not available, not using enum34 package)). - -* Mark package as stable (tested by unit tests and external run). - -Version 0.9.0 -------------- -* get_arg_names and get_call_args now presents only for historical reasons - -* logwrap now logs argument types as commentaries -(POSITIONAL_ONLY (builtins only) | POSITIONAL_OR_KEYWORD (standard) | VAR_POSITIONAL (e.g. *args) | KEYWORD_ONLY (Python 3+ only) | VAR_KEYWORD (e.g. **kwargs)) - -Version 0.8.5 -------------- -* Use funcsigs instead of manual reimplementation of inspect.signature & supplemental - -* Implement parsing of functions and methods (log interfaces in additional to standard repr) - -* internal modules was moved to protected scope - -Version 0.8.0 -------------- -* Drop six requirement - -Version 0.7.3 -------------- -* Documentation update only - -Version 0.7.2 -------------- -Internal bump for CI systems check - -Version 0.7.1 -------------- - -* Tests is included in package - -* Docstrings and misprints in documents fixed - -* CI CD - -Version 0.7 ------------ -Functional changes: - -* Fixed difference of repr empty set() between python versions: replace by string `set()` - - -CI and structure changes: - -* Added CHANGELOG - -* Use CirceCI for pylint and coverage upload (uploaded from python 2.7) - -* LICENSE file has been replaced by template from GitHub due to parsing issues - -Version 0.6 ------------ -* Started stabilization: package structure, tests, CI - -Prior to 0.6 ------------- -Preparing package, CI and fixing found issues. diff --git a/README.rst b/README.rst index b6f88f1..a3a7619 100644 --- a/README.rst +++ b/README.rst @@ -80,6 +80,7 @@ Argumented usage with arguments from signature: blacklisted_exceptions=None, # Exceptions to skip in log log_call_args=True, # Log call arguments before call log_call_args_on_exc=True, # Log call arguments if exception happens + log_traceback: bool = True, # Log traceback if exception happens log_result_obj=True, # Log result object ) @@ -181,6 +182,7 @@ Example construction and read from test: log_call.blacklisted_exceptions == [] log_call.log_call_args == True log_call.log_call_args_on_exc == True + log_call.log_traceback == True log_call.log_result_obj == True On object change, variable types is validated. diff --git a/doc/source/logwrap.rst b/doc/source/logwrap.rst index d144a09..4c3e98b 100644 --- a/doc/source/logwrap.rst +++ b/doc/source/logwrap.rst @@ -12,7 +12,7 @@ API: Decorators: `LogWrap` class and `logwrap` function. .. versionadded:: 2.2.0 - .. py:method:: __init__(func=None, *, log=logging.getLogger('logwrap'), log_level=logging.DEBUG, exc_level=logging.ERROR, max_indent=20, spec=None, blacklisted_names=None, blacklisted_exceptions=None, log_call_args=True, log_call_args_on_exc=True, log_result_obj=True, ) + .. py:method:: __init__(func=None, *, log=logging.getLogger('logwrap'), log_level=logging.DEBUG, exc_level=logging.ERROR, max_indent=20, spec=None, blacklisted_names=None, blacklisted_exceptions=None, log_call_args=True, log_call_args_on_exc=True, log_traceback=True, log_result_obj=True, ) :param func: function to wrap :type func: typing.Optional[typing.Callable] @@ -43,12 +43,15 @@ API: Decorators: `LogWrap` class and `logwrap` function. :type log_call_args: bool :param log_call_args_on_exc: log call arguments if exception raised. :type log_call_args_on_exc: bool + :param log_traceback: log traceback on excaption in addition to failure info + :type log_traceback: bool :param log_result_obj: log result of function call. :type log_result_obj: bool .. versionchanged:: 3.3.0 Extract func from log and do not use Union. .. versionchanged:: 3.3.0 Deprecation of `*args` .. versionchanged:: 4.0.0 Drop of `*args` + .. versionchanged:: 4.9.2 log_traceback parameter .. py:method:: pre_process_param(self, arg) @@ -92,6 +95,7 @@ API: Decorators: `LogWrap` class and `logwrap` function. ``typing.List[typing.Type[Exception]]``, modified via mutability .. py:attribute:: log_call_args .. py:attribute:: log_call_args_on_exc + .. py:attribute:: log_traceback .. py:attribute:: log_result_obj .. py:attribute:: _func diff --git a/logwrap/__init__.py b/logwrap/__init__.py index e8954e0..0e18cde 100644 --- a/logwrap/__init__.py +++ b/logwrap/__init__.py @@ -37,7 +37,7 @@ "bind_args_kwargs", ) -__version__ = "4.9.1" +__version__ = "4.9.2" __author__ = "Alexey Stepanov" __author_email__ = "penguinolog@gmail.com" __maintainers__ = { diff --git a/logwrap/_log_wrap.py b/logwrap/_log_wrap.py index 2765bba..517361d 100644 --- a/logwrap/_log_wrap.py +++ b/logwrap/_log_wrap.py @@ -184,6 +184,7 @@ class LogWrap(_class_decorator.BaseDecorator): "__spec", "__log_call_args", "__log_call_args_on_exc", + "__log_traceback", "__log_result_obj", ) @@ -200,6 +201,7 @@ def __init__( blacklisted_exceptions: typing.Optional[typing.Iterable[typing.Type[Exception]]] = None, log_call_args: bool = True, log_call_args_on_exc: bool = True, + log_traceback: bool = True, log_result_obj: bool = True ) -> None: """Log function calls and return values. @@ -230,10 +232,13 @@ def __init__( :type log_call_args: bool :param log_call_args_on_exc: log call arguments if exception raised. :type log_call_args_on_exc: bool + :param log_traceback: log traceback on excaption in addition to failure info + :type log_traceback: bool :param log_result_obj: log result of function call. :type log_result_obj: bool .. versionchanged:: 3.3.0 Extract func from log and do not use Union. + .. versionchanged:: 4.9.2 log_traceback parameter """ super(LogWrap, self).__init__(func=func) @@ -255,6 +260,7 @@ def __init__( self.__spec = spec or self._func self.__log_call_args = log_call_args self.__log_call_args_on_exc = log_call_args_on_exc + self.__log_traceback = log_traceback self.__log_result_obj = log_result_obj # We are not interested to pass any arguments to object @@ -375,6 +381,26 @@ def log_call_args_on_exc(self, val: bool) -> None: raise TypeError("Unexpected type: {}. Should be {}.".format(val.__class__.__name__, bool.__name__)) self.__log_call_args_on_exc = val + @property + def log_traceback(self) -> bool: + """Flag: log traceback on exception. + + :rtype: bool + """ + return self.__log_traceback + + @log_traceback.setter + def log_traceback(self, val: bool) -> None: + """Flag: log traceback on exception. + + :param val: Enable flag + :type val: bool + :raises TypeError: Value is not bool + """ + if not isinstance(val, bool): + raise TypeError("Unexpected type: {}. Should be {}.".format(val.__class__.__name__, bool.__name__)) + self.__log_traceback = val + @property def log_result_obj(self) -> bool: """Flag: log result object. @@ -565,7 +591,9 @@ def _make_exc_record(self, name: str, arguments: str) -> None: self._logger.log( # type: ignore level=self.exc_level, msg="Failed: \n{name!r}({arguments})\n{tb_text}".format( - name=name, arguments=arguments if self.log_call_args_on_exc else "", tb_text=tb_text + name=name, + arguments=arguments if self.log_call_args_on_exc else "", + tb_text=tb_text if self.log_traceback else "", ), exc_info=False, ) @@ -641,6 +669,7 @@ def logwrap( blacklisted_exceptions: typing.Optional[typing.List[typing.Type[Exception]]] = None, log_call_args: bool = True, log_call_args_on_exc: bool = True, + log_traceback: bool = True, log_result_obj: bool = True ) -> LogWrap: """Overload: with no func.""" @@ -660,6 +689,7 @@ def logwrap( blacklisted_exceptions: typing.Optional[typing.List[typing.Type[Exception]]] = None, log_call_args: bool = True, log_call_args_on_exc: bool = True, + log_traceback: bool = True, log_result_obj: bool = True ) -> typing.Callable: """Overload: func provided.""" @@ -679,6 +709,7 @@ def logwrap( # noqa: F811 # pylint: disable=unexpected-keyword-arg, no-value-f blacklisted_exceptions: typing.Optional[typing.Iterable[typing.Type[Exception]]] = None, log_call_args: bool = True, log_call_args_on_exc: bool = True, + log_traceback: bool = True, log_result_obj: bool = True ) -> typing.Union[LogWrap, typing.Callable]: """Log function calls and return values. Python 3.4+ version. @@ -708,6 +739,8 @@ def logwrap( # noqa: F811 # pylint: disable=unexpected-keyword-arg, no-value-f :type log_call_args: bool :param log_call_args_on_exc: log call arguments if exception raised. :type log_call_args_on_exc: bool + :param log_traceback: log traceback on excaption in addition to failure info + :type log_traceback: bool :param log_result_obj: log result of function call. :type log_result_obj: bool :return: built real decorator. @@ -716,6 +749,7 @@ def logwrap( # noqa: F811 # pylint: disable=unexpected-keyword-arg, no-value-f .. versionchanged:: 3.3.0 Extract func from log and do not use Union. .. versionchanged:: 3.3.0 Deprecation of *args .. versionchanged:: 4.0.0 Drop of *args + .. versionchanged:: 4.9.2 log_traceback parameter """ wrapper = LogWrap( log=log, @@ -727,6 +761,7 @@ def logwrap( # noqa: F811 # pylint: disable=unexpected-keyword-arg, no-value-f blacklisted_exceptions=blacklisted_exceptions, log_call_args=log_call_args, log_call_args_on_exc=log_call_args_on_exc, + log_traceback=log_traceback, log_result_obj=log_result_obj, ) if func is not None: diff --git a/test/test_log_wrap.py b/test/test_log_wrap.py index 2401cc2..baca1f0 100644 --- a/test/test_log_wrap.py +++ b/test/test_log_wrap.py @@ -14,7 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -# pylint: disable=missing-docstring, unused-argument +# pylint: disable=missing-docstring, unused-argument, no-self-argument, no-self-use, keyword-arg-before-vararg """Python independent logwrap tests.""" @@ -22,11 +22,10 @@ import io import logging import unittest +from unittest import mock import logwrap -from unittest import mock - class AnyStringWith(str): def __eq__(self, other): @@ -341,7 +340,7 @@ def func(): ) def test_011_method(self): - class Tst(object): + class Tst: @logwrap.logwrap def func(tst_self): return 'No args' @@ -672,7 +671,7 @@ def func(): log.mock_calls, ) - def test_empty_args_kwargs(self): + def test_021_empty_args_kwargs(self): @logwrap.logwrap def func(*args, **kwargs): return 'No args' @@ -692,6 +691,45 @@ def func(*args, **kwargs): self.stream.getvalue(), ) + def test_022_disable_traceback(self): + new_logger = mock.Mock(spec=logging.Logger, name='logger') + log = mock.Mock(name='log') + new_logger.attach_mock(log, 'log') + + @logwrap.logwrap( + log=new_logger, + log_traceback=False + ) + def func(): + raise TypeError('Blacklisted') + + with self.assertRaises(TypeError): + func() + + self.assertEqual( + [ + mock.call( + level=logging.DEBUG, + msg="Calling: \n" + "'func'()" + ), + mock.call( + level=logging.ERROR, + msg=AnyStringWith("Failed: \n'func'()"), + exc_info=False + ), + ], + log.mock_calls, + ) + self.assertNotEqual( + mock.call( + level=logging.ERROR, + msg=AnyStringWith("Failed: \n'func'()\nTraceback (most recent call last):"), + exc_info=False + ), + log.mock_calls[1], + ) + class TestObject(unittest.TestCase): def test_001_basic(self): @@ -703,6 +741,7 @@ def test_001_basic(self): self.assertEqual(log_call.blacklisted_exceptions, []) self.assertTrue(log_call.log_call_args) self.assertTrue(log_call.log_call_args_on_exc) + self.assertTrue(log_call.log_traceback) self.assertTrue(log_call.log_result_obj) log_call.log_level = logging.INFO @@ -712,6 +751,7 @@ def test_001_basic(self): log_call.blacklisted_exceptions.append(IOError) log_call.log_call_args = False log_call.log_call_args_on_exc = False + log_call.log_traceback = False log_call.log_result_obj = False self.assertEqual(log_call.log_level, logging.INFO) @@ -721,6 +761,7 @@ def test_001_basic(self): self.assertEqual(log_call.blacklisted_exceptions, [IOError]) self.assertFalse(log_call.log_call_args) self.assertFalse(log_call.log_call_args_on_exc) + self.assertFalse(log_call.log_traceback) self.assertFalse(log_call.log_result_obj) with self.assertRaises(TypeError):