Have you ever tried to spot a certain message in screens full of debugging output? Normally, you have a hard time scanning the text in order to find the output section you were looking for. Why not making the life easier for yourself (and your users) and start printing messages organized in a hierarchical structure? Here is how it may look like:
For your convenience, here is a small python module that follows this idea. It is a logging facility which mimics file objects and keeps track of proper indentation together with colorized output. The class supports fluent interface calls and detects pipe redirections automatically. In the latter case, the messages are redirected to stderr, so the content your application sends to stdout is unaffected and the piping works as expected.
How to use it
Let’s assume, you have downloaded the source file below and now have a module file in your input path that is called output_tracker. This usage example will help you to get the idea
import output_tracker as ot o = ot.output_tracker o.print_info('Loading cached data').add_level() # do something o.print_info('Cache content A loaded') # do something o.print_info('Cache content B loaded') o.add_level().print_info('Content outdated. Rebuilding.') o.del_level(2) with o: # call external library that fills stdout # output_tracker will capture it, as long as you use the with statement print 'This line will be printed as info output.' o.print_warning(ot.errors.FOOBAR) # the error messages are static so you can use error codes o.print_error(ot.errors.FOOBAR) # terminates the application and prints an exception
If you want to disable printing messages in your application, you may use the output_tracker.null_output class which will only print error messages. Please note that you have to define the error codes and their names by yourself as only the info messages can have arbitrary content.
The source
#!/usr/bin/env python import sys class errors(object): NO_MODE_AND_INPUT = (1, 'Mode and input file missing.') NO_OUTPUT = (2, 'No output file given.') # sanity check for error codes codes = [x[1][0] for x in vars(errors).items() if x[0].upper() == x[0]] if len(codes) != len(set(codes)): raise TypeError('Duplicate error code.') class null_output(object): def __getattribute__(self, name): def print_error(error): raise Exception(error[1]) if name == 'print_error': return print_error return lambda x: self class output_tracker(object): def __enter__(self): self._stdout = sys.stdout sys.stdout = self def __exit__(self, exc_type, exc_value, traceback): sys.stdout = self._stdout def __init__(self): self._indent = 0 self._indent_steps = 3 self._stdout = sys.stdout # bash color codes self.colors = dict([ ('red', '0;31'), ('green', '0;32'), ('brown', '0;33'), ('default', '0') ]) def write(self, text): self.print_info(text.strip('\n')) def flush(self): pass def _print_multiline(self, prefix, message, color): if len(prefix) not in [0, 2]: raise ValueError('Internal Error: Wrong prefix length.') if prefix != '': prefix += ' ' if type(message) != str: message = str(message) parts = message.split('\n') target = self._stdout if not target.isatty(): target = sys.stderr target.write('{1}\033[{3}m{0}\033[0m{2}\n'.format( prefix.upper(), ' ' * self._indent, parts[0], color )) for part in parts[1:]: target.write('{0}{1}\n'.format(' ' * (self._indent + 2), part)) def print_error(self, code): if not type(code[0]) is int: raise ValueError('Error code is not numeric.') self._print_multiline( 'EE', code[1] + (' (error code %d)' % code[0]), self.colors['red'] ) raise Exception('Exception for traceback.') def print_warn(self, code): if not type(code[0]) is int: raise ValueError('Warning code is not numeric.') self._print_multiline( 'WW', code[1] + (' (warning code %d)' % code[0]), self.colors['brown'] ) return self def print_info(self, message): self._print_multiline('II', message, self.colors['green']) return self def add_level(self): self._indent += self._indent_steps return self def del_level(self, depth=1): self._indent -= self._indent_steps*depth if self._indent < 0: raise ValueError('Leftmost indentation level reached.') return self def print_plain(self, message): self._print_multiline('', message, self.colors['default']) return self