@@ -4,12 +4,12 @@ | |||||
# Ignore bot-specific config file: | # Ignore bot-specific config file: | ||||
config.json | config.json | ||||
# Ignore logs directory: | |||||
logs/ | |||||
# Ignore cookies file: | # Ignore cookies file: | ||||
.cookies | .cookies | ||||
# Ignore statistics file: | |||||
statistics.txt | |||||
# Ignore OS X's crud: | # Ignore OS X's crud: | ||||
.DS_Store | .DS_Store | ||||
@@ -12,12 +12,13 @@ class Connection(object): | |||||
"""A class to interface with IRC.""" | """A class to interface with IRC.""" | ||||
def __init__(self, host=None, port=None, nick=None, ident=None, | def __init__(self, host=None, port=None, nick=None, ident=None, | ||||
realname=None): | |||||
realname=None, logger=None): | |||||
self.host = host | self.host = host | ||||
self.port = port | self.port = port | ||||
self.nick = nick | self.nick = nick | ||||
self.ident = ident | self.ident = ident | ||||
self.realname = realname | self.realname = realname | ||||
self.logger = logger | |||||
# A lock to prevent us from sending two messages at once: | # A lock to prevent us from sending two messages at once: | ||||
self.lock = threading.Lock() | self.lock = threading.Lock() | ||||
@@ -50,7 +51,7 @@ class Connection(object): | |||||
# Ensure that we only send one message at a time with a blocking lock: | # Ensure that we only send one message at a time with a blocking lock: | ||||
with self.lock: | with self.lock: | ||||
self.sock.sendall(msg + "\r\n") | self.sock.sendall(msg + "\r\n") | ||||
print " %s" % msg | |||||
self.logger.debug(msg) | |||||
def say(self, target, msg): | def say(self, target, msg): | ||||
"""Send a private message to a target on the server.""" | """Send a private message to a target on the server.""" | ||||
@@ -7,9 +7,9 @@ This package provides the IRC "commands" used by the bot's front-end component. | |||||
In __init__, you can find some functions used to load and run these commands. | In __init__, you can find some functions used to load and run these commands. | ||||
""" | """ | ||||
import logging | |||||
import os | import os | ||||
import sys | import sys | ||||
import traceback | |||||
from classes import BaseCommand | from classes import BaseCommand | ||||
import config | import config | ||||
@@ -23,6 +23,9 @@ base_dir = os.path.join(config.root_dir, "bot", "commands") | |||||
# is an instance of the command's class: | # is an instance of the command's class: | ||||
_commands = {} | _commands = {} | ||||
# Logger for this module: | |||||
logger = logging.getLogger("tasks") | |||||
def _load_command(connection, filename): | def _load_command(connection, filename): | ||||
"""Try to load a specific command from a module, identified by file name. | """Try to load a specific command from a module, identified by file name. | ||||
@@ -39,8 +42,7 @@ def _load_command(connection, filename): | |||||
try: | try: | ||||
__import__(name) | __import__(name) | ||||
except: | except: | ||||
print "Couldn't load file {0}:".format(filename) | |||||
traceback.print_exc() | |||||
logger.exception("Couldn't load file {0}".format(filename)) | |||||
return | return | ||||
command = sys.modules[name].Command(connection) | command = sys.modules[name].Command(connection) | ||||
@@ -48,7 +50,7 @@ def _load_command(connection, filename): | |||||
return | return | ||||
_commands[command.name] = command | _commands[command.name] = command | ||||
print "Added command {0}...".format(command.name) | |||||
logger.debug("Added command {0}".format(command.name)) | |||||
def load(connection): | def load(connection): | ||||
"""Load all valid commands into the _commands global variable. | """Load all valid commands into the _commands global variable. | ||||
@@ -67,8 +69,8 @@ def load(connection): | |||||
except AttributeError: | except AttributeError: | ||||
pass # The file is doesn't contain a command, so just move on | pass # The file is doesn't contain a command, so just move on | ||||
msg = "Found {0} commands: {1}." | |||||
print msg.format(len(_commands), ", ".join(_commands.keys())) | |||||
msg = "Found {0} commands: {1}" | |||||
logger.info(msg.format(len(_commands), ", ".join(_commands.keys()))) | |||||
def get_all(): | def get_all(): | ||||
"""Return our dict of all loaded commands.""" | """Return our dict of all loaded commands.""" | ||||
@@ -85,6 +87,5 @@ def check(hook, data): | |||||
try: | try: | ||||
command.process(data) | command.process(data) | ||||
except: | except: | ||||
print "Error executing command '{0}':".format(data.command) | |||||
traceback.print_exc() | |||||
logger.exception("Error executing command '{0}'".format(data.command)) | |||||
break | break |
@@ -70,7 +70,7 @@ def _setup_logging(): | |||||
stream_handler = logging.StreamHandler() | stream_handler = logging.StreamHandler() | ||||
main_handler.setLevel(logging.INFO) | main_handler.setLevel(logging.INFO) | ||||
error_handler.setLevel(logging.ERROR) | |||||
error_handler.setLevel(logging.WARNING) | |||||
debug_handler.setLevel(logging.DEBUG) | debug_handler.setLevel(logging.DEBUG) | ||||
stream_handler.setLevel(logging.DEBUG) | stream_handler.setLevel(logging.DEBUG) | ||||
@@ -9,6 +9,7 @@ of BaseCommand in irc/base_command.py. All command classes are automatically | |||||
imported by irc/command_handler.py if they are in irc/commands. | imported by irc/command_handler.py if they are in irc/commands. | ||||
""" | """ | ||||
import logging | |||||
import re | import re | ||||
import config | import config | ||||
@@ -18,6 +19,7 @@ from classes import Connection, Data, BrokenSocketException | |||||
__all__ = ["get_connection", "startup", "main"] | __all__ = ["get_connection", "startup", "main"] | ||||
connection = None | connection = None | ||||
logger = logging.getLogger("frontend") | |||||
sender_regex = re.compile(":(.*?)!(.*?)@(.*?)\Z") | sender_regex = re.compile(":(.*?)!(.*?)@(.*?)\Z") | ||||
def get_connection(): | def get_connection(): | ||||
@@ -25,7 +27,7 @@ def get_connection(): | |||||
connection, but don't actually connect yet.""" | connection, but don't actually connect yet.""" | ||||
cf = config.irc["frontend"] | cf = config.irc["frontend"] | ||||
connection = Connection(cf["host"], cf["port"], cf["nick"], cf["ident"], | connection = Connection(cf["host"], cf["port"], cf["nick"], cf["ident"], | ||||
cf["realname"]) | |||||
cf["realname"], logger) | |||||
return connection | return connection | ||||
def startup(conn): | def startup(conn): | ||||
@@ -48,7 +50,7 @@ def main(): | |||||
try: | try: | ||||
read_buffer = read_buffer + connection.get() | read_buffer = read_buffer + connection.get() | ||||
except BrokenSocketException: | except BrokenSocketException: | ||||
print "Socket has broken on front-end; restarting bot..." | |||||
logger.warn("Socket has broken on front-end; restarting bot") | |||||
return | return | ||||
lines = read_buffer.split("\n") | lines = read_buffer.split("\n") | ||||
@@ -90,7 +92,7 @@ def _process_message(line): | |||||
# ordinary command): | # ordinary command): | ||||
if data.msg in ["!restart", ".restart"]: | if data.msg in ["!restart", ".restart"]: | ||||
if data.host in config.irc["permissions"]["owners"]: | if data.host in config.irc["permissions"]["owners"]: | ||||
print "Restarting bot per owner request..." | |||||
logger.info("Restarting bot per owner request") | |||||
return True | return True | ||||
# If we are pinged, pong back: | # If we are pinged, pong back: | ||||
@@ -30,9 +30,9 @@ There is a "priority" system here: | |||||
Else, the bot will stop, as no components are enabled. | Else, the bot will stop, as no components are enabled. | ||||
""" | """ | ||||
import logging | |||||
import threading | import threading | ||||
import time | import time | ||||
import traceback | |||||
import config | import config | ||||
import frontend | import frontend | ||||
@@ -49,13 +49,12 @@ def irc_watcher(f_conn=None): | |||||
while 1: # restart the watcher component if it breaks (and nothing else) | while 1: # restart the watcher component if it breaks (and nothing else) | ||||
w_conn = watcher.get_connection() | w_conn = watcher.get_connection() | ||||
w_conn.connect() | w_conn.connect() | ||||
print # blank line to signify that the bot has finished starting up | |||||
try: | try: | ||||
watcher.main(w_conn, f_conn) | watcher.main(w_conn, f_conn) | ||||
except: | except: | ||||
traceback.print_exc() | |||||
logging.exception("Watcher had an error") | |||||
time.sleep(5) # sleep a bit before restarting watcher | time.sleep(5) # sleep a bit before restarting watcher | ||||
print "\nWatcher has stopped; restarting component..." | |||||
logging.warn("Watcher has stopped; restarting component") | |||||
def wiki_scheduler(): | def wiki_scheduler(): | ||||
"""Function to handle the wiki scheduler as another thread, or as the | """Function to handle the wiki scheduler as another thread, or as the | ||||
@@ -77,12 +76,12 @@ def irc_frontend(): | |||||
enabled.""" | enabled.""" | ||||
global f_conn | global f_conn | ||||
print "Starting IRC frontend..." | |||||
logging.info("Starting IRC frontend") | |||||
f_conn = frontend.get_connection() | f_conn = frontend.get_connection() | ||||
frontend.startup(f_conn) | frontend.startup(f_conn) | ||||
if "wiki_schedule" in config.components: | if "wiki_schedule" in config.components: | ||||
print "\nStarting wiki scheduler..." | |||||
logging.info("Starting wiki scheduler") | |||||
tasks.load() | tasks.load() | ||||
t_scheduler = threading.Thread(target=wiki_scheduler) | t_scheduler = threading.Thread(target=wiki_scheduler) | ||||
t_scheduler.name = "wiki-scheduler" | t_scheduler.name = "wiki-scheduler" | ||||
@@ -90,7 +89,7 @@ def irc_frontend(): | |||||
t_scheduler.start() | t_scheduler.start() | ||||
if "irc_watcher" in config.components: | if "irc_watcher" in config.components: | ||||
print "\nStarting IRC watcher..." | |||||
logging.info("Starting IRC watcher") | |||||
t_watcher = threading.Thread(target=irc_watcher, args=(f_conn,)) | t_watcher = threading.Thread(target=irc_watcher, args=(f_conn,)) | ||||
t_watcher.name = "irc-watcher" | t_watcher.name = "irc-watcher" | ||||
t_watcher.daemon = True | t_watcher.daemon = True | ||||
@@ -105,40 +104,47 @@ def irc_frontend(): | |||||
def run(): | def run(): | ||||
config.load() | config.load() | ||||
try: | try: | ||||
key = raw_input() # wait for our password decrypt key from the bot's | |||||
except EOFError: # wrapper, then decrypt passwords | |||||
# Wait for our password decrypt key from the bot's wrapper, then | |||||
# decrypt passwords: | |||||
key = raw_input() | |||||
except EOFError: | |||||
pass | pass | ||||
else: | else: | ||||
config.decrypt(key) | config.decrypt(key) | ||||
enabled = config.components | enabled = config.components | ||||
if "irc_frontend" in enabled: # make the frontend run on our primary | |||||
irc_frontend() # thread if enabled, and enable additional | |||||
# components through that function | |||||
if "irc_frontend" in enabled: | |||||
# Make the frontend run on our primary thread if enabled, and enable | |||||
# additional components through that function | |||||
irc_frontend() | |||||
elif "wiki_schedule" in enabled: # run the scheduler on the main | |||||
print "Starting wiki scheduler..." # thread, but also run the IRC | |||||
tasks.load() # watcher on another thread iff it | |||||
if "irc_watcher" in enabled: # is enabled | |||||
print "\nStarting IRC watcher..." | |||||
elif "wiki_schedule" in enabled: | |||||
# Run the scheduler on the main thread, but also run the IRC watcher on | |||||
# another thread iff it is enabled | |||||
logging.info("Starting wiki scheduler") | |||||
tasks.load() | |||||
if "irc_watcher" in enabled: | |||||
logging.info("Starting IRC watcher") | |||||
t_watcher = threading.Thread(target=irc_watcher) | t_watcher = threading.Thread(target=irc_watcher) | ||||
t_watcher.name = "irc-watcher" | t_watcher.name = "irc-watcher" | ||||
t_watcher.daemon = True | t_watcher.daemon = True | ||||
t_watcher.start() | t_watcher.start() | ||||
wiki_scheduler() | wiki_scheduler() | ||||
elif "irc_watcher" in enabled: # the IRC watcher is our only enabled | |||||
print "Starting IRC watcher..." # component, so run its function only | |||||
irc_watcher() # and don't worry about anything else | |||||
elif "irc_watcher" in enabled: | |||||
# The IRC watcher is our only enabled component, so run its function | |||||
# only and don't worry about anything else: | |||||
logging.info("Starting IRC watcher") | |||||
irc_watcher() | |||||
else: # nothing is enabled! | |||||
print "No bot parts are enabled; stopping..." | |||||
else: # Nothing is enabled! | |||||
logging.critical("No bot parts are enabled; stopping") | |||||
exit(1) | exit(1) | ||||
if __name__ == "__main__": | if __name__ == "__main__": | ||||
try: | try: | ||||
run() | run() | ||||
except KeyboardInterrupt: | except KeyboardInterrupt: | ||||
print "\nKeyboardInterrupt: stopping main bot loop." | |||||
logging.critical("KeyboardInterrupt: stopping main bot loop") | |||||
exit(1) | exit(1) |
@@ -7,11 +7,11 @@ This package provides the wiki bot "tasks" EarwigBot runs. Here in __init__, | |||||
you can find some functions used to load and run these tasks. | you can find some functions used to load and run these tasks. | ||||
""" | """ | ||||
import logging | |||||
import os | import os | ||||
import sys | import sys | ||||
import threading | import threading | ||||
import time | import time | ||||
import traceback | |||||
from classes import BaseTask | from classes import BaseTask | ||||
import config | import config | ||||
@@ -25,6 +25,9 @@ base_dir = os.path.join(config.root_dir, "bot", "tasks") | |||||
# an instance of the task class: | # an instance of the task class: | ||||
_tasks = {} | _tasks = {} | ||||
# Logger for this module: | |||||
logger = logging.getLogger("commands") | |||||
def _load_task(filename): | def _load_task(filename): | ||||
"""Try to load a specific task from a module, identified by file name.""" | """Try to load a specific task from a module, identified by file name.""" | ||||
global _tasks | global _tasks | ||||
@@ -34,8 +37,7 @@ def _load_task(filename): | |||||
try: | try: | ||||
__import__(name) | __import__(name) | ||||
except: | except: | ||||
print "Couldn't load file {0}:".format(filename) | |||||
traceback.print_exc() | |||||
logger.exception("Couldn't load file {0}:".format(filename)) | |||||
return | return | ||||
task = sys.modules[name].Task() | task = sys.modules[name].Task() | ||||
@@ -44,18 +46,17 @@ def _load_task(filename): | |||||
return | return | ||||
_tasks[task.name] = task | _tasks[task.name] = task | ||||
print "Added task {0}...".format(task.name) | |||||
logger.debug("Added task {0}".format(task.name)) | |||||
def _wrapper(task, **kwargs): | def _wrapper(task, **kwargs): | ||||
"""Wrapper for task classes: run the task and catch any errors.""" | """Wrapper for task classes: run the task and catch any errors.""" | ||||
try: | try: | ||||
task.run(**kwargs) | task.run(**kwargs) | ||||
except: | except: | ||||
error = "Task '{0}' raised an exception and had to stop:" | |||||
print error.format(task.name) | |||||
traceback.print_exc() | |||||
error = "Task '{0}' raised an exception and had to stop" | |||||
logger.exception(error.format(task.name)) | |||||
else: | else: | ||||
print "Task '{0}' finished without error.".format(task.name) | |||||
logger.info("Task '{0}' finished without error".format(task.name)) | |||||
def load(): | def load(): | ||||
"""Load all valid tasks from bot/tasks/, into the _tasks variable.""" | """Load all valid tasks from bot/tasks/, into the _tasks variable.""" | ||||
@@ -70,7 +71,7 @@ def load(): | |||||
except AttributeError: | except AttributeError: | ||||
pass # The file is doesn't contain a task, so just move on | pass # The file is doesn't contain a task, so just move on | ||||
print "Found {0} tasks: {1}.".format(len(_tasks), ', '.join(_tasks.keys())) | |||||
logger.info("Found {0} tasks: {1}".format(len(_tasks), ', '.join(_tasks.keys()))) | |||||
def schedule(now=time.gmtime()): | def schedule(now=time.gmtime()): | ||||
"""Start all tasks that are supposed to be run at a given time.""" | """Start all tasks that are supposed to be run at a given time.""" | ||||
@@ -87,13 +88,13 @@ def schedule(now=time.gmtime()): | |||||
def start(task_name, **kwargs): | def start(task_name, **kwargs): | ||||
"""Start a given task in a new thread. Pass args to the task's run() | """Start a given task in a new thread. Pass args to the task's run() | ||||
function.""" | function.""" | ||||
print "Starting task '{0}' in a new thread...".format(task_name) | |||||
logger.info("Starting task '{0}' in a new thread".format(task_name)) | |||||
try: | try: | ||||
task = _tasks[task_name] | task = _tasks[task_name] | ||||
except KeyError: | except KeyError: | ||||
error = "Couldn't find task '{0}': bot/tasks/{0}.py does not exist." | |||||
print error.format(task_name) | |||||
error = "Couldn't find task '{0}': bot/tasks/{0}.py does not exist" | |||||
logger.error(error.format(task_name)) | |||||
return | return | ||||
task_thread = threading.Thread(target=lambda: _wrapper(task, **kwargs)) | task_thread = threading.Thread(target=lambda: _wrapper(task, **kwargs)) | ||||
@@ -10,11 +10,14 @@ being started (located in tasks/) or messages being sent to channels on the IRC | |||||
frontend. | frontend. | ||||
""" | """ | ||||
import logging | |||||
import config | import config | ||||
from classes import Connection, RC, BrokenSocketException | from classes import Connection, RC, BrokenSocketException | ||||
import rules | import rules | ||||
frontend_conn = None | frontend_conn = None | ||||
logger = logging.getLogger("watcher") | |||||
def get_connection(): | def get_connection(): | ||||
"""Return a new Connection() instance with connection information. | """Return a new Connection() instance with connection information. | ||||
@@ -23,7 +26,7 @@ def get_connection(): | |||||
""" | """ | ||||
cf = config.irc["watcher"] | cf = config.irc["watcher"] | ||||
connection = Connection(cf["host"], cf["port"], cf["nick"], cf["ident"], | connection = Connection(cf["host"], cf["port"], cf["nick"], cf["ident"], | ||||
cf["realname"]) | |||||
cf["realname"], logger) | |||||
return connection | return connection | ||||
def main(connection, f_conn=None): | def main(connection, f_conn=None): | ||||
@@ -10,6 +10,10 @@ written by Mr.Z-man, other than a similar purpose. We share no code. | |||||
Import the toolset with `import wiki`. | Import the toolset with `import wiki`. | ||||
""" | """ | ||||
import logging | |||||
logger = logging.getLogger("wiki") | |||||
logger.addHandler(logging.NullHandler()) | |||||
from wiki.constants import * | from wiki.constants import * | ||||
from wiki.exceptions import * | from wiki.exceptions import * | ||||
from wiki.functions import * | from wiki.functions import * | ||||
@@ -16,6 +16,7 @@ try: | |||||
except ImportError: | except ImportError: | ||||
oursql = None | oursql = None | ||||
from wiki import logger | |||||
from wiki.category import Category | from wiki.category import Category | ||||
from wiki.constants import * | from wiki.constants import * | ||||
from wiki.exceptions import * | from wiki.exceptions import * | ||||
@@ -169,7 +170,7 @@ class Site(object): | |||||
data = urlencode(params) | data = urlencode(params) | ||||
print url, data # debug code | |||||
logger.debug("{0} -> {1}".format(url, data)) | |||||
try: | try: | ||||
response = self._opener.open(url, data) | response = self._opener.open(url, data) | ||||
@@ -207,7 +208,7 @@ class Site(object): | |||||
raise SiteAPIError(e.format(self._max_retries)) | raise SiteAPIError(e.format(self._max_retries)) | ||||
tries += 1 | tries += 1 | ||||
msg = 'Server says: "{0}". Retrying in {1} seconds ({2}/{3}).' | msg = 'Server says: "{0}". Retrying in {1} seconds ({2}/{3}).' | ||||
print msg.format(info, wait, tries, self._max_retries) | |||||
logger.info(msg.format(info, wait, tries, self._max_retries)) | |||||
sleep(wait) | sleep(wait) | ||||
return self._api_query(params, tries=tries, wait=wait*3) | return self._api_query(params, tries=tries, wait=wait*3) | ||||
else: | else: | ||||