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.
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:
@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:
@logwrap.logwrap()
def foo():
    passis equal to:
@logwrap.logwrap
def foo():
    passGet decorator for use without parameters:
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():
    passCall example (python 3.8):
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',
    },
 )
Example construction and read from test:
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 == TrueOn 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.
This is specified helper for making human-readable repr on complex objects. Signature is self-documenting:
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
)This is specified helper for making human-readable str on complex objects. Signature is self-documenting:
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 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:
def __init__(
    self,
    max_indent=20,  # maximum allowed indent level
    indent_step=4,  # step between indents
)Callable object (PrettyFormat instance) signature:
def __call__(
    self,
    src,  # object for repr
    indent=0,  # start indent
    no_indent_start=False  # do not indent the first level
)pretty_repr behavior could be overridden for your classes by implementing specific magic method:
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.
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.
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:
- 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.
 
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 = ""
- Use with global logger for class:
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
The main test mechanism for the package logwrap is using tox. Available environments can be collected via tox -l
GitHub: is used for functional tests.