A VCS repository archival tool
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

196 linhas
6.1 KiB

  1. import argparse
  2. import contextlib
  3. import datetime
  4. import inspect
  5. import logging
  6. import requests.models
  7. # Imported in parse_args() and main() after setting up the logger:
  8. #import codearchiver.core
  9. #import codearchiver.modules
  10. #import codearchiver.version
  11. import tempfile
  12. ## Logging
  13. dumpLocals = False
  14. logger = logging # Replaced below after setting the logger class
  15. class Logger(logging.Logger):
  16. def _log_with_stack(self, level, *args, **kwargs):
  17. super().log(level, *args, **kwargs)
  18. if dumpLocals:
  19. stack = inspect.stack()
  20. if len(stack) >= 3:
  21. name = _dump_stack_and_locals(stack[2:][::-1])
  22. super().log(level, f'Dumped stack and locals to {name}')
  23. def warning(self, *args, **kwargs):
  24. self._log_with_stack(logging.WARNING, *args, **kwargs)
  25. def error(self, *args, **kwargs):
  26. self._log_with_stack(logging.ERROR, *args, **kwargs)
  27. def critical(self, *args, **kwargs):
  28. self._log_with_stack(logging.CRITICAL, *args, **kwargs)
  29. def log(self, level, *args, **kwargs):
  30. if level >= logging.WARNING:
  31. self._log_with_stack(level, *args, **kwargs)
  32. else:
  33. super().log(level, *args, **kwargs)
  34. def _requests_preparedrequest_repr(name, request):
  35. ret = []
  36. ret.append(repr(request))
  37. ret.append(f'\n {name}.method = {request.method}')
  38. ret.append(f'\n {name}.url = {request.url}')
  39. ret.append(f'\n {name}.headers = \\')
  40. for field in request.headers:
  41. ret.append(f'\n {field} = {_repr("_", request.headers[field])}')
  42. if request.body:
  43. ret.append(f'\n {name}.body = ')
  44. ret.append(_repr('_', request.body).replace('\n', '\n '))
  45. return ''.join(ret)
  46. def _requests_response_repr(name, response, withHistory = True):
  47. ret = []
  48. ret.append(repr(response))
  49. ret.append(f'\n {name}.url = {response.url}')
  50. ret.append(f'\n {name}.request = ')
  51. ret.append(_repr('_', response.request).replace('\n', '\n '))
  52. if withHistory and response.history:
  53. ret.append(f'\n {name}.history = [')
  54. for previousResponse in response.history:
  55. ret.append(f'\n ')
  56. ret.append(_requests_response_repr('_', previousResponse, withHistory = False).replace('\n', '\n '))
  57. ret.append('\n ]')
  58. ret.append(f'\n {name}.status_code = {response.status_code}')
  59. ret.append(f'\n {name}.headers = \\')
  60. for field in response.headers:
  61. ret.append(f'\n {field} = {_repr("_", response.headers[field])}')
  62. ret.append(f'\n {name}.content = {_repr("_", response.content)}')
  63. return ''.join(ret)
  64. def _repr(name, value):
  65. if type(value) is requests.models.Response:
  66. return _requests_response_repr(name, value)
  67. if type(value) is requests.models.PreparedRequest:
  68. return _requests_preparedrequest_repr(name, value)
  69. valueRepr = repr(value)
  70. if '\n' in valueRepr:
  71. return ''.join(['\\\n ', valueRepr.replace('\n', '\n ')])
  72. return valueRepr
  73. @contextlib.contextmanager
  74. def _dump_locals_on_exception():
  75. try:
  76. yield
  77. except Exception as e:
  78. trace = inspect.trace()
  79. if len(trace) >= 2:
  80. name = _dump_stack_and_locals(trace[1:], exc = e)
  81. logger.fatal(f'Dumped stack and locals to {name}')
  82. raise
  83. def _dump_stack_and_locals(trace, exc = None):
  84. with tempfile.NamedTemporaryFile('w', prefix = 'codearchiver_locals_', delete = False) as fp:
  85. if exc is not None:
  86. fp.write('Exception:\n')
  87. fp.write(f' {type(exc).__module__}.{type(exc).__name__}: {exc!s}\n')
  88. fp.write(f' args: {exc.args!r}\n')
  89. fp.write('\n')
  90. fp.write('Stack:\n')
  91. for frameRecord in trace:
  92. fp.write(f' File "{frameRecord.filename}", line {frameRecord.lineno}, in {frameRecord.function}\n')
  93. for line in frameRecord.code_context:
  94. fp.write(f' {line.strip()}\n')
  95. fp.write('\n')
  96. for frameRecord in trace:
  97. module = inspect.getmodule(frameRecord[0])
  98. if not module.__name__.startswith('codearchiver.') and module.__name__ != 'codearchiver':
  99. continue
  100. locals_ = frameRecord[0].f_locals
  101. fp.write(f'Locals from file "{frameRecord.filename}", line {frameRecord.lineno}, in {frameRecord.function}:\n')
  102. for variableName in locals_:
  103. variable = locals_[variableName]
  104. varRepr = _repr(variableName, variable)
  105. fp.write(f' {variableName} {type(variable)} = ')
  106. fp.write(varRepr.replace('\n', '\n '))
  107. fp.write('\n')
  108. fp.write('\n')
  109. if 'self' in locals_ and hasattr(locals_['self'], '__dict__'):
  110. fp.write(f'Object dict:\n')
  111. fp.write(repr(locals_['self'].__dict__))
  112. fp.write('\n\n')
  113. name = fp.name
  114. return name
  115. def parse_args():
  116. import codearchiver.core
  117. import codearchiver.modules
  118. import codearchiver.version
  119. parser = argparse.ArgumentParser(formatter_class = argparse.ArgumentDefaultsHelpFormatter)
  120. parser.add_argument('--version', action = 'version', version = f'codearchiver {codearchiver.version.__version__}')
  121. parser.add_argument('-v', '--verbose', '--verbosity', dest = 'verbosity', action = 'count', default = 0, help = 'Increase output verbosity')
  122. parser.add_argument('--dump-locals', dest = 'dumpLocals', action = 'store_true', default = False, help = 'Dump local variables on serious log messages (warnings or higher)')
  123. parser.add_argument('url', help = 'Target URL')
  124. args = parser.parse_args()
  125. return args
  126. def setup_logging():
  127. logging.setLoggerClass(Logger)
  128. global logger
  129. logger = logging.getLogger(__name__)
  130. def configure_logging(verbosity, dumpLocals_):
  131. global dumpLocals
  132. dumpLocals = dumpLocals_
  133. rootLogger = logging.getLogger()
  134. # Set level
  135. if verbosity > 0:
  136. level = logging.INFO if verbosity == 1 else logging.DEBUG
  137. rootLogger.setLevel(level)
  138. for handler in rootLogger.handlers:
  139. handler.setLevel(level)
  140. # Create formatter
  141. formatter = logging.Formatter('{asctime}.{msecs:03.0f} {levelname} {name} {message}', datefmt = '%Y-%m-%d %H:%M:%S', style = '{')
  142. # Remove existing handlers
  143. for handler in rootLogger.handlers:
  144. rootLogger.removeHandler(handler)
  145. # Add stream handler
  146. handler = logging.StreamHandler()
  147. handler.setFormatter(formatter)
  148. rootLogger.addHandler(handler)
  149. def main():
  150. setup_logging()
  151. args = parse_args()
  152. configure_logging(args.verbosity, args.dumpLocals)
  153. import codearchiver.core
  154. with _dump_locals_on_exception():
  155. inputUrl = codearchiver.core.InputURL(args.url)
  156. module = codearchiver.core.get_module_instance(inputUrl)
  157. module.process()
  158. if __name__ == '__main__':
  159. main()