Commits

Josh VanderLinden committed 16ee643

Major refactor to handle reconfiguration without reloading

Comments (0)

Files changed (1)

 
 Copyright (C) 2013 Josh VanderLinden <arch@cloudlery.com>
 
-Date: 2013-02-07
-Version: 0.0.2
+CHANGELOG {{{
 
-* 0.0.2 - 2013-02-07: bulk inserts; disconnect from rethinkdb
-* 0.0.1 - 2013-02-07: initial plugin
+* 0.0.3 - 2013-02-07
+
+    - general code reorganization
+    - configuration changes withour requiring a reload
+
+* 0.0.2 - 2013-02-07
+
+    - bulk inserts
+    - disconnect from rethinkdb on unload
+
+* 0.0.1 - 2013-02-07
+
+    - initial plugin
+
+}}}
+
+TODOS {{{
+
+* handle error cases when RethinkDB is inaccessible
+* handle errors inserting records (retries?)
+* show most recent logs for a given buffer when opened
+
+}}}
 
 """
 
 from collections import deque
+from os.path import basename, splitext
 
 from rethinkdb import r
 
 
 __name__ = 'RethinkLog'
 __author__ = 'codekoala'
-__version__ = '0.0.2'
-__license__ = 'MIT'
+__version__ = '0.0.3'
+__license__ = 'GPL3'
 __description__ = 'Log messages to a RethinkDB cluster'
+__modname__ = basename(splitext(__file__)[0])
 
-
-weechat.register(__name__, __author__, __version__, __license__, __description__, 'close', '')
-
-
-defaults = dict(
-    host='localhost',
-    port='28015',
-    db='weechat',
-    table='messages',
-)
-
-# initialize config
-for o, v in defaults.items():
-    if not cfg(o):
-        weechat.config_set_plugin(o, v)
-
-
-# TODO: handle error cases when RethinkDB is inaccessible
-# TODO: handle errors inserting records (retries?)
-# TODO: configuration changes withour requiring a reload
-# TODO: show most recent logs for a given buffer when opened
+OK = weechat.WEECHAT_RC_OK
+ERR = weechat.WEECHAT_RC_ERROR
 
 
 class RethinkLogger(object):
     """Log messages to a RethinkDB cluster"""
 
+    DEFAULTS = dict(
+        host='localhost',
+        port='28015',
+        db='weechat',
+        table='messages',
+        sync_freq='10000',
+        create_db='on',
+        create_table='on',
+    )
+
     def __init__(self):
+        self.sync_hook = None
         self.cnx = None
         self.queue = deque()
 
+        # initialize config
+        for o, v in RethinkLogger.DEFAULTS.items():
+            if not cfg(o):
+                weechat.config_set_plugin(o, v)
+
+        weechat.hook_print('', '', '', 1, 'log', '')
+        weechat.hook_config('plugins.var.python.%s.*' % __modname__,
+                            'reconfigure', '')
+
+        self.start_timer()
+
     @property
     def r(self):
+        """
+        Return a cursor to the database. Connect to RethinkDB if necessary.
+        """
+
         if self.cnx is None:
-            self.cnx = r.connect(cfg('host'), int(cfg('port')))
+            host = cfg('host')
+            port = int(cfg('port'))
+            self.p('Connecting to RethinkDB: %s:%s' % (host, port))
+            self.cnx = r.connect(host, port)
 
-        return r.db(cfg('db')).table(cfg('table'))
+        # create the database?
+        db_name = cfg('db')
+        if db_name not in r.db_list().run() and cfg('create_db') == 'on':
+            self.p('Creating RethinkLog database: %s' % (db_name,))
+            r.db_create(db_name).run()
+
+        db = r.db(db_name)
+
+        # create the table?
+        table_name = cfg('table')
+        if table_name not in db.table_list().run() and cfg('create_table') == 'on':
+            self.p('Creating RethinkLog table: %s' % (table_name,))
+            db.table_create(table_name).run()
+
+        return db.table(table_name)
+
+    def p(self, msg, buf=''):
+        """Print a message in weechat"""
+
+        weechat.prnt(buf, msg)
 
     def log(self, data, buf, time, tags, display, hilight, prefix, msg):
-        buffer_name = get(buf, 'short_name') or get(buf, 'name')
+        """Queue a message to be written to the database."""
 
-        record = dict(
-            data=data.strip(),
-            buffer=buffer_name.strip(),
-            timestamp=int(time),
-            tags=[t.strip() for t in tags.split(',') if t],
-            display=bool(int(display)),
-            hilight=bool(int(hilight)),
-            prefix=prefix.strip(),
-            message=msg.strip().decode('utf-8')
-        )
+        return_code = OK
 
-        self.queue.append(record)
+        try:
+            buffer_name = get(buf, 'short_name') or get(buf, 'name')
+
+            record = dict(
+                data=data.strip(),
+                buffer=buffer_name.strip(),
+                timestamp=int(time),
+                tags=[t.strip() for t in tags.split(',') if t],
+                display=bool(int(display)),
+                hilight=bool(int(hilight)),
+                prefix=prefix.strip(),
+                message=msg.strip().decode('utf-8')
+            )
+
+            self.queue.append(record)
+        except Exception as e:
+            self.p('Failed to queue message for insertion: %s' % str(e))
+            return_code = ERR
 
-        return weechat.WEECHAT_RC_OK
+        return return_code
 
     def flush(self, data, remaining_calls):
-        """Send multiple records to RethinkDB at one time"""
+        """Send multiple records to RethinkDB at one time."""
 
-        return_code = weechat.WEECHAT_RC_OK
+        return_code = OK
 
         try:
             self.r.insert(self.queue).run()
         except Exception as e:
-            weechat.prnt('', 'Failed to log messages to rethinkdb: %s' % str(e))
-            return_code = weechat.WEECHAT_RC_ERROR
-
-        # TODO: handle errors instead of postentially dropping a ton of records
-        self.queue.clear()
+            self.p('Failed to log messages to rethinkdb: %s' % str(e))
+            return_code = ERR
+        else:
+            self.queue.clear()
 
         return return_code
 
+    def reconfigure(self, data, option, value):
+        """Handle a configuration change without reloading"""
+
+        # disconnect from the database when the host or port change
+        if option in ('host', 'port'):
+            self.p('RethinkLog database information changed')
+            self.close()
+
+        if option == 'sync_freq':
+            self.p('RethinkLog sync interval updated')
+            weechat.unhook(self.sync_hook)
+            self.start_timer()
+
+        return OK
+
+    def start_timer(self):
+        """
+        Start a timer to automatically synchronize messages with the
+        database.
+        """
+
+        interval = int(cfg('sync_freq'))
+        self.p('Starting RethinkLog timer to sync every %sms' % (interval,))
+        self.sync_hook = weechat.hook_timer(interval, 0, 0, 'flush', '')
+
     def close(self):
         """Disconnect from the RethinkDB"""
 
         if self.cnx:
             self.cnx.close()
 
+        return OK
 
+
+weechat.register(__name__, __author__, __version__, __license__,
+                 __description__, 'close', '')
+
+
+# instantiate and expose useful methods
 plugin = RethinkLogger()
+
 log = plugin.log
 flush = plugin.flush
+reconfigure = plugin.reconfigure
 close = plugin.close
 
-weechat.hook_print('', '', '', 1, 'log', '')
-weechat.hook_timer(5000, 0, 0, 'flush', '')
+# vim:et ts=4 sw=4 fdm=marker:
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.