at 23.05-pre 3.4 kB view raw
1from colorama import Style, Fore 2from contextlib import contextmanager 3from typing import Any, Dict, Iterator 4from queue import Queue, Empty 5from xml.sax.saxutils import XMLGenerator 6import codecs 7import os 8import sys 9import time 10import unicodedata 11 12 13class Logger: 14 def __init__(self) -> None: 15 self.logfile = os.environ.get("LOGFILE", "/dev/null") 16 self.logfile_handle = codecs.open(self.logfile, "wb") 17 self.xml = XMLGenerator(self.logfile_handle, encoding="utf-8") 18 self.queue: "Queue[Dict[str, str]]" = Queue() 19 20 self.xml.startDocument() 21 self.xml.startElement("logfile", attrs={}) 22 23 self._print_serial_logs = True 24 25 @staticmethod 26 def _eprint(*args: object, **kwargs: Any) -> None: 27 print(*args, file=sys.stderr, **kwargs) 28 29 def close(self) -> None: 30 self.xml.endElement("logfile") 31 self.xml.endDocument() 32 self.logfile_handle.close() 33 34 def sanitise(self, message: str) -> str: 35 return "".join(ch for ch in message if unicodedata.category(ch)[0] != "C") 36 37 def maybe_prefix(self, message: str, attributes: Dict[str, str]) -> str: 38 if "machine" in attributes: 39 return "{}: {}".format(attributes["machine"], message) 40 return message 41 42 def log_line(self, message: str, attributes: Dict[str, str]) -> None: 43 self.xml.startElement("line", attributes) 44 self.xml.characters(message) 45 self.xml.endElement("line") 46 47 def info(self, *args, **kwargs) -> None: # type: ignore 48 self.log(*args, **kwargs) 49 50 def warning(self, *args, **kwargs) -> None: # type: ignore 51 self.log(*args, **kwargs) 52 53 def error(self, *args, **kwargs) -> None: # type: ignore 54 self.log(*args, **kwargs) 55 sys.exit(1) 56 57 def log(self, message: str, attributes: Dict[str, str] = {}) -> None: 58 self._eprint(self.maybe_prefix(message, attributes)) 59 self.drain_log_queue() 60 self.log_line(message, attributes) 61 62 def log_serial(self, message: str, machine: str) -> None: 63 self.enqueue({"msg": message, "machine": machine, "type": "serial"}) 64 if self._print_serial_logs: 65 self._eprint( 66 Style.DIM + "{} # {}".format(machine, message) + Style.RESET_ALL 67 ) 68 69 def enqueue(self, item: Dict[str, str]) -> None: 70 self.queue.put(item) 71 72 def drain_log_queue(self) -> None: 73 try: 74 while True: 75 item = self.queue.get_nowait() 76 msg = self.sanitise(item["msg"]) 77 del item["msg"] 78 self.log_line(msg, item) 79 except Empty: 80 pass 81 82 @contextmanager 83 def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]: 84 self._eprint( 85 self.maybe_prefix( 86 Style.BRIGHT + Fore.GREEN + message + Style.RESET_ALL, attributes 87 ) 88 ) 89 90 self.xml.startElement("nest", attrs={}) 91 self.xml.startElement("head", attributes) 92 self.xml.characters(message) 93 self.xml.endElement("head") 94 95 tic = time.time() 96 self.drain_log_queue() 97 yield 98 self.drain_log_queue() 99 toc = time.time() 100 self.log("(finished: {}, in {:.2f} seconds)".format(message, toc - tic)) 101 102 self.xml.endElement("nest") 103 104 105rootlog = Logger()