Skip to content

Commit 450fed7

Browse files
committed
Implement async_logwrap
Probably CI will require job re-align
1 parent 6eb6ffe commit 450fed7

File tree

6 files changed

+350
-81
lines changed

6 files changed

+350
-81
lines changed

CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
CHANGELOG
22
=========
3+
Version 2.0.0
4+
* Async version is added
5+
36
Version 1.3.0
47
-------------
58
* Allowed to blacklist call arguments

logwrap/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
from __future__ import absolute_import
2626

27+
import sys
28+
2729
from ._log_wrap import logwrap
2830
from ._repr_utils import PrettyFormat
2931
from ._repr_utils import pretty_repr
@@ -37,3 +39,10 @@
3739
'pretty_repr',
3840
'pretty_str'
3941
)
42+
43+
# pylint: disable=ungrouped-imports, no-name-in-module
44+
if sys.version_info[0:2] >= (3, 5):
45+
from ._alogwrap import async_logwrap
46+
47+
__all__ += ('async_logwrap', )
48+
# pylint: enable=ungrouped-imports, no-name-in-module

logwrap/_alogwrap.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Copyright 2016-2017 Alexey Stepanov aka penguinolog
2+
3+
# Copyright 2016 Mirantis, Inc.
4+
5+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License. You may obtain
7+
# a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
# License for the specific language governing permissions and limitations
15+
# under the License.
16+
17+
"""log_wrap: async part (python 3.5+).
18+
19+
This is no reason to import this submodule directly, all required methods is
20+
available from the main module.
21+
"""
22+
23+
from __future__ import absolute_import
24+
from __future__ import unicode_literals
25+
26+
import functools
27+
import inspect
28+
import logging
29+
import types
30+
import typing
31+
32+
import logwrap as core
33+
34+
from . import _log_wrap_shared
35+
36+
37+
def async_logwrap(
38+
log: logging.Logger=_log_wrap_shared.logger,
39+
log_level: int=logging.DEBUG,
40+
exc_level: int=logging.ERROR,
41+
max_indent: int=20,
42+
spec: types.FunctionType=None,
43+
blacklisted_names: typing.Iterable[str]=None,
44+
) -> types.FunctionType:
45+
"""Log function calls and return values. Async version.
46+
47+
:param log: logger object for decorator, by default used 'logwrap'
48+
:param log_level: log level for successful calls
49+
:param exc_level: log level for exception cases
50+
:param max_indent: maximal indent before classic repr() call.
51+
:param spec: callable object used as spec for arguments bind.
52+
This is designed for the special cases only,
53+
when impossible to change signature of target object,
54+
but processed/redirected signature is accessible.
55+
Note: this object should provide fully compatible signature
56+
with decorated function, or arguments bind will be failed!
57+
:param blacklisted_names: Blacklisted argument names.
58+
Arguments with this names will be skipped in log.
59+
:return: built real decorator
60+
"""
61+
if blacklisted_names is None:
62+
blacklisted_names = []
63+
64+
def real_decorator(func: types.FunctionType) -> types.CoroutineType:
65+
"""Log function calls and return values.
66+
67+
This decorator could be extracted as configured from outer function.
68+
69+
:param func: function to log calls from
70+
:return: wrapped function
71+
"""
72+
# Get signature _before_ call
73+
sig = inspect.signature(obj=func if not spec else spec)
74+
75+
# pylint: disable=missing-docstring
76+
# noinspection PyCompatibility
77+
@functools.wraps(func)
78+
async def wrapped(*args, **kwargs):
79+
args_repr = _log_wrap_shared.get_func_args_repr(
80+
sig=sig,
81+
args=args,
82+
kwargs=kwargs,
83+
max_indent=max_indent,
84+
blacklisted_names=blacklisted_names
85+
)
86+
87+
log.log(
88+
level=log_level,
89+
msg="Calling: \n{name!r}({arguments})".format(
90+
name=func.__name__,
91+
arguments=args_repr
92+
)
93+
)
94+
try:
95+
result = await func(*args, **kwargs)
96+
log.log(
97+
level=log_level,
98+
msg="Done: {name!r} with result:\n{result}".format(
99+
name=func.__name__,
100+
result=core.pretty_repr(
101+
result,
102+
max_indent=max_indent,
103+
)
104+
)
105+
)
106+
except BaseException:
107+
log.log(
108+
level=exc_level,
109+
msg="Failed: \n{name!r}({arguments})".format(
110+
name=func.__name__,
111+
arguments=args_repr,
112+
),
113+
exc_info=True
114+
)
115+
raise
116+
return result
117+
118+
# pylint: enable=missing-docstring
119+
return wrapped
120+
121+
if not isinstance(log, logging.Logger):
122+
func, log = log, _log_wrap_shared.logger
123+
124+
return real_decorator(func)
125+
126+
return real_decorator
127+
128+
129+
__all__ = ('async_logwrap',)

logwrap/_log_wrap.py

Lines changed: 10 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2016 Alexey Stepanov aka penguinolog
1+
# Copyright 2016-2017 Alexey Stepanov aka penguinolog
22

33
# Copyright 2016 Mirantis, Inc.
44

@@ -29,6 +29,8 @@
2929

3030
import logwrap as core
3131

32+
from . import _log_wrap_shared
33+
3234
# pylint: disable=ungrouped-imports, no-name-in-module
3335
if sys.version_info[0:2] > (3, 0):
3436
from inspect import signature
@@ -38,53 +40,8 @@
3840
# pylint: enable=ungrouped-imports, no-name-in-module
3941

4042

41-
_logger = logging.getLogger(__name__)
42-
43-
44-
indent = 4
45-
fmt = "\n{spc:<{indent}}{{key!r}}={{val}},".format(
46-
spc='',
47-
indent=indent,
48-
).format
49-
comment = "\n{spc:<{indent}}# {{kind!s}}:".format(spc='', indent=indent).format
50-
51-
52-
def _get_func_args_repr(sig, args, kwargs, max_indent, blacklisted_names):
53-
"""Internal helper for reducing complexity of decorator code.
54-
55-
:type sig: inspect.Signature
56-
:type max_indent: int
57-
:type blacklisted_names: list
58-
:rtype: str
59-
"""
60-
bound = sig.bind(*args, **kwargs).arguments
61-
62-
param_str = ""
63-
64-
last_kind = None
65-
for param in sig.parameters.values():
66-
if param.name in blacklisted_names:
67-
continue
68-
69-
if last_kind != param.kind:
70-
param_str += comment(kind=param.kind)
71-
last_kind = param.kind
72-
param_str += fmt(
73-
key=param.name,
74-
val=core.pretty_repr(
75-
src=bound.get(param.name, param.default),
76-
indent=indent + 4,
77-
no_indent_start=True,
78-
max_indent=max_indent,
79-
),
80-
)
81-
if param_str:
82-
param_str += "\n"
83-
return param_str
84-
85-
8643
def logwrap(
87-
log=_logger,
44+
log=_log_wrap_shared.logger,
8845
log_level=logging.DEBUG,
8946
exc_level=logging.ERROR,
9047
max_indent=20,
@@ -130,9 +87,10 @@ def real_decorator(func):
13087
# Get signature _before_ call
13188
sig = signature(obj=func if not spec else spec)
13289

90+
# pylint: disable=missing-docstring
13391
@functools.wraps(func)
13492
def wrapped(*args, **kwargs):
135-
args_repr = _get_func_args_repr(
93+
args_repr = _log_wrap_shared.get_func_args_repr(
13694
sig=sig,
13795
args=args,
13896
kwargs=kwargs,
@@ -170,10 +128,13 @@ def wrapped(*args, **kwargs):
170128
)
171129
raise
172130
return result
131+
132+
# pylint: enable=missing-docstring
173133
return wrapped
174134

175135
if not isinstance(log, logging.Logger):
176-
func, log = log, _logger
136+
func, log = log, _log_wrap_shared.logger
137+
177138
return real_decorator(func)
178139

179140
return real_decorator

logwrap/_log_wrap_shared.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright 2016-2017 Alexey Stepanov aka penguinolog
2+
3+
# Copyright 2016 Mirantis, Inc.
4+
5+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License. You may obtain
7+
# a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
# License for the specific language governing permissions and limitations
15+
# under the License.
16+
17+
"""log_wrap shared code module."""
18+
19+
import logging
20+
21+
import logwrap as core
22+
23+
logger = logging.getLogger(__name__)
24+
25+
26+
indent = 4
27+
fmt = "\n{spc:<{indent}}{{key!r}}={{val}},".format(
28+
spc='',
29+
indent=indent,
30+
).format
31+
comment = "\n{spc:<{indent}}# {{kind!s}}:".format(spc='', indent=indent).format
32+
33+
34+
def get_func_args_repr(sig, args, kwargs, max_indent, blacklisted_names):
35+
"""Internal helper for reducing complexity of decorator code.
36+
37+
:type sig: inspect.Signature
38+
:type args: tuple
39+
:type kwargs: dict
40+
:type max_indent: int
41+
:type blacklisted_names: list
42+
:rtype: str
43+
"""
44+
bound = sig.bind(*args, **kwargs).arguments
45+
46+
param_str = ""
47+
48+
last_kind = None
49+
for param in sig.parameters.values():
50+
if param.name in blacklisted_names:
51+
continue
52+
53+
if last_kind != param.kind:
54+
param_str += comment(kind=param.kind)
55+
last_kind = param.kind
56+
param_str += fmt(
57+
key=param.name,
58+
val=core.pretty_repr(
59+
src=bound.get(param.name, param.default),
60+
indent=indent + 4,
61+
no_indent_start=True,
62+
max_indent=max_indent,
63+
),
64+
)
65+
if param_str:
66+
param_str += "\n"
67+
return param_str

0 commit comments

Comments
 (0)