class IrcLogModule

Author

Giuseppe “Oblomov” Bilotta <giuseppe.bilotta@gmail.com>

Attributes

nolog_rx[RW]

Public Class Methods

new() click to toggle source
Calls superclass method
# File lib/rbot/core/irclog.rb, line 31
def initialize
  super
  @queue = Queue.new
  @thread = Thread.new { loggers_thread }
  @logs = Hash.new
  logdir = @bot.path 'logs'
  Dir.mkdir(logdir) unless File.exist?(logdir)
  # TODO what shall we do if the logdir couldn't be created? (e.g. it existed as a file)
  event_irclog_list_changed(@bot.config['irclog.no_log'], @bot.config['irclog.do_log'])
  @fn_format = @bot.config['irclog.filename_format']
end

Public Instance Methods

can_log_on(where) click to toggle source
# File lib/rbot/core/irclog.rb, line 43
def can_log_on(where)
  return true if @dolog_rx and where.match @dolog_rx
  return false if @nolog_rx and where.match @nolog_rx
  return true
end
cleanup() click to toggle source
# File lib/rbot/core/irclog.rb, line 77
def cleanup
  @queue << nil
  @thread.join
  @thread = nil
end
event_irclog_list_changed(nolist, dolist) click to toggle source
# File lib/rbot/core/irclog.rb, line 53
def event_irclog_list_changed(nolist, dolist)
  @nolog_rx = nolist.empty? ? nil : Regexp.union(*(nolist.map { |r| r.to_irc_regexp }))
  debug "no log: #{@nolog_rx}"
  @dolog_rx = dolist.empty? ? nil : Regexp.union(*(dolist.map { |r| r.to_irc_regexp }))
  debug "do log: #{@dolog_rx}"
  @logs.inject([]) { |ar, kv|
    ar << kv.first unless can_log_on(kv.first)
    ar
  }.each { |w| logfile_close(w, 'logging disabled here') }
end
irclog(message, where="server") click to toggle source

log IRC-related message message to a file determined by where. where can be a channel name, or a nick for private message logging

# File lib/rbot/core/irclog.rb, line 73
def irclog(message, where="server")
  @queue.push [message, where]
end
listen(m) click to toggle source
# File lib/rbot/core/irclog.rb, line 118
def listen(m)
  case m
  when PrivMessage
    method = 'log_message'
  else
    method = 'log_' + m.class.name.downcase.match(/^irc::(\w+)message$/).captures.first
  end
  if self.respond_to?(method)
    self.__send__(method, m)
  else
    warning "unhandled logging for #{m.pretty_inspect} (no such method #{method})"
    unknown_message(m)
  end
end
log_join(m) click to toggle source
# File lib/rbot/core/irclog.rb, line 194
def log_join(m)
  if m.address?
    debug "joined channel #{m.channel}"
    irclog "@ Joined channel #{m.channel}", m.channel
  else
    irclog "@ #{m.source} joined channel #{m.channel}", m.channel
  end
end
log_kick(m) click to toggle source
# File lib/rbot/core/irclog.rb, line 212
def log_kick(m)
  if(m.address?)
    debug "kicked from channel #{m.channel}"
    irclog "@ You have been kicked from #{m.channel} by #{m.source} (#{m.logmessage})", m.channel
  else
    irclog "@ #{m.target} has been kicked from #{m.channel} by #{m.source} (#{m.logmessage})", m.channel
  end
end
log_message(m) click to toggle source
# File lib/rbot/core/irclog.rb, line 133
def log_message(m)
  if m.ctcp
    who = m.private? ? "me" : m.target
    logtarget = m.private? ? m.source : m.target
    case m.ctcp.intern
    when :ACTION
      if m.public?
        irclog "* #{m.source} #{m.logmessage}", m.target
      else
        irclog "* #{m.source}(#{m.sourceaddress}) #{m.logmessage}", m.source
      end
    when :VERSION
      irclog "@ #{m.source} asked #{who} about version info", logtarget
    when :SOURCE
      irclog "@ #{m.source} asked #{who} about source info", logtarget
    when :PING
      irclog "@ #{m.source} pinged #{who}", logtarget
    when :TIME
      irclog "@ #{m.source} asked #{who} what time it is", logtarget
    else
      irclog "@ #{m.source} asked #{who} about #{[m.ctcp, m.message].join(' ')}", logtarget
    end
  else
    if m.public?
      irclog "<#{m.source}> #{m.logmessage}", m.target
    else
      irclog "<#{m.source}(#{m.sourceaddress})> #{m.logmessage}", m.source
    end
  end
end
log_nick(m) click to toggle source
# File lib/rbot/core/irclog.rb, line 178
def log_nick(m)
  (m.is_on & @bot.myself.channels).each { |ch|
    irclog "@ #{m.oldnick} is now known as #{m.newnick}", ch
  }
end
log_notice(m) click to toggle source
# File lib/rbot/core/irclog.rb, line 164
def log_notice(m)
  if m.private?
    irclog "-#{m.source}(#{m.sourceaddress})- #{m.logmessage}", m.source
  else
    irclog "-#{m.source}- #{m.logmessage}", m.target
  end
end
log_part(m) click to toggle source
# File lib/rbot/core/irclog.rb, line 203
def log_part(m)
  if(m.address?)
    debug "left channel #{m.channel}"
    irclog "@ Left channel #{m.channel} (#{m.logmessage})", m.channel
  else
    irclog "@ #{m.source} left channel #{m.channel} (#{m.logmessage})", m.channel
  end
end
log_quit(m) click to toggle source
# File lib/rbot/core/irclog.rb, line 184
def log_quit(m)
  (m.was_on & @bot.myself.channels).each { |ch|
    irclog "@ Quit: #{m.source}: #{m.logmessage}", ch
  }
end
log_topic(m) click to toggle source

def log_invite(m)

# TODO

end

# File lib/rbot/core/irclog.rb, line 225
def log_topic(m)
  case m.info_or_set
  when :set
    if m.source == @bot.myself
      irclog "@ I set topic \"#{m.topic}\"", m.channel
    else
      irclog "@ #{m.source} set topic \"#{m.topic}\"", m.channel
    end
  when :info
    topic = m.channel.topic
    irclog "@ Topic is \"#{m.topic}\"", m.channel
    irclog "@ Topic set by #{topic.set_by} on #{topic.set_on}", m.channel
  end
end
logfile_close(where_str, reason = 'unknown reason') click to toggle source
# File lib/rbot/core/irclog.rb, line 64
def logfile_close(where_str, reason = 'unknown reason')
  f = @logs.delete(where_str) or return
  stamp = timestamp(Time.now)
  f[1].puts "#{stamp} @ Log closed by #{@bot.myself.nick} (#{reason})"
  f[1].close
end
logfilepath(where_str, now) click to toggle source
# File lib/rbot/core/irclog.rb, line 248
def logfilepath(where_str, now)
  @bot.path('logs', now.strftime(@fn_format) % { :where => where_str })
end
modechange(m) click to toggle source
# File lib/rbot/core/irclog.rb, line 190
def modechange(m)
  irclog "@ Mode #{m.logmessage} by #{m.source}", m.target
end
motd(m) click to toggle source
# File lib/rbot/core/irclog.rb, line 172
def motd(m)
  m.message.each_line { |line|
    irclog "MOTD: #{line}", "server"
  }
end
sent(m) click to toggle source
# File lib/rbot/core/irclog.rb, line 83
def sent(m)
  case m
  when NoticeMessage
    irclog "-#{m.source}- #{m.message}", m.target
  when PrivMessage
    logtarget = who = m.target
    if m.ctcp
      case m.ctcp.intern
      when :ACTION
        irclog "* #{m.source} #{m.logmessage}", logtarget
      when :VERSION
        irclog "@ #{m.source} asked #{who} about version info", logtarget
      when :SOURCE
        irclog "@ #{m.source} asked #{who} about source info", logtarget
      when :PING
        irclog "@ #{m.source} pinged #{who}", logtarget
      when :TIME
        irclog "@ #{m.source} asked #{who} what time it is", logtarget
      else
        irclog "@ #{m.source} asked #{who} about #{[m.ctcp, m.message].join(' ')}", logtarget
      end
    else
      irclog "<#{m.source}> #{m.logmessage}", logtarget
    end
  when QuitMessage
    m.was_on.each { |ch|
      irclog "@ quit (#{m.message})", ch
    }
  end
end
timestamp(time) click to toggle source
# File lib/rbot/core/irclog.rb, line 49
def timestamp(time)
  return time.strftime(@bot.config['irclog.timestamp_format'])
end
unknown_message(m) click to toggle source

def names(m)

# TODO

end

# File lib/rbot/core/irclog.rb, line 244
def unknown_message(m)
  irclog m.logmessage, ".unknown"
end
welcome(m) click to toggle source
# File lib/rbot/core/irclog.rb, line 114
def welcome(m)
  irclog "joined server #{m.server} as #{m.target}", "server"
end

Protected Instance Methods

loggers_thread() click to toggle source
# File lib/rbot/core/irclog.rb, line 253
def loggers_thread
  ls = nil
  debug 'loggers_thread starting'
  while ls = @queue.pop
    message, where = ls
    message = message.chomp
    now = Time.now
    stamp = timestamp(now)
    if where.class <= Server
      where_str = "server"
    else
      where_str = where.downcase.gsub(/[:!?$*()\/\\<>|"']/, "_")
    end
    next unless can_log_on(where_str)

    # close the previous logfile if we're rotating
    if @logs.has_key? where_str
      fp = logfilepath(where_str, now)
      logfile_close(where_str, 'log rotation') if fp != @logs[where_str][1].path
    end

    # (re)open the logfile if necessary
    unless @logs.has_key? where_str
      if @logs.size > @bot.config['irclog.max_open_files']
        @logs.keys.sort do |a, b|
          @logs[a][0] <=> @logs[b][0]
        end.slice(0, @logs.size - @bot.config['irclog.max_open_files']).each do |w|
          logfile_close w, "idle since #{@logs[w][0]}"
        end
      end
      fp = logfilepath(where_str, now)
      begin
        dir = File.dirname(fp)
        # first of all, we check we're not trying to build a directory structure
        # where one of the components exists already as a file, so we
        # backtrack along dir until we come across the topmost existing name.
        # If it's a file, we rename to filename.old.filedate
        up = dir.dup
        until File.exist? up
          up.replace(File.dirname(up))
        end
        unless File.directory? up
          backup = up.dup
          backup << ".old." << File.atime(up).strftime('%Y%m%d%H%M%S')
          debug "#{up} is not a directory! renaming to #{backup}"
          File.rename(up, backup)
        end
        FileUtils.mkdir_p(dir)
        # conversely, it may happen that fp exists and is a directory, in
        # which case we rename the directory instead
        if File.directory? fp
          backup = fp.dup
          backup << ".old." << File.atime(fp).strftime('%Y%m%d%H%M%S')
          debug "#{fp} is not a file! renaming to #{backup}"
          File.rename(fp, backup)
        end
        # it should be fine to create the file now
        f = File.new(fp, "a")
        f.sync = true
        f.puts "#{stamp} @ Log started by #{@bot.myself.nick}"
      rescue Exception => e
        error e
        next
      end
      @logs[where_str] = [now, f]
    end
    @logs[where_str][1].puts "#{stamp} #{message}"
    @logs[where_str][0] = now
    #debug "#{stamp} <#{where}> #{message}"
  end
  @logs.keys.each { |w| logfile_close(w, 'rescan or shutdown') }
  debug 'loggers_thread terminating'
end