Skip to content

Commit 4988e1b

Browse files
committed
API changes: pickup logger from target module if possible
add constants (not cythonized: useless)
1 parent cee83b2 commit 4988e1b

File tree

8 files changed

+232
-57
lines changed

8 files changed

+232
-57
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ Argumented usage with arguments from signature:
7979
.. code-block:: python
8080
8181
@logwrap.logwrap(
82-
log=logging.getLogger(__name__), # __name__ = 'logwrap'
82+
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
8383
log_level=logging.DEBUG,
8484
exc_level=logging.ERROR,
8585
max_indent=20, # forwarded to the pretty_repr

doc/source/logwrap.rst

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ API: Decorators: `LogWrap` class and `logwrap` function.
1212

1313
.. versionadded:: 2.2.0
1414

15-
.. 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, )
15+
.. py:method:: __init__(func=None, *, log=None, 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, )
1616
1717
:param func: function to wrap
1818
:type func: typing.Optional[typing.Callable]
19-
:param log: logger object for decorator, by default used 'logwrap'
20-
:type log: logging.Logger
19+
:param log: logger object for decorator, by default trying to use logger from target module. Fallback: 'logwrap'
20+
:type log: typing.Optional[logging.Logger]
2121
:param log_level: log level for successful calls
2222
:type log_level: int
2323
:param exc_level: log level for exception cases
@@ -52,6 +52,7 @@ API: Decorators: `LogWrap` class and `logwrap` function.
5252
.. versionchanged:: 3.3.0 Deprecation of `*args`
5353
.. versionchanged:: 4.0.0 Drop of `*args`
5454
.. versionchanged:: 5.1.0 log_traceback parameter
55+
.. versionchanged:: 8.0.0 pick up logger from target module if possible
5556

5657
.. py:method:: pre_process_param(self, arg)
5758
@@ -111,6 +112,50 @@ API: Decorators: `LogWrap` class and `logwrap` function.
111112
:rtype: typing.Union[typing.Callable, typing.Awaitable]
112113

113114

115+
.. py:function:: logwrap(func=None, *, log=None, 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, )
116+
117+
Log function calls and return values.
118+
119+
:param func: function to wrap
120+
:type func: typing.Optional[typing.Callable]
121+
:param log: logger object for decorator, by default trying to use logger from target module. Fallback: 'logwrap'
122+
:type log: typing.Optional[logging.Logger]
123+
:param log_level: log level for successful calls
124+
:type log_level: int
125+
:param exc_level: log level for exception cases
126+
:type exc_level: int
127+
:param max_indent: maximum indent before classic `repr()` call.
128+
:type max_indent: int
129+
:param spec: callable object used as spec for arguments bind.
130+
This is designed for the special cases only,
131+
when impossible to change signature of target object,
132+
but processed/redirected signature is accessible.
133+
Note: this object should provide fully compatible signature
134+
with decorated function, or arguments bind will be failed!
135+
:type spec: typing.Optional[typing.Callable]
136+
:param blacklisted_names: Blacklisted argument names. Arguments with this names will be skipped in log.
137+
:type blacklisted_names: typing.Optional[typing.Iterable[str]]
138+
:param blacklisted_exceptions: list of exceptions, which should be re-raised
139+
without producing traceback and text log record.
140+
:type blacklisted_exceptions: typing.Optional[typing.Iterable[typing.Type[Exception]]]
141+
:param log_call_args: log call arguments before executing wrapped function.
142+
:type log_call_args: bool
143+
:param log_call_args_on_exc: log call arguments if exception raised.
144+
:type log_call_args_on_exc: bool
145+
:param log_traceback: log traceback on excaption in addition to failure info
146+
:type log_traceback: bool
147+
:param log_result_obj: log result of function call.
148+
:type log_result_obj: bool
149+
:return: built real decorator.
150+
:rtype: typing.Union[LogWrap, typing.Callable[..., typing.Union[typing.Awaitable[typing.Any], typing.Any]]]
151+
152+
.. versionchanged:: 3.3.0 Extract func from log and do not use Union.
153+
.. versionchanged:: 3.3.0 Deprecation of *args
154+
.. versionchanged:: 4.0.0 Drop of *args
155+
.. versionchanged:: 5.1.0 log_traceback parameter
156+
.. versionchanged:: 8.0.0 pick up logger from target module if possible
157+
158+
114159
.. py:class:: BoundParameter(inspect.Parameter)
115160
116161
Parameter-like object store BOUND with value parameter.

logwrap/constants.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright 2016 - 2019 Alexey Stepanov aka penguinolog
2+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
3+
# not use this file except in compliance with the License. You may obtain
4+
# a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
# License for the specific language governing permissions and limitations
12+
# under the License.
13+
14+
"""Global constants."""
15+
16+
VALID_LOGGER_NAMES = ("LOGGER", "LOG", "logger", "log")

logwrap/log_on_access.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727
import typing
2828

2929
# LogWrap Implementation
30+
from logwrap import constants
3031
from logwrap import repr_utils
3132

32-
3333
_LOGGER: logging.Logger = logging.getLogger(__name__)
3434
_CURRENT_FILE = os.path.abspath(__file__)
3535

@@ -222,14 +222,13 @@ def _get_logger_for_instance(self, instance: typing.Any) -> logging.Logger:
222222
"""
223223
if self.logger is not None:
224224
return self.logger
225-
valid_logger_names = ("logger", "log")
226-
for logger_name in valid_logger_names:
225+
for logger_name in constants.VALID_LOGGER_NAMES:
227226
logger_candidate = getattr(instance, logger_name, None)
228227
if isinstance(logger_candidate, logging.Logger):
229228
return logger_candidate
230229
instance_module = inspect.getmodule(instance)
231-
for logger_name in valid_logger_names:
232-
logger_candidate = getattr(instance_module, logger_name.upper(), None)
230+
for logger_name in constants.VALID_LOGGER_NAMES:
231+
logger_candidate = getattr(instance_module, logger_name, None)
233232
if isinstance(logger_candidate, logging.Logger):
234233
return logger_candidate
235234
return _LOGGER

logwrap/log_wrap.pxd

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""log_wrap shared code module."""
1616

1717
import inspect
18+
import logging
1819
import typing
1920

2021
from logwrap cimport class_decorator
@@ -48,6 +49,6 @@ cdef:
4849

4950
cdef:
5051
str _get_func_args_repr(self, sig: inspect.Signature, tuple args, dict kwargs)
51-
void _make_done_record(self, str func_name, result: typing.Any) except *
52-
void _make_calling_record(self, str name, str arguments, str method=?) except *
53-
void _make_exc_record(self, str name, str arguments, Exception exception) except *
52+
void _make_done_record(self, logger: logging.Logger, str func_name, result: typing.Any) except *
53+
void _make_calling_record(self, logger: logging.Logger, str name, str arguments, str method=?) except *
54+
void _make_exc_record(self, logger: logging.Logger, str name, str arguments, Exception exception) except *

logwrap/log_wrap.py

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
# LogWrap Implementation
3232
from logwrap import class_decorator
33+
from logwrap import constants
3334
from logwrap import repr_utils
3435

3536
LOGGER: logging.Logger = logging.getLogger("logwrap")
@@ -154,7 +155,7 @@ def __init__(
154155
self,
155156
func: typing.Optional[typing.Callable[..., FuncResultType]] = None,
156157
*,
157-
log: logging.Logger = LOGGER,
158+
log: typing.Optional[logging.Logger] = None,
158159
log_level: int = logging.DEBUG,
159160
exc_level: int = logging.ERROR,
160161
max_indent: int = 20,
@@ -170,8 +171,8 @@ def __init__(
170171
171172
:param func: function to wrap
172173
:type func: typing.Optional[typing.Callable]
173-
:param log: logger object for decorator, by default used 'logwrap'
174-
:type log: logging.Logger
174+
:param log: logger object for decorator, by default trying to use logger from target module. Fallback: 'logwrap'
175+
:type log: typing.Optional[logging.Logger]
175176
:param log_level: log level for successful calls
176177
:type log_level: int
177178
:param exc_level: log level for exception cases
@@ -202,6 +203,7 @@ def __init__(
202203
203204
.. versionchanged:: 3.3.0 Extract func from log and do not use Union.
204205
.. versionchanged:: 5.1.0 log_traceback parameter
206+
.. versionchanged:: 8.0.0 pick up logger from target module if possible
205207
"""
206208
super().__init__(func=func)
207209

@@ -215,7 +217,13 @@ def __init__(
215217
else:
216218
self.__blacklisted_exceptions = list(blacklisted_exceptions)
217219

218-
self.__logger: logging.Logger = log
220+
if isinstance(log, logging.Logger):
221+
self.__logger: typing.Optional[logging.Logger] = log
222+
else:
223+
self.__logger = None
224+
225+
if func is not None: # Special case: we can prefetch logger
226+
self.__logger = self._get_logger_for_func(func)
219227

220228
self.__log_level: int = log_level
221229
self.__exc_level: int = exc_level
@@ -228,6 +236,18 @@ def __init__(
228236

229237
# We are not interested to pass any arguments to object
230238

239+
def _get_logger_for_func(self, func: FuncResultType) -> logging.Logger:
240+
"""Get logger for function from function module if possible."""
241+
if self.__logger is not None:
242+
return self.__logger
243+
244+
func_module = inspect.getmodule(func)
245+
for logger_name in constants.VALID_LOGGER_NAMES:
246+
logger_candidate = getattr(func_module, logger_name, None)
247+
if isinstance(logger_candidate, logging.Logger):
248+
return logger_candidate
249+
return LOGGER
250+
231251
@property
232252
def log_level(self) -> int:
233253
"""Log level for normal behavior.
@@ -385,10 +405,10 @@ def log_result_obj(self, val: bool) -> None:
385405
self.__log_result_obj = val
386406

387407
@property
388-
def _logger(self) -> logging.Logger:
408+
def _logger(self) -> typing.Optional[logging.Logger]:
389409
"""Logger instance.
390410
391-
:rtype: logging.Logger
411+
:rtype: typing.Optional[logging.Logger]
392412
"""
393413
return self.__logger
394414

@@ -515,30 +535,33 @@ def _get_func_args_repr(
515535
param_str += "\n"
516536
return param_str
517537

518-
def _make_done_record(self, func_name: str, result: typing.Any) -> None:
538+
def _make_done_record(self, logger: logging.Logger, func_name: str, result: typing.Any) -> None:
519539
"""Construct success record.
520540
541+
:type logger: logging.Logger
521542
:type func_name: str
522543
:type result: typing.Any
523544
"""
524545
msg: str = f"Done: {func_name!r}"
525546

526547
if self.log_result_obj:
527548
msg += f" with result:\n{repr_utils.pretty_repr(result, max_indent=self.max_indent)}"
528-
self._logger.log(level=self.log_level, msg=msg)
549+
logger.log(level=self.log_level, msg=msg)
529550

530-
def _make_calling_record(self, name: str, arguments: str, method: str = "Calling") -> None:
551+
def _make_calling_record(self, logger: logging.Logger, name: str, arguments: str, method: str = "Calling") -> None:
531552
"""Make log record before execution.
532553
554+
:type logger: logging.Logger
533555
:type name: str
534556
:type arguments: str
535557
:type method: str
536558
"""
537-
self._logger.log(level=self.log_level, msg=f"{method}: \n{name}({arguments if self.log_call_args else ''})")
559+
logger.log(level=self.log_level, msg=f"{method}: \n{name}({arguments if self.log_call_args else ''})")
538560

539-
def _make_exc_record(self, name: str, arguments: str, exception: Exception) -> None:
561+
def _make_exc_record(self, logger: logging.Logger, name: str, arguments: str, exception: Exception) -> None:
540562
"""Make log record if exception raised.
541563
564+
:type logger: logging.Logger
542565
:type name: str
543566
:type arguments: str
544567
:type exception: Exception
@@ -554,7 +577,7 @@ def _make_exc_record(self, name: str, arguments: str, exception: Exception) -> N
554577
else exception.__class__.__name__
555578
)
556579

557-
self._logger.log(
580+
logger.log(
558581
level=self.exc_level,
559582
msg=f"Failed: \n{name}({arguments if self.log_call_args_on_exc else ''})\n{tb_text}",
560583
exc_info=False,
@@ -569,18 +592,20 @@ def _get_function_wrapper(self, func: typing.Callable[..., FuncResultType]) -> t
569592
:rtype: typing.Callable
570593
"""
571594

595+
logger: logging.Logger = self._get_logger_for_func(func)
596+
572597
# noinspection PyCompatibility,PyMissingOrEmptyDocstring
573598
@functools.wraps(func)
574599
async def async_wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
575600
sig: inspect.Signature = inspect.signature(self._spec or func)
576601
args_repr: str = self._get_func_args_repr(sig=sig, args=args, kwargs=kwargs)
577602

578603
try:
579-
self._make_calling_record(name=func.__name__, arguments=args_repr, method="Awaiting")
604+
self._make_calling_record(logger=logger, name=func.__name__, arguments=args_repr, method="Awaiting")
580605
result = await func(*args, **kwargs)
581-
self._make_done_record(func.__name__, result)
606+
self._make_done_record(logger=logger, func_name=func.__name__, result=result)
582607
except Exception as e:
583-
self._make_exc_record(name=func.__name__, arguments=args_repr, exception=e)
608+
self._make_exc_record(logger=logger, name=func.__name__, arguments=args_repr, exception=e)
584609
raise
585610
return result # type: ignore
586611

@@ -591,11 +616,11 @@ def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
591616
args_repr: str = self._get_func_args_repr(sig=sig, args=args, kwargs=kwargs)
592617

593618
try:
594-
self._make_calling_record(name=func.__name__, arguments=args_repr)
619+
self._make_calling_record(logger=logger, name=func.__name__, arguments=args_repr)
595620
result = func(*args, **kwargs)
596-
self._make_done_record(func.__name__, result)
621+
self._make_done_record(logger=logger, func_name=func.__name__, result=result)
597622
except Exception as e:
598-
self._make_exc_record(name=func.__name__, arguments=args_repr, exception=e)
623+
self._make_exc_record(logger=logger, name=func.__name__, arguments=args_repr, exception=e)
599624
raise
600625
return result
601626

@@ -622,7 +647,7 @@ def __call__( # pylint: disable=useless-super-delegation
622647
def logwrap(
623648
func: None = None,
624649
*,
625-
log: logging.Logger = LOGGER,
650+
log: typing.Optional[logging.Logger] = None,
626651
log_level: int = logging.DEBUG,
627652
exc_level: int = logging.ERROR,
628653
max_indent: int = 20,
@@ -641,7 +666,7 @@ def logwrap(
641666
def logwrap(
642667
func: typing.Callable[..., typing.Awaitable[FuncFinalResult]],
643668
*,
644-
log: logging.Logger = LOGGER,
669+
log: typing.Optional[logging.Logger] = None,
645670
log_level: int = logging.DEBUG,
646671
exc_level: int = logging.ERROR,
647672
max_indent: int = 20,
@@ -660,7 +685,7 @@ def logwrap(
660685
def logwrap(
661686
func: typing.Callable[..., FuncFinalResult],
662687
*,
663-
log: logging.Logger = LOGGER,
688+
log: typing.Optional[logging.Logger] = None,
664689
log_level: int = logging.DEBUG,
665690
exc_level: int = logging.ERROR,
666691
max_indent: int = 20,
@@ -678,7 +703,7 @@ def logwrap(
678703
def logwrap( # noqa: F811
679704
func: typing.Optional[typing.Callable[..., FuncResultType]] = None,
680705
*,
681-
log: logging.Logger = LOGGER,
706+
log: typing.Optional[logging.Logger] = None,
682707
log_level: int = logging.DEBUG,
683708
exc_level: int = logging.ERROR,
684709
max_indent: int = 20,
@@ -694,8 +719,8 @@ def logwrap( # noqa: F811
694719
695720
:param func: function to wrap
696721
:type func: typing.Optional[typing.Callable]
697-
:param log: logger object for decorator, by default used 'logwrap'
698-
:type log: logging.Logger
722+
:param log: logger object for decorator, by default trying to use logger from target module. Fallback: 'logwrap'
723+
:type log: typing.Optional[logging.Logger]
699724
:param log_level: log level for successful calls
700725
:type log_level: int
701726
:param exc_level: log level for exception cases
@@ -723,12 +748,13 @@ def logwrap( # noqa: F811
723748
:param log_result_obj: log result of function call.
724749
:type log_result_obj: bool
725750
:return: built real decorator.
726-
:rtype: _log_wrap_shared.BaseLogWrap
751+
:rtype: typing.Union[LogWrap, typing.Callable[..., FuncResultType]]
727752
728753
.. versionchanged:: 3.3.0 Extract func from log and do not use Union.
729754
.. versionchanged:: 3.3.0 Deprecation of *args
730755
.. versionchanged:: 4.0.0 Drop of *args
731756
.. versionchanged:: 5.1.0 log_traceback parameter
757+
.. versionchanged:: 8.0.0 pick up logger from target module if possible
732758
"""
733759
wrapper = LogWrap(
734760
log=log,

0 commit comments

Comments
 (0)