You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

combine_logs.py 4.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. #!/usr/bin/env python3
  2. """Combine logs from multiple bitcoin nodes as well as the test_framework log.
  3. This streams the combined log output to stdout. Use combine_logs.py > outputfile
  4. to write to an outputfile."""
  5. import argparse
  6. from collections import defaultdict, namedtuple
  7. import heapq
  8. import itertools
  9. import os
  10. import re
  11. import sys
  12. # Matches on the date format at the start of the log event
  13. TIMESTAMP_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}")
  14. LogEvent = namedtuple('LogEvent', ['timestamp', 'source', 'event'])
  15. def main():
  16. """Main function. Parses args, reads the log files and renders them as text or html."""
  17. parser = argparse.ArgumentParser(usage='%(prog)s [options] <test temporary directory>', description=__doc__)
  18. parser.add_argument('-c', '--color', dest='color', action='store_true', help='outputs the combined log with events colored by source (requires posix terminal colors. Use less -r for viewing)')
  19. parser.add_argument('--html', dest='html', action='store_true', help='outputs the combined log as html. Requires jinja2. pip install jinja2')
  20. args, unknown_args = parser.parse_known_args()
  21. if args.color and os.name != 'posix':
  22. print("Color output requires posix terminal colors.")
  23. sys.exit(1)
  24. if args.html and args.color:
  25. print("Only one out of --color or --html should be specified")
  26. sys.exit(1)
  27. # There should only be one unknown argument - the path of the temporary test directory
  28. if len(unknown_args) != 1:
  29. print("Unexpected arguments" + str(unknown_args))
  30. sys.exit(1)
  31. log_events = read_logs(unknown_args[0])
  32. print_logs(log_events, color=args.color, html=args.html)
  33. def read_logs(tmp_dir):
  34. """Reads log files.
  35. Delegates to generator function get_log_events() to provide individual log events
  36. for each of the input log files."""
  37. files = [("test", "%s/test_framework.log" % tmp_dir)]
  38. for i in itertools.count():
  39. logfile = "{}/node{}/regtest/debug.log".format(tmp_dir, i)
  40. if not os.path.isfile(logfile):
  41. break
  42. files.append(("node%d" % i, logfile))
  43. return heapq.merge(*[get_log_events(source, f) for source, f in files])
  44. def get_log_events(source, logfile):
  45. """Generator function that returns individual log events.
  46. Log events may be split over multiple lines. We use the timestamp
  47. regex match as the marker for a new log event."""
  48. try:
  49. with open(logfile, 'r') as infile:
  50. event = ''
  51. timestamp = ''
  52. for line in infile:
  53. # skip blank lines
  54. if line == '\n':
  55. continue
  56. # if this line has a timestamp, it's the start of a new log event.
  57. time_match = TIMESTAMP_PATTERN.match(line)
  58. if time_match:
  59. if event:
  60. yield LogEvent(timestamp=timestamp, source=source, event=event.rstrip())
  61. event = line
  62. timestamp = time_match.group()
  63. # if it doesn't have a timestamp, it's a continuation line of the previous log.
  64. else:
  65. event += "\n" + line
  66. # Flush the final event
  67. yield LogEvent(timestamp=timestamp, source=source, event=event.rstrip())
  68. except FileNotFoundError:
  69. print("File %s could not be opened. Continuing without it." % logfile, file=sys.stderr)
  70. def print_logs(log_events, color=False, html=False):
  71. """Renders the iterator of log events into text or html."""
  72. if not html:
  73. colors = defaultdict(lambda: '')
  74. if color:
  75. colors["test"] = "\033[0;36m" # CYAN
  76. colors["node0"] = "\033[0;34m" # BLUE
  77. colors["node1"] = "\033[0;32m" # GREEN
  78. colors["node2"] = "\033[0;31m" # RED
  79. colors["node3"] = "\033[0;33m" # YELLOW
  80. colors["reset"] = "\033[0m" # Reset font color
  81. for event in log_events:
  82. print("{0} {1: <5} {2} {3}".format(colors[event.source.rstrip()], event.source, event.event, colors["reset"]))
  83. else:
  84. try:
  85. import jinja2
  86. except ImportError:
  87. print("jinja2 not found. Try `pip install jinja2`")
  88. sys.exit(1)
  89. print(jinja2.Environment(loader=jinja2.FileSystemLoader('./'))
  90. .get_template('combined_log_template.html')
  91. .render(title="Combined Logs from testcase", log_events=[event._asdict() for event in log_events]))
  92. if __name__ == '__main__':
  93. main()