.. _fbf.plugs.extra.chatlog: chatlog ~~~~~~~ .. automodule:: fbf.plugs.extra.chatlog :show-inheritance: :members: :undoc-members: CODE ---- :: # fbf/plugs/socket/chatlog.py # # """ log channels to [hour:min] txt format, only logging to files is supported right now. """ .. _fbf.plugs.extra.chatlog_fbf_imports: fbf imports -------------- :: from fbf.lib.commands import cmnds from fbf.lib.callbacks import callbacks, remote_callbacks, last_callbacks, first_callbacks from fbf.lib.persistconfig import PersistConfig from fbf.utils.locking import lockdec from fbf.utils.timeutils import hourmin from fbf.lib.examples import examples from fbf.utils.exception import handle_exception from fbf.utils.lazydict import LazyDict from fbf.lib.datadir import getdatadir from fbf.utils.name import stripname from fbf.utils.url import striphtml from fbf.utils.format import formatevent, format_opt from fbf.utils.log import init from fbf.utils.statdict import StatDict from fbf.utils.timeutils import striptime, strtotime2 .. _fbf.plugs.extra.chatlog_basic_imports: basic imports ---------------- :: import time import os import logging import _thread from os import path from datetime import datetime .. _fbf.plugs.extra.chatlog_locks_: locks -------- :: outlock = _thread.allocate_lock() outlocked = lockdec(outlock) .. _fbf.plugs.extra.chatlog_defines_: defines ---------- :: cfg = PersistConfig() cfg.define('channels', []) cfg.define('format', 'log') cfg.define('basepath', getdatadir()) cfg.define('nologprefix', '[nolog]') cfg.define('nologmsg', '-= THIS MESSAGE NOT LOGGED =-') cfg.define('backend', 'log') logfiles = {} backends = {} stopped = False db = None eventstolog = ["OUTPUT", "PRIVMSG", "CONSOLE", "PART", "JOIN", "QUIT", "PRESENCE", "MESSAGE", "NOTICE", "MODE", "TOPIC", "KICK", "CONVORE", "TORNADO"] .. _fbf.plugs.extra.chatlog_logging_part: logging part --------------- :: # BHJTW 21-02-2011 revamped to work with standard python logger loggers = {} def initlog(d): """ create the necesary directories to enable logging. """ try: LOGDIR = d + os.sep + "chatlogs" except ImportError: LOGDIR = d + os.sep + "chatlogs" try: ddir = os.sep.join(LOGDIR.split(os.sep)[:-1]) if not os.path.isdir(ddir): os.mkdir(ddir) except: pass try: if not os.path.isdir(LOGDIR): os.mkdir(LOGDIR) except: pass return LOGDIR format = "%(message)s" def timestr(dt): """ convert datatime object to a time string. """ return dt.strftime(format_opt('timestamp_format')) .. _fbf.plugs.extra.chatlog_enablelogging_function: enablelogging function ------------------------- :: def enablelogging(botname, channel): """ set loglevel to level_name. """ global loggers LOGDIR = initlog(getdatadir()) logging.warn("enabling on (%s,%s)" % (botname, channel)) chan = channel channel = stripname(channel) logname = "%s_%s" % (botname, channel) #if logname in loggers: logging.warn("there is already a logger for %s" % logname) ; return try: filehandler = logging.handlers.TimedRotatingFileHandler(LOGDIR + os.sep + logname + ".log", 'midnight') formatter = logging.Formatter(format) filehandler.setFormatter(formatter) except IOError: filehandler = None chatlogger = logging.getLoggerClass()(logname) chatlogger.setLevel(logging.INFO) if chatlogger.handlers: for handler in chatlogger.handlers: chatlogger.removeHandler(handler) if filehandler: chatlogger.addHandler(filehandler) ; logging.warn("%s - logging enabled on %s" % (botname, chan)) else: logging.error("no file handler found - not enabling logging.") global lastlogger lastlogger = chatlogger loggers[logname] = lastlogger .. _fbf.plugs.extra.chatlog_do_tha_actual_logging: do tha actual logging ------------------------ :: @outlocked def write(m): """ m is a dict with the following properties: datetime type : (comment, nick, topic etc..) target : (#channel, bot etc..) txt : actual message network """ backend_name = cfg.get('backend', 'log') backend = backends.get(backend_name, log_write) if m.txt.startswith(cfg.get('nologprefix')): m.txt = cfg.get('nologmsg') backend(m) def log_write(m): if stopped: return logname = "%s_%s" % (m.botname, stripname(m.target)) timestamp = timestr(m.datetime) m.type = m.type.upper() line = '%(timestamp)s%(separator)s %(txt)s\n'%({ 'timestamp': timestamp, 'separator': format_opt('separator'), 'nick': m.nick, 'txt': m.txt, 'nick': m.nick, 'type': m.type }) global loggers try: loggers[logname].info(line.strip()) except KeyError: logging.debug("no logger available for channel %s" % logname) except Exception as ex: handle_exception() backends['log'] = log_write .. _fbf.plugs.extra.chatlog_log_function: log function --------------- :: def log(bot, event): """ format an event and send it to the logging backend. """ m = formatevent(bot, event, cfg.get("channels") or []) if m["txt"]: write(m) .. _fbf.plugs.extra.chatlog_chatlog_precondition: chatlog precondition ----------------------- :: def prechatlogcb(bot, ievent): """ Check if event should be logged. QUIT and NICK are not channel specific, so we will check each channel in log(). """ if not ievent.channel: logging.debug("channel not set .. not logging.") ; return False if not cfg.channels: logging.debug("no channels set") ; return False if [bot.cfg.name, ievent.channel] in cfg.get('channels'): logging.debug("%s %s in channels .. logging" % (bot.cfg.name, ievent.channel)) ; return True if not ievent.cbtype in eventstolog: logging.debug("%s ut not in eventstolog list." % ievent.cbtype) ; return False if ievent.msg: logging.debug("is messsage .. not logging") ; return False if ievent.cmnd in ('QUIT', 'NICK'): return True if ievent.cmnd == 'NOTICE': if [bot.cfg.name, ievent.arguments[0]] in cfg.get('channels'): return True logging.debug("not match for logging.") return False .. _fbf.plugs.extra.chatlog_chatlog_callbacks: chatlog callbacks -------------------- :: def chatlogcb(bot, ievent): """ logging callback. """ log(bot, ievent) .. _fbf.plugs.extra.chatlog_plugin-start_: plugin-start --------------- :: def init(): """ called upon plugin registration. """ global stopped stopped = False global loggers for (botname, channel) in cfg.get("channels"): enablelogging(botname, channel) callbacks.add("PRIVMSG", chatlogcb, prechatlogcb) callbacks.add("JOIN", chatlogcb, prechatlogcb) callbacks.add("PART", chatlogcb, prechatlogcb) callbacks.add("NOTICE", chatlogcb, prechatlogcb) callbacks.add("QUIT", chatlogcb, prechatlogcb) callbacks.add("NICK", chatlogcb, prechatlogcb) callbacks.add("PRESENCE", chatlogcb, prechatlogcb) callbacks.add("MESSAGE", chatlogcb, prechatlogcb) callbacks.add("CONSOLE", chatlogcb, prechatlogcb) callbacks.add("CONVORE", chatlogcb, prechatlogcb) first_callbacks.add("OUTPUT", chatlogcb, prechatlogcb) return 1 .. _fbf.plugs.extra.chatlog_plugin-stop_: plugin-stop -------------- :: def shutdown(): """ shutdown the plugin. """ global stopped stopped = True for file in list(logfiles.values()): file.close() return 1 .. _fbf.plugs.extra.chatlog_chatlog-on_command: chatlog-on command --------------------- :: def handle_chatlogon(bot, ievent): """ no arguments - enable chatlog. """ chan = ievent.channel enablelogging(bot.cfg.name, chan) if [bot.cfg.name, chan] not in cfg.get('channels'): cfg['channels'].append([bot.cfg.name, chan]) cfg.save() ievent.reply('chatlog enabled on (%s,%s)' % (bot.cfg.name, chan)) cmnds.add('chatlog-on', handle_chatlogon, 'OPER') examples.add('chatlog-on', 'enable chatlog on the channel the commands is given in', 'chatlog-on') .. _fbf.plugs.extra.chatlog_chatlog-off_command: chatlog-off command ---------------------- :: def handle_chatlogoff(bot, ievent): """ no arguments - disable chatlog. """ try: cfg['channels'].remove([bot.cfg.name, ievent.channel]) ; cfg.save() except ValueError: ievent.reply('chatlog is not enabled in (%s,%s)' % (bot.cfg.name, ievent.channel)) ; return try: del loggers["%s-%s" % (bot.cfg.name, stripname(ievent.channel))] except KeyError: pass except Exception as ex: handle_exception() ievent.reply('chatlog disabled on (%s,%s)' % (bot.cfg.name, ievent.channel)) cmnds.add('chatlog-off', handle_chatlogoff, 'OPER') examples.add('chatlog-off', 'disable chatlog on the channel the commands is given in', 'chatlog-off') .. _fbf.plugs.extra.chatlog_chatlog-search_command: chatlog-search command ------------------------- :: def handle_chatlogsearch(bot, event): """ arguments: - search in the logs. """ if not event.rest: event.missing("") ; return result = [] chatlogdir = getdatadir() + os.sep + "chatlogs" if event.options and event.options.channel: chan = event.options.channel else: chan = event.channel logs = os.listdir(chatlogdir) logs.sort() for f in logs: filename = stripname(f) if not stripname(chan) in filename: continue for line in open(chatlogdir + os.sep + filename, 'r'): if event.rest in line: result.append(line) if result: event.reply("search results for %s: " % event.rest, result, dot= " || ") else: event.reply("no result found for %s" % chan) cmnds.add("chatlog-search", handle_chatlogsearch, ["OPER", "USER", "GUEST"], threaded=True) examples.add("chatlog-search", "search the chatlogs of a channel.", "chatlog-search fbfbot") .. _fbf.plugs.extra.chatlog_chatlog-stats_command: chatlog-stats command ------------------------ :: def handle_chatlogstats(bot, event): """ no arguments - create log stats of the channel, possible options: --chan """ what = event.rest.strip() chatlogdir = getdatadir() + os.sep + "chatlogs" if event.options and event.options.channel: chan = event.options.channel else: chan = event.channel logs = os.listdir(chatlogdir) if not logs: event.reply("no logs available for %s" % chan) ; return now = time.time() if what: timetarget = strtotime2(what) ; what = striptime(what) else: timetarget = 0 ; what = None event.reply("creating stats for channel %s (%s)" % (chan, time.ctime(timetarget))) userstats = StatDict() wordstats = StatDict() stop = False for f in logs[::-1]: filename = stripname(f) channel = stripname(chan) if not channel in filename: continue for line in open(chatlogdir + os.sep + filename, 'r'): splitted = line.strip().split() if len(splitted) < 2: continue who = "unknown" for i in splitted: if i.startswith("<"): who = i[1:-1] if what and who != what: continue timestr = "%s %s" % (splitted[0], splitted[1]) logtime = strtotime2(timestr) if logtime: if logtime > timetarget: userstats.upitem(who) else: continue else: userstats.upitem(who) for word in splitted[4:]: wordstats.upitem(word) if what: result = wordstats.top() else: result = userstats.top() if result: res = ["%s: %s" % item for item in result] event.reply("stat results for %s: " % (what or chan), res) else: event.reply("no result found for %s" % (what or chan)) cmnds.add("chatlog-stats", handle_chatlogstats, ["OPER", "USER", "GUEST"], threaded=True) examples.add("chatlog-stats", "stats of a channel.", "chatlog-stats")