Skip to content

Commit

Permalink
Show colored output
Browse files Browse the repository at this point in the history
  • Loading branch information
cool-RR committed Jan 14, 2022
1 parent 31bfc63 commit 0f1e67b
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 96 deletions.
8 changes: 7 additions & 1 deletion ADVANCED_USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,10 @@ can customize that:
You can also use `max_variable_length=None` to never truncate them.

Use `relative_time=True` to show timestamps relative to start time rather than
wall time.
wall time.

The output is colored for easy viewing by default, except on Windows. Disable colors like so:

```python
@pysnooper.snoop(color=False)
````
31 changes: 1 addition & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,36 +34,7 @@ number_to_bits(6)
```
The output to stderr is:

```
Source path:... /my_code/foo.py
Starting var:.. number = 6
15:29:11.327032 call 4 def number_to_bits(number):
15:29:11.327032 line 5 if number:
15:29:11.327032 line 6 bits = []
New var:....... bits = []
15:29:11.327032 line 7 while number:
15:29:11.327032 line 8 number, remainder = divmod(number, 2)
New var:....... remainder = 0
Modified var:.. number = 3
15:29:11.327032 line 9 bits.insert(0, remainder)
Modified var:.. bits = [0]
15:29:11.327032 line 7 while number:
15:29:11.327032 line 8 number, remainder = divmod(number, 2)
Modified var:.. number = 1
Modified var:.. remainder = 1
15:29:11.327032 line 9 bits.insert(0, remainder)
Modified var:.. bits = [1, 0]
15:29:11.327032 line 7 while number:
15:29:11.327032 line 8 number, remainder = divmod(number, 2)
Modified var:.. number = 0
15:29:11.327032 line 9 bits.insert(0, remainder)
Modified var:.. bits = [1, 1, 0]
15:29:11.327032 line 7 while number:
15:29:11.327032 line 10 return bits
15:29:11.327032 return 10 return bits
Return value:.. [1, 1, 0]
Elapsed time: 00:00:00.000001
```
![](https://i.imgur.com/TrF3VVj.jpg)

Or if you don't want to trace an entire function, you can wrap the relevant part in a `with` block:

Expand Down
Binary file added misc/output.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
90 changes: 74 additions & 16 deletions pysnooper/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,16 @@ class Tracer:
@pysnooper.snoop(relative_time=True)
The output is colored for easy viewing by default, except on Windows.
Disable colors like so:
@pysnooper.snoop(color=False)
'''
def __init__(self, output=None, watch=(), watch_explode=(), depth=1,
prefix='', overwrite=False, thread_info=False, custom_repr=(),
max_variable_length=100, normalize=False, relative_time=False):
max_variable_length=100, normalize=False, relative_time=False,
color=True):
self._write = get_write_function(output, overwrite)

self.watch = [
Expand Down Expand Up @@ -233,6 +239,33 @@ def __init__(self, output=None, watch=(), watch_explode=(), depth=1,
self.max_variable_length = max_variable_length
self.normalize = normalize
self.relative_time = relative_time
self.color = color and sys.platform in ('linux', 'linux2', 'cygwin',
'darwin')

if self.color:
self._FOREGROUND_BLUE = '\x1b[34m'
self._FOREGROUND_CYAN = '\x1b[36m'
self._FOREGROUND_GREEN = '\x1b[32m'
self._FOREGROUND_MAGENTA = '\x1b[35m'
self._FOREGROUND_RED = '\x1b[31m'
self._FOREGROUND_RESET = '\x1b[39m'
self._FOREGROUND_YELLOW = '\x1b[33m'
self._STYLE_BRIGHT = '\x1b[1m'
self._STYLE_DIM = '\x1b[2m'
self._STYLE_NORMAL = '\x1b[22m'
self._STYLE_RESET_ALL = '\x1b[0m'
else:
self._FOREGROUND_BLUE = ''
self._FOREGROUND_CYAN = ''
self._FOREGROUND_GREEN = ''
self._FOREGROUND_MAGENTA = ''
self._FOREGROUND_RED = ''
self._FOREGROUND_RESET = ''
self._FOREGROUND_YELLOW = ''
self._STYLE_BRIGHT = ''
self._STYLE_DIM = ''
self._STYLE_NORMAL = ''
self._STYLE_RESET_ALL = ''

def __call__(self, function_or_class):
if DISABLED:
Expand Down Expand Up @@ -317,12 +350,19 @@ def __exit__(self, exc_type, exc_value, exc_traceback):

### Writing elapsed time: #############################################
# #
_FOREGROUND_YELLOW = self._FOREGROUND_YELLOW
_STYLE_DIM = self._STYLE_DIM
_STYLE_NORMAL = self._STYLE_NORMAL
_STYLE_RESET_ALL = self._STYLE_RESET_ALL

start_time = self.start_times.pop(calling_frame)
duration = datetime_module.datetime.now() - start_time
elapsed_time_string = pycompat.timedelta_format(duration)
indent = ' ' * 4 * (thread_global.depth + 1)
self.write(
'{indent}Elapsed time: {elapsed_time_string}'.format(**locals())
'{indent}{_FOREGROUND_YELLOW}{_STYLE_DIM}'
'Elapsed time: {_STYLE_NORMAL}{elapsed_time_string}'
'{_STYLE_RESET_ALL}'.format(**locals())
)
# #
### Finished writing elapsed time. ####################################
Expand Down Expand Up @@ -363,12 +403,24 @@ def trace(self, frame, event, arg):
else:
return None

# #
### Finished checking whether we should trace this line. ##############

if event == 'call':
thread_global.depth += 1
indent = ' ' * 4 * thread_global.depth

# #
### Finished checking whether we should trace this line. ##############
_FOREGROUND_BLUE = self._FOREGROUND_BLUE
_FOREGROUND_CYAN = self._FOREGROUND_CYAN
_FOREGROUND_GREEN = self._FOREGROUND_GREEN
_FOREGROUND_MAGENTA = self._FOREGROUND_MAGENTA
_FOREGROUND_RED = self._FOREGROUND_RED
_FOREGROUND_RESET = self._FOREGROUND_RESET
_FOREGROUND_YELLOW = self._FOREGROUND_YELLOW
_STYLE_BRIGHT = self._STYLE_BRIGHT
_STYLE_DIM = self._STYLE_DIM
_STYLE_NORMAL = self._STYLE_NORMAL
_STYLE_RESET_ALL = self._STYLE_RESET_ALL

### Making timestamp: #################################################
# #
Expand All @@ -394,8 +446,9 @@ def trace(self, frame, event, arg):
source_path, source = get_path_and_source_from_frame(frame)
source_path = source_path if not self.normalize else os.path.basename(source_path)
if self.last_source_path != source_path:
self.write(u'{indent}Source path:... {source_path}'.
format(**locals()))
self.write(u'{_FOREGROUND_YELLOW}{_STYLE_DIM}{indent}Source path:... '
u'{_STYLE_NORMAL}{source_path}'
u'{_STYLE_RESET_ALL}'.format(**locals()))
self.last_source_path = source_path
source_line = source[line_no - 1]
thread_info = ""
Expand Down Expand Up @@ -423,11 +476,13 @@ def trace(self, frame, event, arg):

for name, value_repr in local_reprs.items():
if name not in old_local_reprs:
self.write('{indent}{newish_string}{name} = {value_repr}'.format(
**locals()))
self.write('{indent}{_FOREGROUND_GREEN}{_STYLE_DIM}'
'{newish_string}{_STYLE_NORMAL}{name} = '
'{value_repr}{_STYLE_RESET_ALL}'.format(**locals()))
elif old_local_reprs[name] != value_repr:
self.write('{indent}Modified var:.. {name} = {value_repr}'.format(
**locals()))
self.write('{indent}{_FOREGROUND_GREEN}{_STYLE_DIM}'
'Modified var:.. {_STYLE_NORMAL}{name} = '
'{value_repr}{_STYLE_RESET_ALL}'.format(**locals()))

# #
### Finished newish and modified variables. ###########################
Expand Down Expand Up @@ -468,11 +523,11 @@ def trace(self, frame, event, arg):
)

if ended_by_exception:
self.write('{indent}Call ended by exception'.
self.write('{_FOREGROUND_RED}{indent}Call ended by exception{_STYLE_RESET_ALL}'.
format(**locals()))
else:
self.write(u'{indent}{timestamp} {thread_info}{event:9} '
u'{line_no:4} {source_line}'.format(**locals()))
self.write(u'{indent}{_STYLE_DIM}{timestamp} {thread_info}{event:9} '
u'{line_no:4}{_STYLE_RESET_ALL} {source_line}'.format(**locals()))

if event == 'return':
self.frame_to_local_reprs.pop(frame, None)
Expand All @@ -485,14 +540,17 @@ def trace(self, frame, event, arg):
max_length=self.max_variable_length,
normalize=self.normalize,
)
self.write('{indent}Return value:.. {return_value_repr}'.
self.write('{indent}{_FOREGROUND_CYAN}{_STYLE_DIM}'
'Return value:.. {_STYLE_NORMAL}{return_value_repr}'
'{_STYLE_RESET_ALL}'.
format(**locals()))

if event == 'exception':
exception = '\n'.join(traceback.format_exception_only(*arg[:2])).strip()
if self.max_variable_length:
exception = utils.truncate(exception, self.max_variable_length)
self.write('{indent}Exception:..... {exception}'.
format(**locals()))
self.write('{indent}{_FOREGROUND_RED}Exception:..... '
'{_STYLE_BRIGHT}{exception}'
'{_STYLE_RESET_ALL}'.format(**locals()))

return self.trace
2 changes: 1 addition & 1 deletion tests/samples/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def bar():
raise


@pysnooper.snoop(depth=3)
@pysnooper.snoop(depth=3, color=False)
def main():
try:
bar()
Expand Down
4 changes: 2 additions & 2 deletions tests/samples/indentation.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pysnooper


@pysnooper.snoop(depth=2)
@pysnooper.snoop(depth=2, color=False)
def main():
f2()

Expand All @@ -14,7 +14,7 @@ def f3():
f4()


@pysnooper.snoop(depth=2)
@pysnooper.snoop(depth=2, color=False)
def f4():
f5()

Expand Down
2 changes: 1 addition & 1 deletion tests/samples/recursion.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pysnooper


@pysnooper.snoop(depth=2)
@pysnooper.snoop(depth=2, color=False)
def factorial(x):
if x <= 1:
return 1
Expand Down
2 changes: 1 addition & 1 deletion tests/test_chinese.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
def test_chinese():
with mini_toolbox.create_temp_folder(prefix='pysnooper') as folder:
path = folder / 'foo.log'
@pysnooper.snoop(path)
@pysnooper.snoop(path, color=False)
def foo():
a = 1
x = '失败'
Expand Down
2 changes: 1 addition & 1 deletion tests/test_multiple_files/multiple_files/foo.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from .bar import bar_function

@pysnooper.snoop(depth=2)
@pysnooper.snoop(depth=2, color=False)
def foo_function():
z = bar_function(3)
return z
4 changes: 2 additions & 2 deletions tests/test_not_implemented.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async def foo(x):
assert pycompat.iscoroutinefunction(foo)
assert not pycompat.isasyncgenfunction(foo)
with pytest.raises(NotImplementedError):
pysnooper.snoop()(foo)
pysnooper.snoop(color=False)(foo)


def test_rejecting_async_generator_functions():
Expand All @@ -56,6 +56,6 @@ async def foo(x):
assert not pycompat.iscoroutinefunction(foo)
assert pycompat.isasyncgenfunction(foo)
with pytest.raises(NotImplementedError):
pysnooper.snoop()(foo)
pysnooper.snoop(color=False)(foo)


Loading

0 comments on commit 0f1e67b

Please sign in to comment.