logwrap
=======
.. image:: https://github.com/python-useful-helpers/logwrap/workflows/Python%20package/badge.svg
:target: https://github.com/python-useful-helpers/logwrap/actions
.. image:: https://coveralls.io/repos/github/python-useful-helpers/logwrap/badge.svg?branch=master
:target: https://coveralls.io/github/python-useful-helpers/logwrap?branch=master
.. image:: https://readthedocs.org/projects/logwrap/badge/?version=latest
:target: http://logwrap.readthedocs.io/
:alt: Documentation Status
.. image:: https://img.shields.io/pypi/v/logwrap.svg
:target: https://pypi.python.org/pypi/logwrap
.. image:: https://img.shields.io/pypi/pyversions/logwrap.svg
:target: https://pypi.python.org/pypi/logwrap
.. image:: https://img.shields.io/pypi/status/logwrap.svg
:target: https://pypi.python.org/pypi/logwrap
.. image:: https://img.shields.io/github/license/python-useful-helpers/logwrap.svg
:target: https://raw.githubusercontent.com/python-useful-helpers/logwrap/master/LICENSE
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/ambv/black
logwrap is a helper for logging in human-readable format function arguments and call result on function call.
Why? Because logging of `*args, **kwargs` become useless with project grow and you need more details in call log.
Cons:
* Log records are not single line.
Pros:
* Log records are not single 100500 symbols length line.
(Especially actual for testing/development environments and for Kibana users).
* Service free: job is done by this library and it's dependencies. It works at virtualenv
* Free software: Apache license
* Open Source: https://github.com/python-useful-helpers/logwrap
* PyPI packaged: https://pypi.python.org/pypi/logwrap
* Self-documented code: docstrings with types in comments
* Tested: see bages on top
This package includes helpers:
* `logwrap` - main helper. The same is `LogWrap`.
* `LogWrap` - class with `logwrap` implementation. May be used directly.
* `pretty_repr`
* `pretty_str`
* `PrettyFormat`
* `LogOnAccess` - property with logging on successful get/set/delete or failure.
Usage
=====
logwrap
-------
The main decorator. Could be used as not argumented (`@logwrap.logwrap`) and argumented (`@logwrap.logwrap()`).
Not argumented usage simple calls with default values for all positions.
.. note:: Argumens should be set via keywords only.
Argumented usage with arguments from signature:
.. code-block:: python
@logwrap.logwrap(
log=None, # if not set: try to find LOGGER, LOG, logger or log object in target module and use it if it logger instance. Fallback: logger named logwrap
log_level=logging.DEBUG,
exc_level=logging.ERROR,
max_indent=20, # forwarded to the pretty_repr,
max_iter=0, # forwarded to the pretty_repr, max number of items in the Iterable before ellipsis. Unlimited if 0.
blacklisted_names=None, # list argument names, which should be dropped from log
blacklisted_exceptions=None, # Exceptions to skip details in log (no traceback, no exception details - just class name)
log_call_args=True, # Log call arguments before call
log_call_args_on_exc=True, # Log call arguments if exception happens
log_traceback = True, # Log traceback if exception happens
log_result_obj=True, # Log result object
)
Usage examples:
.. code-block:: python
@logwrap.logwrap()
def foo():
pass
is equal to:
.. code-block:: python
@logwrap.logwrap
def foo():
pass
Get decorator for use without parameters:
.. code-block:: python
get_logs = logwrap.logwrap() # set required parameters via arguments
type(get_logs) == LogWrap # All logic is implemented in LogWrap class starting from version 2.2.0
@get_logs
def foo():
pass
Call example (python 3.8):
.. code-block:: python
import logwrap
@logwrap.logwrap
def example_function1(
arg0: str,
/,
arg1: str,
arg2: str='arg2',
*args,
kwarg1: str,
kwarg2: str='kwarg2',
**kwargs
) -> tuple():
return (arg0, arg1, arg2, args, kwarg1, kwarg2, kwargs)
example_function1('arg0', 'arg1', kwarg1='kwarg1', kwarg3='kwarg3')
This code during execution will produce log records:
::
Calling:
'example_function1'(
# POSITIONAL_ONLY:
arg0='arg0', # type: str
# POSITIONAL_OR_KEYWORD:
arg1='arg1', # type: str
arg2='arg2', # type: str
# VAR_POSITIONAL:
args=(),
# KEYWORD_ONLY:
kwarg1='kwarg1', # type: str
kwarg2='kwarg2', # type: str
# VAR_KEYWORD:
kwargs={
'kwarg3': 'kwarg3',
},
)
Done: 'example_function1' with result:
(
'arg0',
'arg1',
'arg2',
(),
'kwarg1',
'kwarg2',
{
'kwarg3': 'kwarg3',
},
)
LogWrap
-------
Example construction and read from test:
.. code-block:: python
log_call = logwrap.LogWrap()
log_call.log_level == logging.DEBUG
log_call.exc_level == logging.ERROR
log_call.max_indent == 20
log_call.blacklisted_names == []
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.
In special cases, when special processing required for parameters logging (hide or change parameters in log),
it can be done by override `pre_process_param` and `post_process_param`.
See API documentation for details.
pretty_repr
-----------
This is specified helper for making human-readable repr on complex objects.
Signature is self-documenting:
.. code-block:: python
def pretty_repr(
src, # object for repr
indent=0, # start indent
no_indent_start=False, # do not indent the first level
max_indent=20, # maximum allowed indent level
indent_step=4, # step between indents
)
pretty_str
----------
This is specified helper for making human-readable str on complex objects.
Signature is self-documenting:
.. code-block:: python
def pretty_str(
src, # object for __str__
indent=0, # start indent
no_indent_start=False, # do not indent the first level
max_indent=20, # maximum allowed indent level
indent_step=4, # step between indents
)
Limitations:
Dict like objects is always marked inside `{}` for readability, even if it is `collections.OrderedDict` (standard repr as list of tuples).
Iterable types is not declared, only brackets is used.
String and bytes looks the same (its __str__, not __repr__).
PrettyFormat
------------
PrettyFormat is the main formatting implementation class.
`pretty_repr` and `pretty_str` uses instances of subclasses `PrettyRepr` and `PrettyStr` from this class.
This class is mostly exposed for typing reasons.
Object signature:
.. code-block:: python
def __init__(
self,
max_indent=20, # maximum allowed indent level
indent_step=4, # step between indents
)
Callable object (`PrettyFormat` instance) signature:
.. code-block:: python
def __call__(
self,
src, # object for repr
indent=0, # start indent
no_indent_start=False # do not indent the first level
)
Adopting your code
------------------
pretty_repr behavior could be overridden for your classes by implementing specific magic method:
.. code-block:: python
def __pretty_repr__(
self,
parser # PrettyFormat class instance,
indent # start indent,
no_indent_start # do not indent the first level
):
return ...
This method will be executed instead of __repr__ on your object.
.. code-block:: python
def __pretty_str__(
self,
parser # PrettyFormat class instance,
indent # start indent,
no_indent_start # do not indent the first level
):
return ...
This method will be executed instead of __str__ on your object.
LogOnAccess
-----------
This special case of property is useful in cases, where a lot of properties should be logged by similar way without writing a lot of code.
Basic API is conform with `property`, but in addition it is possible to customize logger, log levels and log conditions.
Usage examples:
1. Simple usage. All by default.
logger is re-used:
* from instance if available with names `logger` or `log`,
* from instance module if available with names `LOGGER`, `log`,
* else used internal `logwrap.log_on_access` logger.
.. code-block:: python
import logging
class Target(object):
def init(self, val='ok')
self.val = val
self.logger = logging.get_logger(self.__class__.__name__) # Single for class, follow subclassing
def __repr__(self):
return "{cls}(val={self.val})".format(cls=self.__class__.__name__, self=self)
@logwrap.LogOnAccess
def ok(self):
return self.val
@ok.setter
def ok(self, val):
self.val = val
@ok.deleter
def ok(self):
self.val = ""
2. Use with global logger for class:
.. code-block:: python
class Target(object):
def init(self, val='ok')
self.val = val
def __repr__(self):
return "{cls}(val={self.val})".format(cls=self.__class__.__name__, self=self)
@logwrap.LogOnAccess
def ok(self):
return self.val
@ok.setter
def ok(self, val):
self.val = val
@ok.deleter
def ok(self):
self.val = ""
ok.logger = 'test_logger'
ok.log_level = logging.INFO
ok.exc_level = logging.ERROR
ok.log_object_repr = True # As by default
ok.log_before = True # As by default
ok.log_success = True # As by default
ok.log_failure = True # As by default
ok.log_traceback = True # As by default
ok.override_name = None # As by default: use original name
Testing
=======
The main test mechanism for the package `logwrap` is using `tox`.
Available environments can be collected via `tox -l`
CI/CD systems
=============
`GitHub: <https://github.com/python-useful-helpers/logwrap/actions>`_ is used for functional tests.
Raw data
{
"_id": null,
"home_page": "https://github.com/python-useful-helpers/logwrap",
"name": "logwrap",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8.0",
"maintainer_email": "Aleksei Stepanov <penguinolog@gmail.com>, Antonio Esposito <esposito.cloud@gmail.com>, Dennis Dmitriev <dis-xcom@gmail.com>",
"keywords": "logging, debugging, development",
"author": null,
"author_email": "Aleksei Stepanov <penguinolog@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/3f/19/88de476d08b3fd3eb201fdd53909c1f16ba01c03810a5726ffb0a788fe94/logwrap-11.1.1.tar.gz",
"platform": null,
"description": "logwrap\n=======\n\n.. image:: https://github.com/python-useful-helpers/logwrap/workflows/Python%20package/badge.svg\n :target: https://github.com/python-useful-helpers/logwrap/actions\n.. image:: https://coveralls.io/repos/github/python-useful-helpers/logwrap/badge.svg?branch=master\n :target: https://coveralls.io/github/python-useful-helpers/logwrap?branch=master\n.. image:: https://readthedocs.org/projects/logwrap/badge/?version=latest\n :target: http://logwrap.readthedocs.io/\n :alt: Documentation Status\n.. image:: https://img.shields.io/pypi/v/logwrap.svg\n :target: https://pypi.python.org/pypi/logwrap\n.. image:: https://img.shields.io/pypi/pyversions/logwrap.svg\n :target: https://pypi.python.org/pypi/logwrap\n.. image:: https://img.shields.io/pypi/status/logwrap.svg\n :target: https://pypi.python.org/pypi/logwrap\n.. image:: https://img.shields.io/github/license/python-useful-helpers/logwrap.svg\n :target: https://raw.githubusercontent.com/python-useful-helpers/logwrap/master/LICENSE\n.. image:: https://img.shields.io/badge/code%20style-black-000000.svg\n :target: https://github.com/ambv/black\n\n\nlogwrap is a helper for logging in human-readable format function arguments and call result on function call.\nWhy? Because logging of `*args, **kwargs` become useless with project grow and you need more details in call log.\n\nCons:\n\n* Log records are not single line.\n\nPros:\n\n* Log records are not single 100500 symbols length line.\n (Especially actual for testing/development environments and for Kibana users).\n* Service free: job is done by this library and it's dependencies. It works at virtualenv\n* Free software: Apache license\n* Open Source: https://github.com/python-useful-helpers/logwrap\n* PyPI packaged: https://pypi.python.org/pypi/logwrap\n* Self-documented code: docstrings with types in comments\n* Tested: see bages on top\n\nThis package includes helpers:\n\n* `logwrap` - main helper. The same is `LogWrap`.\n\n* `LogWrap` - class with `logwrap` implementation. May be used directly.\n\n* `pretty_repr`\n\n* `pretty_str`\n\n* `PrettyFormat`\n\n* `LogOnAccess` - property with logging on successful get/set/delete or failure.\n\nUsage\n=====\n\nlogwrap\n-------\nThe main decorator. Could be used as not argumented (`@logwrap.logwrap`) and argumented (`@logwrap.logwrap()`).\nNot argumented usage simple calls with default values for all positions.\n\n.. note:: Argumens should be set via keywords only.\n\nArgumented usage with arguments from signature:\n\n.. code-block:: python\n\n @logwrap.logwrap(\n log=None, # if not set: try to find LOGGER, LOG, logger or log object in target module and use it if it logger instance. Fallback: logger named logwrap\n log_level=logging.DEBUG,\n exc_level=logging.ERROR,\n max_indent=20, # forwarded to the pretty_repr,\n max_iter=0, # forwarded to the pretty_repr, max number of items in the Iterable before ellipsis. Unlimited if 0.\n blacklisted_names=None, # list argument names, which should be dropped from log\n blacklisted_exceptions=None, # Exceptions to skip details in log (no traceback, no exception details - just class name)\n log_call_args=True, # Log call arguments before call\n log_call_args_on_exc=True, # Log call arguments if exception happens\n log_traceback = True, # Log traceback if exception happens\n log_result_obj=True, # Log result object\n )\n\nUsage examples:\n\n.. code-block:: python\n\n @logwrap.logwrap()\n def foo():\n pass\n\nis equal to:\n\n.. code-block:: python\n\n @logwrap.logwrap\n def foo():\n pass\n\nGet decorator for use without parameters:\n\n.. code-block:: python\n\n get_logs = logwrap.logwrap() # set required parameters via arguments\n\n type(get_logs) == LogWrap # All logic is implemented in LogWrap class starting from version 2.2.0\n\n @get_logs\n def foo():\n pass\n\nCall example (python 3.8):\n\n.. code-block:: python\n\n import logwrap\n\n @logwrap.logwrap\n def example_function1(\n arg0: str,\n /,\n arg1: str,\n arg2: str='arg2',\n *args,\n kwarg1: str,\n kwarg2: str='kwarg2',\n **kwargs\n ) -> tuple():\n return (arg0, arg1, arg2, args, kwarg1, kwarg2, kwargs)\n\n example_function1('arg0', 'arg1', kwarg1='kwarg1', kwarg3='kwarg3')\n\nThis code during execution will produce log records:\n\n::\n\n Calling:\n 'example_function1'(\n # POSITIONAL_ONLY:\n arg0='arg0', # type: str\n # POSITIONAL_OR_KEYWORD:\n arg1='arg1', # type: str\n arg2='arg2', # type: str\n # VAR_POSITIONAL:\n args=(),\n # KEYWORD_ONLY:\n kwarg1='kwarg1', # type: str\n kwarg2='kwarg2', # type: str\n # VAR_KEYWORD:\n kwargs={\n 'kwarg3': 'kwarg3',\n },\n )\n Done: 'example_function1' with result:\n\n (\n 'arg0',\n 'arg1',\n 'arg2',\n (),\n 'kwarg1',\n 'kwarg2',\n {\n 'kwarg3': 'kwarg3',\n },\n )\n\nLogWrap\n-------\nExample construction and read from test:\n\n.. code-block:: python\n\n log_call = logwrap.LogWrap()\n log_call.log_level == logging.DEBUG\n log_call.exc_level == logging.ERROR\n log_call.max_indent == 20\n log_call.blacklisted_names == []\n log_call.blacklisted_exceptions == []\n log_call.log_call_args == True\n log_call.log_call_args_on_exc == True\n log_call.log_traceback == True\n log_call.log_result_obj == True\n\nOn object change, variable types is validated.\n\nIn special cases, when special processing required for parameters logging (hide or change parameters in log),\nit can be done by override `pre_process_param` and `post_process_param`.\n\nSee API documentation for details.\n\n\npretty_repr\n-----------\nThis is specified helper for making human-readable repr on complex objects.\nSignature is self-documenting:\n\n.. code-block:: python\n\n def pretty_repr(\n src, # object for repr\n indent=0, # start indent\n no_indent_start=False, # do not indent the first level\n max_indent=20, # maximum allowed indent level\n indent_step=4, # step between indents\n )\n\n\npretty_str\n----------\nThis is specified helper for making human-readable str on complex objects.\nSignature is self-documenting:\n\n.. code-block:: python\n\n def pretty_str(\n src, # object for __str__\n indent=0, # start indent\n no_indent_start=False, # do not indent the first level\n max_indent=20, # maximum allowed indent level\n indent_step=4, # step between indents\n )\n\nLimitations:\n Dict like objects is always marked inside `{}` for readability, even if it is `collections.OrderedDict` (standard repr as list of tuples).\n\n Iterable types is not declared, only brackets is used.\n\n String and bytes looks the same (its __str__, not __repr__).\n\nPrettyFormat\n------------\nPrettyFormat is the main formatting implementation class.\n`pretty_repr` and `pretty_str` uses instances of subclasses `PrettyRepr` and `PrettyStr` from this class.\nThis class is mostly exposed for typing reasons.\nObject signature:\n\n.. code-block:: python\n\n def __init__(\n self,\n max_indent=20, # maximum allowed indent level\n indent_step=4, # step between indents\n )\n\nCallable object (`PrettyFormat` instance) signature:\n\n.. code-block:: python\n\n def __call__(\n self,\n src, # object for repr\n indent=0, # start indent\n no_indent_start=False # do not indent the first level\n )\n\nAdopting your code\n------------------\npretty_repr behavior could be overridden for your classes by implementing specific magic method:\n\n.. code-block:: python\n\n def __pretty_repr__(\n self,\n parser # PrettyFormat class instance,\n indent # start indent,\n no_indent_start # do not indent the first level\n ):\n return ...\n\nThis method will be executed instead of __repr__ on your object.\n\n.. code-block:: python\n\n def __pretty_str__(\n self,\n parser # PrettyFormat class instance,\n indent # start indent,\n no_indent_start # do not indent the first level\n ):\n return ...\n\nThis method will be executed instead of __str__ on your object.\n\nLogOnAccess\n-----------\n\nThis special case of property is useful in cases, where a lot of properties should be logged by similar way without writing a lot of code.\n\nBasic API is conform with `property`, but in addition it is possible to customize logger, log levels and log conditions.\n\nUsage examples:\n\n1. Simple usage. All by default.\n logger is re-used:\n\n * from instance if available with names `logger` or `log`,\n * from instance module if available with names `LOGGER`, `log`,\n * else used internal `logwrap.log_on_access` logger.\n\n .. code-block:: python\n\n import logging\n\n class Target(object):\n\n def init(self, val='ok')\n self.val = val\n self.logger = logging.get_logger(self.__class__.__name__) # Single for class, follow subclassing\n\n def __repr__(self):\n return \"{cls}(val={self.val})\".format(cls=self.__class__.__name__, self=self)\n\n @logwrap.LogOnAccess\n def ok(self):\n return self.val\n\n @ok.setter\n def ok(self, val):\n self.val = val\n\n @ok.deleter\n def ok(self):\n self.val = \"\"\n\n2. Use with global logger for class:\n\n .. code-block:: python\n\n class Target(object):\n\n def init(self, val='ok')\n self.val = val\n\n def __repr__(self):\n return \"{cls}(val={self.val})\".format(cls=self.__class__.__name__, self=self)\n\n @logwrap.LogOnAccess\n def ok(self):\n return self.val\n\n @ok.setter\n def ok(self, val):\n self.val = val\n\n @ok.deleter\n def ok(self):\n self.val = \"\"\n\n ok.logger = 'test_logger'\n ok.log_level = logging.INFO\n ok.exc_level = logging.ERROR\n ok.log_object_repr = True # As by default\n ok.log_before = True # As by default\n ok.log_success = True # As by default\n ok.log_failure = True # As by default\n ok.log_traceback = True # As by default\n ok.override_name = None # As by default: use original name\n\nTesting\n=======\nThe main test mechanism for the package `logwrap` is using `tox`.\nAvailable environments can be collected via `tox -l`\n\nCI/CD systems\n=============\n\n`GitHub: <https://github.com/python-useful-helpers/logwrap/actions>`_ is used for functional tests.\n",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "Decorator for logging function arguments and return value by human-readable way",
"version": "11.1.1",
"project_urls": {
"Bug Tracker": "https://github.com/python-useful-helpers/logwrap/issues",
"Documentation": "https://logwrap.readthedocs.io/",
"Homepage": "https://github.com/python-useful-helpers/logwrap",
"Repository": "https://github.com/python-useful-helpers/logwrap"
},
"split_keywords": [
"logging",
" debugging",
" development"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "3a05bff9365ec39d1b2836405ac3d028d476ea6230071686747331fcdd30ee0a",
"md5": "a7fe83b697986aa53e378bb6f3eb3891",
"sha256": "2cb0891d33a37f6c7c23c738017b16f860c70135da98c9ed7fc5541662321d8c"
},
"downloads": -1,
"filename": "logwrap-11.1.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "a7fe83b697986aa53e378bb6f3eb3891",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8.0",
"size": 27676,
"upload_time": "2024-11-28T11:02:34",
"upload_time_iso_8601": "2024-11-28T11:02:34.568492Z",
"url": "https://files.pythonhosted.org/packages/3a/05/bff9365ec39d1b2836405ac3d028d476ea6230071686747331fcdd30ee0a/logwrap-11.1.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "3f1988de476d08b3fd3eb201fdd53909c1f16ba01c03810a5726ffb0a788fe94",
"md5": "8a3b5e12bb899fbeba9d83b83aafc137",
"sha256": "8c5925ade0b347d22c60499a8719e14872468c7aeb9fc4bc35d61f7f3555b9c5"
},
"downloads": -1,
"filename": "logwrap-11.1.1.tar.gz",
"has_sig": false,
"md5_digest": "8a3b5e12bb899fbeba9d83b83aafc137",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8.0",
"size": 42805,
"upload_time": "2024-11-28T11:02:36",
"upload_time_iso_8601": "2024-11-28T11:02:36.690084Z",
"url": "https://files.pythonhosted.org/packages/3f/19/88de476d08b3fd3eb201fdd53909c1f16ba01c03810a5726ffb0a788fe94/logwrap-11.1.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-11-28 11:02:36",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "python-useful-helpers",
"github_project": "logwrap",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [],
"tox": true,
"lcname": "logwrap"
}