add first real cli functionality: showing log
This commit is contained in:
parent
cefde48e42
commit
de389b34b8
3 changed files with 67 additions and 24 deletions
|
@ -1,11 +1,13 @@
|
||||||
import json, os, socket, typing
|
import json, os, socket, typing
|
||||||
from src import EventManager, PollSource
|
from src import IRCBot, Logging, PollSource
|
||||||
|
|
||||||
class ControlClient(object):
|
class ControlClient(object):
|
||||||
def __init__(self, sock: socket.socket):
|
def __init__(self, sock: socket.socket):
|
||||||
self._socket = sock
|
self._socket = sock
|
||||||
self._read_buffer = b""
|
self._read_buffer = b""
|
||||||
self._write_buffer = b""
|
self._write_buffer = b""
|
||||||
|
self.version = None
|
||||||
|
self.log_level = None
|
||||||
|
|
||||||
def fileno(self) -> int:
|
def fileno(self) -> int:
|
||||||
return self._socket.fileno()
|
return self._socket.fileno()
|
||||||
|
@ -13,19 +15,14 @@ class ControlClient(object):
|
||||||
def read_lines(self) -> typing.List[str]:
|
def read_lines(self) -> typing.List[str]:
|
||||||
data = self._socket.recv(2048)
|
data = self._socket.recv(2048)
|
||||||
if not data:
|
if not data:
|
||||||
return []
|
return None
|
||||||
lines = (self._read_buffer+data).split(b"\n")
|
lines = (self._read_buffer+data).split(b"\n")
|
||||||
lines = [line.strip(b"\r") for line in lines]
|
lines = [line.strip(b"\r") for line in lines]
|
||||||
self._read_buffer = lines.pop(-1)
|
self._read_buffer = lines.pop(-1)
|
||||||
return [line.decode("utf8") for line in lines]
|
return [line.decode("utf8") for line in lines]
|
||||||
|
|
||||||
def write_line(self, line: str):
|
def write_line(self, line: str):
|
||||||
self._write_buffer += ("%s\n" % line).encode("utf8")
|
self._socket.send(("%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)
|
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
try:
|
try:
|
||||||
|
@ -39,11 +36,19 @@ class ControlClient(object):
|
||||||
|
|
||||||
|
|
||||||
class Control(PollSource.PollSource):
|
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_location = "%s.sock" % database_location
|
||||||
self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
self._clients = {}
|
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):
|
def bind(self):
|
||||||
if os.path.exists(self._socket_location):
|
if os.path.exists(self._socket_location):
|
||||||
os.remove(self._socket_location)
|
os.remove(self._socket_location)
|
||||||
|
@ -52,8 +57,6 @@ class Control(PollSource.PollSource):
|
||||||
|
|
||||||
def get_readables(self) -> typing.List[int]:
|
def get_readables(self) -> typing.List[int]:
|
||||||
return [self._socket.fileno()]+list(self._clients.keys())
|
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):
|
def is_readable(self, fileno: int):
|
||||||
if fileno == self._socket.fileno():
|
if fileno == self._socket.fileno():
|
||||||
|
@ -62,20 +65,40 @@ class Control(PollSource.PollSource):
|
||||||
elif fileno in self._clients:
|
elif fileno in self._clients:
|
||||||
client = self._clients[fileno]
|
client = self._clients[fileno]
|
||||||
lines = client.read_lines()
|
lines = client.read_lines()
|
||||||
if not lines:
|
if lines == None:
|
||||||
client.disconnect()
|
client.disconnect()
|
||||||
del self._clients[fileno]
|
del self._clients[fileno]
|
||||||
else:
|
else:
|
||||||
for line in lines:
|
for line in lines:
|
||||||
response = self._parse_line(client, line)
|
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):
|
def _parse_line(self, client: ControlClient, line: str):
|
||||||
version, _, id = line.partition(" ")
|
id, _, command = line.partition(" ")
|
||||||
id, _, data_str = id.partition(" ")
|
command, _, data = command.partition(" ")
|
||||||
if version == "0.1":
|
if not id or not command:
|
||||||
# data = json.loads(data_str)
|
client.disconnect()
|
||||||
response = {"action": "ack"}
|
return
|
||||||
return "0.1 %s %s" % (id, json.dumps(response))
|
|
||||||
|
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}))
|
||||||
|
|
|
@ -15,8 +15,18 @@ class BitBotFormatter(logging.Formatter):
|
||||||
datetime_obj = datetime.datetime.fromtimestamp(record.created)
|
datetime_obj = datetime.datetime.fromtimestamp(record.created)
|
||||||
return utils.iso8601_format(datetime_obj, milliseconds=True)
|
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):
|
class Log(object):
|
||||||
def __init__(self, to_file: bool, level: str, location: str):
|
def __init__(self, to_file: bool, level: str, location: str):
|
||||||
|
self._hooks = []
|
||||||
|
|
||||||
logging.addLevelName(LEVELS["trace"], "TRACE")
|
logging.addLevelName(LEVELS["trace"], "TRACE")
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -33,6 +43,11 @@ class Log(object):
|
||||||
stdout_handler.setFormatter(formatter)
|
stdout_handler.setFormatter(formatter)
|
||||||
self.logger.addHandler(stdout_handler)
|
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:
|
if to_file:
|
||||||
trace_path = os.path.join(location, "trace.log")
|
trace_path = os.path.join(location, "trace.log")
|
||||||
trace_handler = logging.handlers.TimedRotatingFileHandler(
|
trace_handler = logging.handlers.TimedRotatingFileHandler(
|
||||||
|
@ -54,6 +69,12 @@ class Log(object):
|
||||||
warn_handler.setFormatter(formatter)
|
warn_handler.setFormatter(formatter)
|
||||||
self.logger.addHandler(warn_handler)
|
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):
|
def trace(self, message: str, params: typing.List=None, **kwargs):
|
||||||
self._log(message, params, LEVELS["trace"], kwargs)
|
self._log(message, params, LEVELS["trace"], kwargs)
|
||||||
def debug(self, message: str, params: typing.List=None, **kwargs):
|
def debug(self, message: str, params: typing.List=None, **kwargs):
|
||||||
|
|
5
start.py
5
start.py
|
@ -96,9 +96,6 @@ events = EventManager.EventRoot(log).wrap()
|
||||||
exports = Exports.Exports()
|
exports = Exports.Exports()
|
||||||
timers = Timers.Timers(database, events, log)
|
timers = Timers.Timers(database, events, log)
|
||||||
|
|
||||||
control = Control.Control(events, args.database)
|
|
||||||
control.bind()
|
|
||||||
|
|
||||||
module_directories = [os.path.join(directory, "modules")]
|
module_directories = [os.path.join(directory, "modules")]
|
||||||
if args.external:
|
if args.external:
|
||||||
module_directories.append(os.path.abspath(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(lock_file)
|
||||||
bot.add_poll_hook(timers)
|
bot.add_poll_hook(timers)
|
||||||
|
|
||||||
|
control = Control.Control(bot, args.database)
|
||||||
|
control.bind()
|
||||||
bot.add_poll_source(control)
|
bot.add_poll_source(control)
|
||||||
|
|
||||||
if args.module:
|
if args.module:
|
||||||
|
|
Loading…
Reference in a new issue