diff options
| author | 2019-10-11 15:12:26 +0100 | |
|---|---|---|
| committer | 2019-10-11 15:12:26 +0100 | |
| commit | de389b34b817d0bcc8940730f45a2c50407df1cf (patch) | |
| tree | 0cc9047c7af43a438c6b2758dc6259172d1915ff | |
| parent | first draft of infrastructure for unix domain control socket (diff) | |
| signature | ||
add first real cli functionality: showing log
| -rw-r--r-- | src/Control.py | 65 | ||||
| -rw-r--r-- | src/Logging.py | 21 | ||||
| -rwxr-xr-x | start.py | 5 |
3 files changed, 67 insertions, 24 deletions
diff --git a/src/Control.py b/src/Control.py index 2fe742cc..19c2faaa 100644 --- a/src/Control.py +++ b/src/Control.py @@ -1,11 +1,13 @@ import json, os, socket, typing -from src import EventManager, PollSource +from src import IRCBot, Logging, PollSource class ControlClient(object): def __init__(self, sock: socket.socket): self._socket = sock self._read_buffer = b"" self._write_buffer = b"" + self.version = None + self.log_level = None def fileno(self) -> int: return self._socket.fileno() @@ -13,19 +15,14 @@ class ControlClient(object): def read_lines(self) -> typing.List[str]: data = self._socket.recv(2048) if not data: - return [] + return None lines = (self._read_buffer+data).split(b"\n") lines = [line.strip(b"\r") for line in lines] self._read_buffer = lines.pop(-1) return [line.decode("utf8") for line in lines] def write_line(self, line: str): - self._write_buffer += ("%s\n" % line).encode("utf8") - def _send(self): - sent = self._socket.send(self._write_buffer) - self._write_buffer = self._write_buffer[sent:] - def writeable(self) -> bool: - return bool(self._write_buffer) + self._socket.send(("%s\n" % line).encode("utf8")) def disconnect(self): try: @@ -39,11 +36,19 @@ class ControlClient(object): class Control(PollSource.PollSource): - def __init__(self, events: EventManager.Events, database_location): + def __init__(self, bot: IRCBot.Bot, database_location: str): + self._bot = bot + self._bot.log.hook(self._on_log) + self._socket_location = "%s.sock" % database_location self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self._clients = {} + def _on_log(self, levelno: int, line: str): + for client in self._clients.values(): + if not client.log_level == None and client.log_level <= levelno: + self._send_action(client, "log", line) + def bind(self): if os.path.exists(self._socket_location): os.remove(self._socket_location) @@ -52,8 +57,6 @@ class Control(PollSource.PollSource): def get_readables(self) -> typing.List[int]: return [self._socket.fileno()]+list(self._clients.keys()) - def get_writables(self) -> typing.List[int]: - return [f for f, c in self._clients.items() if c.writeable()] def is_readable(self, fileno: int): if fileno == self._socket.fileno(): @@ -62,20 +65,40 @@ class Control(PollSource.PollSource): elif fileno in self._clients: client = self._clients[fileno] lines = client.read_lines() - if not lines: + if lines == None: client.disconnect() del self._clients[fileno] else: for line in lines: response = self._parse_line(client, line) - client.write_line(response) - def is_writeable(self, fileno: int): - self._clients[fileno]._send() def _parse_line(self, client: ControlClient, line: str): - version, _, id = line.partition(" ") - id, _, data_str = id.partition(" ") - if version == "0.1": -# data = json.loads(data_str) - response = {"action": "ack"} - return "0.1 %s %s" % (id, json.dumps(response)) + id, _, command = line.partition(" ") + command, _, data = command.partition(" ") + if not id or not command: + client.disconnect() + return + + command = command.lower() + response_action = "ack" + response_data = None + + if command == "version": + client.version = int(data) + elif command == "log": + client.log_level = Logging.LEVELS[data.lower()] + + elif command == "command": + result = self._bot._events.on("control.command").on( + data["command"]).call_for_result(command=data["command"], + args=data["args"]) + if not result == None: + response_action = "result" + response_data = result + + self._send_action(client, response_action, response_data, id) + + def _send_action(self, client: ControlClient, action: str, data: str, + id: int=None): + client.write_line( + json.dumps({"action": action, "data": data, "id": id})) diff --git a/src/Logging.py b/src/Logging.py index 8c141c8f..9d24fce0 100644 --- a/src/Logging.py +++ b/src/Logging.py @@ -15,8 +15,18 @@ class BitBotFormatter(logging.Formatter): datetime_obj = datetime.datetime.fromtimestamp(record.created) return utils.iso8601_format(datetime_obj, milliseconds=True) +class HookedHandler(logging.StreamHandler): + def __init__(self, func: typing.Callable[[int, str], None]): + logging.StreamHandler.__init__(self) + self._func = func + + def emit(self, record): + self._func(record.levelno, self.format(record)) + class Log(object): def __init__(self, to_file: bool, level: str, location: str): + self._hooks = [] + logging.addLevelName(LEVELS["trace"], "TRACE") self.logger = logging.getLogger(__name__) @@ -33,6 +43,11 @@ class Log(object): stdout_handler.setFormatter(formatter) self.logger.addHandler(stdout_handler) + test_handler = HookedHandler(self._on_log) + test_handler.setLevel(LEVELS["debug"]) + test_handler.setFormatter(formatter) + self.logger.addHandler(test_handler) + if to_file: trace_path = os.path.join(location, "trace.log") trace_handler = logging.handlers.TimedRotatingFileHandler( @@ -54,6 +69,12 @@ class Log(object): warn_handler.setFormatter(formatter) self.logger.addHandler(warn_handler) + def hook(self, func: typing.Callable[[int, str], None]): + self._hooks.append(func) + def _on_log(self, levelno, line): + for func in self._hooks: + func(levelno, line) + def trace(self, message: str, params: typing.List=None, **kwargs): self._log(message, params, LEVELS["trace"], kwargs) def debug(self, message: str, params: typing.List=None, **kwargs): @@ -96,9 +96,6 @@ events = EventManager.EventRoot(log).wrap() exports = Exports.Exports() timers = Timers.Timers(database, events, log) -control = Control.Control(events, args.database) -control.bind() - module_directories = [os.path.join(directory, "modules")] if args.external: module_directories.append(os.path.abspath(args.external)) @@ -114,6 +111,8 @@ bot.add_poll_hook(cache) bot.add_poll_hook(lock_file) bot.add_poll_hook(timers) +control = Control.Control(bot, args.database) +control.bind() bot.add_poll_source(control) if args.module: |
