output_helper.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. # -*- coding: utf-8 -*-
  2. import os, sys, re
  3. from colorama import Style
  4. from contextlib import contextmanager
  5. # RegExp for stripping color codes
  6. seq = re.compile(r'\x1B\[\d+m')
  7. FNULL = open(os.devnull, 'w')
  8. # To prevent the entire disk from being consumed, refuse to
  9. # append more lines to a log file once it's grown too large.
  10. # Logs that hit this limit are probably repeating the same
  11. # message endlessly anyway.
  12. TOO_MANY_BYTES = 50 * 1024 * 1024
  13. class Logger:
  14. '''
  15. Logs the given text and optional prefix to stdout (if quiet is False) and
  16. to an optional log file. By default, we strip out newlines in order to
  17. print our lines correctly, but you can override this functionality if you
  18. want to print multi-line output.
  19. '''
  20. def __init__(self):
  21. self.fileLogger = FileLogger()
  22. def log(self, log_text=None, squash=True, **kwargs):
  23. # set up some defaults
  24. color = kwargs.get('color', '')
  25. color_reset = Style.RESET_ALL if color else ''
  26. prefix = kwargs.get('prefix', '')
  27. border = kwargs.get('border')
  28. border_bottom = kwargs.get('border_bottom')
  29. file = kwargs.get('file')
  30. quiet = kwargs.get('quiet')
  31. if border is not None:
  32. border = color + (border * 80) + os.linesep + color_reset
  33. border_bottom = border if border_bottom is None else \
  34. color + (border_bottom * 80) + os.linesep + color_reset
  35. elif not log_text:
  36. return
  37. try:
  38. new_log_text = border or ''
  39. for line in log_text.splitlines():
  40. if line.strip() is not '':
  41. if prefix:
  42. new_log_text += Style.DIM + prefix + Style.RESET_ALL
  43. new_log_text += color + line + color_reset + os.linesep
  44. new_log_text += border_bottom or ''
  45. if not quiet:
  46. sys.stdout.write(Style.RESET_ALL + new_log_text)
  47. sys.stdout.flush()
  48. if file is not None:
  49. self.fileLogger.log(file, new_log_text, squash)
  50. except:
  51. pass
  52. class FileLogger:
  53. '''
  54. Logs text to a file
  55. '''
  56. def __init__(self):
  57. self.prev_text_count = 0
  58. self.prev_text = ''
  59. def write_to_file(self, file, text):
  60. if os.fstat(file.fileno()).st_size < TOO_MANY_BYTES:
  61. file.write(seq.sub('', text))
  62. file.flush()
  63. def write_prev_text(self, file):
  64. text = self.prev_text
  65. if self.prev_text_count > 1:
  66. text = '[%s]: %s' % (self.prev_text_count, self.prev_text)
  67. self.write_to_file(file, text)
  68. def log(self, file, text, squash):
  69. if not squash:
  70. # If we're not squashing make sure there's no prev text
  71. # to flush out
  72. if self.prev_text_count > 0:
  73. self.write_prev_text(file)
  74. self.prev_text_count = 0
  75. self.prev_text = ''
  76. # Then write the text we're not squashing
  77. self.write_to_file(file, text)
  78. # If we have matching lines, increase the counter without
  79. # writing anything to file
  80. elif self.prev_text and self.prev_text == text:
  81. self.prev_text_count += 1
  82. # If we get here, we don't have matching lines. Write the
  83. # previous text and store the current text
  84. elif self.prev_text_count > 0:
  85. self.write_prev_text(file)
  86. self.prev_text = text
  87. self.prev_text_count = 1
  88. else:
  89. self.prev_text = text
  90. self.prev_text_count = 1
  91. class QuietOutputStream:
  92. '''
  93. Provides an output stream which either writes to stdout or nothing
  94. depending on the is_quiet param.
  95. '''
  96. def __init__(self, is_quiet):
  97. self.is_quiet = is_quiet
  98. def fileno(self):
  99. with self.enable():
  100. return sys.stdout.fileno()
  101. def write(self, message):
  102. with self.enable():
  103. sys.stdout.write(message)
  104. @contextmanager
  105. def enable(self):
  106. if self.is_quiet:
  107. old_out = sys.stdout
  108. old_err = sys.stderr
  109. try:
  110. sys.stdout = FNULL
  111. sys.stderr = FNULL
  112. yield
  113. finally:
  114. sys.stdout = old_out
  115. sys.stderr = old_err
  116. else:
  117. yield