1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
from collections.abc import Iterable, Mapping
from itertools import islice
_MAX_ARGUMENTS = 5
_MAX_ITEMS = 5
_ELLIPSE = '...'
_MAX_STR_LENGTH = 20 - len(_ELLIPSE)
_SENSITIVE_KEYS = {'password', 'token', 'secret'}
def is_sensitive_key(name):
return any(key in str(name) for key in _SENSITIVE_KEYS)
def items_redacted(mapping):
for key, value in mapping.items():
if is_sensitive_key(key):
yield key, Ellipse()
else:
yield key, value
class Ellipse:
__slots__ = ('text',)
def __init__(self, text=_ELLIPSE):
self.text = text
def __str__(self):
return self.text
__repr__ = __str__
class EllipseDict:
__slots__ = ('items',)
def __init__(self, items):
self.items = items
def __str__(self):
strings = []
ellipse = False
for key, value in self.items:
if isinstance(key, Ellipse):
ellipse = True
break
strings.append(f'{key!r}: {value!r}')
if ellipse:
strings.append(_ELLIPSE)
s = ', '.join(strings)
return '{' + s + '}'
__repr__ = __str__
class format_args:
__slots__ = ('args', 'kwargs', 'verbose', 'max_args', 'max_items')
def __init__(self, args, kwargs, verbose=False,
max_args=_MAX_ARGUMENTS, max_items=_MAX_ITEMS):
self.args = args
self.kwargs = kwargs
self.verbose = verbose
self.max_args = max_args
self.max_items = max_items
def __str__(self):
_nb_args = self.max_args
_nb_items = self.max_items
def _shorten_sequence(value):
nonlocal _nb_items
stop = self.max_items + 1 if not self.verbose else None
for v in islice(value, None, stop):
if not self.verbose and not _nb_items:
yield Ellipse()
break
yield v
_nb_items -= 1
def _log_repr(value):
if isinstance(value, bytes):
if self.verbose:
return value
return Ellipse(f'<{len(value)} bytes>')
elif isinstance(value, str):
if len(value) <= _MAX_STR_LENGTH or self.verbose:
return value
return (value[:_MAX_STR_LENGTH]
+ (_ELLIPSE if len(value) > _MAX_STR_LENGTH else ''))
elif isinstance(value, Mapping):
def shorten(value):
for items in _shorten_sequence(items_redacted(value)):
if isinstance(items, Ellipse):
yield Ellipse(), Ellipse()
break
key, value = items
yield _log_repr(key), _log_repr(value)
return EllipseDict(shorten(value))
elif isinstance(value, Iterable):
return type(value)(_log_repr(v)
for v in _shorten_sequence(value))
else:
return value
s = '('
logged_args = []
for args in self.args:
if not _nb_args and not self.verbose:
logged_args.append(Ellipse())
break
_nb_items = self.max_items
logged_args.append(_log_repr(args))
_nb_args -= 1
s += ', '.join(repr(a) for a in logged_args)
if self.kwargs and (not logged_args
or not isinstance(logged_args[-1], Ellipse)):
s += ', ' if self.args and self.kwargs else ''
logged_kwargs = []
for key, value in items_redacted(self.kwargs):
if not _nb_args and not self.verbose:
logged_kwargs.append(repr(Ellipse()))
break
_nb_items = self.max_items
logged_kwargs.append(
f'{_log_repr(key)}={_log_repr(value)!r}')
_nb_args -= 1
s += ', '.join(logged_kwargs)
s += ')'
return s
__all__ = [format_args]
|