mirror of
https://github.com/cool-RR/PySnooper.git
synced 2026-07-20 17:59:34 +00:00
Add support for thread identifiers
Display thread infos (identifier and name) on output to help user to track execution on apps who use threading.
This commit is contained in:
parent
2801ce0c0e
commit
fae0ce0c76
4 changed files with 153 additions and 5 deletions
|
|
@ -143,6 +143,12 @@ Start all snoop lines with a prefix, to grep for them easily:
|
|||
@pysnooper.snoop(prefix='ZZZ ')
|
||||
```
|
||||
|
||||
On multi-threaded apps identify which thread are snooped in output::
|
||||
|
||||
```python
|
||||
@pysnooper.snoop(thread_info=True)
|
||||
```
|
||||
|
||||
# Installation #
|
||||
|
||||
You can install **PySnooper** by:
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ class Tracer:
|
|||
depth=1,
|
||||
prefix='',
|
||||
overwrite=False,
|
||||
thread_info=False,
|
||||
):
|
||||
'''
|
||||
Snoop on the function, writing everything it's doing to stderr.
|
||||
|
|
@ -163,6 +164,9 @@ class Tracer:
|
|||
|
||||
@pysnooper.snoop(prefix='ZZZ ')
|
||||
|
||||
On multi-threaded apps identify which thread are snooped in output::
|
||||
|
||||
@pysnooper.snoop(thread_info=True)
|
||||
'''
|
||||
self._write, self.truncate = get_write_and_truncate_functions(output)
|
||||
|
||||
|
|
@ -183,6 +187,8 @@ class Tracer:
|
|||
self.prefix = prefix
|
||||
self.overwrite = overwrite
|
||||
self._did_overwrite = False
|
||||
self.thread_info = thread_info
|
||||
self.thread_info_padding = 0
|
||||
assert self.depth >= 1
|
||||
self.target_codes = set()
|
||||
self.target_frames = set()
|
||||
|
|
@ -223,6 +229,13 @@ class Tracer:
|
|||
def _is_internal_frame(self, frame):
|
||||
return frame.f_code.co_filename == Tracer.__enter__.__code__.co_filename
|
||||
|
||||
def set_thread_info_padding(self, thread_info):
|
||||
current_thread_len = len(thread_info)
|
||||
self.thread_info_padding = max(self.thread_info_padding,
|
||||
current_thread_len)
|
||||
return thread_info.ljust(self.thread_info_padding)
|
||||
|
||||
|
||||
def trace(self, frame, event, arg):
|
||||
|
||||
### Checking whether we should trace this line: #######################
|
||||
|
|
@ -285,6 +298,12 @@ class Tracer:
|
|||
now_string = datetime_module.datetime.now().time().isoformat()
|
||||
line_no = frame.f_lineno
|
||||
source_line = get_source_from_frame(frame)[line_no - 1]
|
||||
thread_info = ""
|
||||
if self.thread_info:
|
||||
current_thread = threading.current_thread()
|
||||
thread_info = "{ident}-{name} ".format(
|
||||
ident=current_thread.ident, name=current_thread.getName())
|
||||
thread_info = self.set_thread_info_padding(thread_info)
|
||||
|
||||
### Dealing with misplaced function definition: #######################
|
||||
# #
|
||||
|
|
@ -308,7 +327,7 @@ class Tracer:
|
|||
# #
|
||||
### Finished dealing with misplaced function definition. ##############
|
||||
|
||||
self.write(u'{indent}{now_string} {event:9} '
|
||||
self.write(u'{indent}{now_string} {thread_info}{event:9} '
|
||||
u'{line_no:4} {source_line}'.format(**locals()))
|
||||
|
||||
if event == 'return':
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import io
|
||||
import textwrap
|
||||
import threading
|
||||
import types
|
||||
|
||||
from python_toolbox import sys_tools, temp_file_tools
|
||||
|
|
@ -42,6 +43,114 @@ def test_string_io():
|
|||
)
|
||||
|
||||
|
||||
def test_thread_info():
|
||||
|
||||
@pysnooper.snoop(thread_info=True)
|
||||
def my_function(foo):
|
||||
x = 7
|
||||
y = 8
|
||||
return y + x
|
||||
|
||||
with sys_tools.OutputCapturer(stdout=False,
|
||||
stderr=True) as output_capturer:
|
||||
result = my_function('baba')
|
||||
assert result == 15
|
||||
output = output_capturer.string_io.getvalue()
|
||||
assert_output(
|
||||
output,
|
||||
(
|
||||
VariableEntry('foo', value_regex="u?'baba'"),
|
||||
CallEntry('def my_function(foo):'),
|
||||
LineEntry('x = 7'),
|
||||
VariableEntry('x', '7'),
|
||||
LineEntry('y = 8'),
|
||||
VariableEntry('y', '8'),
|
||||
LineEntry('return y + x'),
|
||||
ReturnEntry('return y + x'),
|
||||
ReturnValueEntry('15'),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_multi_thread_info():
|
||||
|
||||
@pysnooper.snoop(thread_info=True)
|
||||
def my_function(foo):
|
||||
x = 7
|
||||
y = 8
|
||||
return y + x
|
||||
|
||||
with sys_tools.OutputCapturer(stdout=False,
|
||||
stderr=True) as output_capturer:
|
||||
my_function('baba')
|
||||
t1 = threading.Thread(target=my_function, name="test123",args=['bubu'])
|
||||
t1.start()
|
||||
t1.join()
|
||||
t1 = threading.Thread(target=my_function, name="bibi",args=['bibi'])
|
||||
t1.start()
|
||||
t1.join()
|
||||
output = output_capturer.string_io.getvalue()
|
||||
calls = [line for line in output.split("\n") if "call" in line]
|
||||
main_thread = calls[0]
|
||||
assert len(main_thread) == len(calls[1])
|
||||
assert len(main_thread) == len(calls[2])
|
||||
main_thread_call_str = main_thread.find("call")
|
||||
assert main_thread_call_str == calls[1].find("call")
|
||||
assert main_thread_call_str == calls[2].find("call")
|
||||
thread_info_regex = '([0-9]+-{name}+[ ]+)'
|
||||
assert_output(
|
||||
output,
|
||||
(
|
||||
VariableEntry('foo', value_regex="u?'baba'"),
|
||||
CallEntry('def my_function(foo):',
|
||||
thread_info_regex=thread_info_regex.format(
|
||||
name="MainThread")),
|
||||
LineEntry('x = 7',
|
||||
thread_info_regex=thread_info_regex.format(
|
||||
name="MainThread")),
|
||||
VariableEntry('x', '7'),
|
||||
LineEntry('y = 8',
|
||||
thread_info_regex=thread_info_regex.format(
|
||||
name="MainThread")),
|
||||
VariableEntry('y', '8'),
|
||||
LineEntry('return y + x',
|
||||
thread_info_regex=thread_info_regex.format(
|
||||
name="MainThread")),
|
||||
ReturnEntry('return y + x'),
|
||||
ReturnValueEntry('15'),
|
||||
VariableEntry('foo', value_regex="u?'bubu'"),
|
||||
CallEntry('def my_function(foo):',
|
||||
thread_info_regex=thread_info_regex.format(
|
||||
name="test123")),
|
||||
LineEntry('x = 7',
|
||||
thread_info_regex=thread_info_regex.format(
|
||||
name="test123")),
|
||||
VariableEntry('x', '7'),
|
||||
LineEntry('y = 8',
|
||||
thread_info_regex=thread_info_regex.format(
|
||||
name="test123")),
|
||||
VariableEntry('y', '8'),
|
||||
LineEntry('return y + x',
|
||||
thread_info_regex=thread_info_regex.format(
|
||||
name="test123")),
|
||||
ReturnEntry('return y + x'),
|
||||
ReturnValueEntry('15'),
|
||||
VariableEntry('foo', value_regex="u?'bibi'"),
|
||||
CallEntry('def my_function(foo):',
|
||||
thread_info_regex=thread_info_regex.format(name='bibi')),
|
||||
LineEntry('x = 7',
|
||||
thread_info_regex=thread_info_regex.format(name='bibi')),
|
||||
VariableEntry('x', '7'),
|
||||
LineEntry('y = 8',
|
||||
thread_info_regex=thread_info_regex.format(name='bibi')),
|
||||
VariableEntry('y', '8'),
|
||||
LineEntry('return y + x',
|
||||
thread_info_regex=thread_info_regex.format(name='bibi')),
|
||||
ReturnEntry('return y + x'),
|
||||
ReturnValueEntry('15'),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_callable():
|
||||
string_io = io.StringIO()
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class _BaseValueEntry(_BaseEntry):
|
|||
|
||||
class VariableEntry(_BaseValueEntry):
|
||||
def __init__(self, name=None, value=None, stage=None, prefix='',
|
||||
name_regex=None, value_regex=None, ):
|
||||
name_regex=None, value_regex=None):
|
||||
_BaseValueEntry.__init__(self, prefix=prefix)
|
||||
if name is not None:
|
||||
assert name_regex is None
|
||||
|
|
@ -165,7 +165,8 @@ class ReturnValueEntry(_BaseValueEntry):
|
|||
|
||||
|
||||
class _BaseEventEntry(_BaseEntry):
|
||||
def __init__(self, source=None, source_regex=None, prefix=''):
|
||||
def __init__(self, source=None, source_regex=None, thread_info=None,
|
||||
thread_info_regex=None, prefix=''):
|
||||
_BaseEntry.__init__(self, prefix=prefix)
|
||||
if type(self) is _BaseEventEntry:
|
||||
raise TypeError
|
||||
|
|
@ -173,6 +174,7 @@ class _BaseEventEntry(_BaseEntry):
|
|||
assert source_regex is None
|
||||
self.line_pattern = re.compile(
|
||||
r"""^%s(?P<indent>(?: {4})*)[0-9:.]{15} """
|
||||
r"""(?P<thread_info>[0-9]+-[0-9A-Za-z_-]+[ ]+)?"""
|
||||
r"""(?P<event_name>[a-z_]*) +(?P<line_number>[0-9]*) """
|
||||
r"""+(?P<source>.*)$""" % (re.escape(self.prefix,))
|
||||
)
|
||||
|
|
@ -180,6 +182,9 @@ class _BaseEventEntry(_BaseEntry):
|
|||
self.source = source
|
||||
self.source_regex = (None if source_regex is None else
|
||||
re.compile(source_regex))
|
||||
self.thread_info = thread_info
|
||||
self.thread_info_regex = (None if thread_info_regex is None else
|
||||
re.compile(thread_info_regex))
|
||||
|
||||
@caching.CachedProperty
|
||||
def event_name(self):
|
||||
|
|
@ -193,13 +198,22 @@ class _BaseEventEntry(_BaseEntry):
|
|||
else:
|
||||
return True
|
||||
|
||||
def _check_thread_info(self, thread_info):
|
||||
if self.thread_info is not None:
|
||||
return thread_info == self.thread_info
|
||||
elif self.thread_info_regex is not None:
|
||||
return self.thread_info_regex.match(thread_info)
|
||||
else:
|
||||
return True
|
||||
|
||||
def check(self, s):
|
||||
match = self.line_pattern.match(s)
|
||||
if not match:
|
||||
return False
|
||||
_, event_name, _, source = match.groups()
|
||||
_, thread_info, event_name, _, source = match.groups()
|
||||
return (event_name == self.event_name and
|
||||
self._check_source(source))
|
||||
self._check_source(source) and
|
||||
self._check_thread_info(thread_info))
|
||||
|
||||
|
||||
class CallEntry(_BaseEventEntry):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue