X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=lib%2Frbot%2Fcore%2Firclog.rb;h=3b3134c205ebe67ce7e0022a839fc4489cd4332b;hb=28502d92c420aefa3832e57561044efa06b9ab8b;hp=c4e9f590c2a093f0ceff62c6f41bf4e464ac22df;hpb=b6d39f5ca585a89c8d2567e59bd8ef67c77c5674;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git diff --git a/lib/rbot/core/irclog.rb b/lib/rbot/core/irclog.rb index c4e9f590..3b3134c2 100644 --- a/lib/rbot/core/irclog.rb +++ b/lib/rbot/core/irclog.rb @@ -4,59 +4,80 @@ # :title: rbot IRC logging facilities # # Author:: Giuseppe "Oblomov" Bilotta -# Copyright:: (C) 2008 Giuseppe Bilotta -# License:: GPL v2 class IrcLogModule < CoreBotModule Config.register Config::IntegerValue.new('irclog.max_open_files', :default => 20, :validate => Proc.new { |v| v > 0 }, :desc => "Maximum number of irclog files to keep open at any one time.") - + Config.register Config::ArrayValue.new('irclog.no_log', + :default => [], :on_change => Proc.new { |bot, v| + bot.plugins.delegate 'event_irclog_list_changed', v, bot.config['irclog.do_log'] + }, + :desc => "List of channels and nicks for which logging is disabled. IRC patterns can be used too.") + Config.register Config::ArrayValue.new('irclog.do_log', + :default => [], :on_change => Proc.new { |bot, v| + bot.plugins.delegate 'event_irclog_list_changed', bot.config['irclog.no_log'], v + }, + :desc => "List of channels and nicks for which logging is enabled. IRC patterns can be used too. This can be used to override wide patters in irclog.no_log") + Config.register Config::StringValue.new('irclog.filename_format', + :default => '%%{where}', :requires_rescan => true, + :desc => "filename pattern for the IRC log. You can put typical strftime keys such as %Y for year and %m for month, plus the special %%{where} key for location (channel name or user nick)") + Config.register Config::StringValue.new('irclog.timestamp_format', + :default => '[%Y/%m/%d %H:%M:%S]', :requires_rescan => true, + :desc => "timestamp pattern for the IRC log, using typical strftime keys") + + attr :nolog_rx, :dolog_rx def initialize super + @queue = Queue.new + @thread = Thread.new { loggers_thread } @logs = Hash.new - Dir.mkdir("#{@bot.botclass}/logs") unless File.exist?("#{@bot.botclass}/logs") + 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 + + 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 + + def timestamp(time) + return time.strftime(@bot.config['irclog.timestamp_format']) + end + + 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 def logfile_close(where_str, reason = 'unknown reason') f = @logs.delete(where_str) or return - stamp = Time.now.strftime '%Y/%m/%d %H:%M:%S' - f[1].puts "[#{stamp}] @ Log closed by #{@bot.myself.nick} (#{reason})" + stamp = timestamp(Time.now) + f[1].puts "#{stamp} @ Log closed by #{@bot.myself.nick} (#{reason})" f[1].close end # log IRC-related message +message+ to a file determined by +where+. # +where+ can be a channel name, or a nick for private message logging def irclog(message, where="server") - message = message.chomp - now = Time.now - stamp = now.strftime("%Y/%m/%d %H:%M:%S") - if where.class <= Server - where_str = "server" - else - where_str = where.downcase.gsub(/[:!?$*()\/\\<>|"']/, "_") - end - 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 - f = File.new("#{@bot.botclass}/logs/#{where_str}", "a") - f.sync = true - f.puts "[#{stamp}] @ Log started by #{@bot.myself.nick}" - @logs[where_str] = [now, f] - end - @logs[where_str][1].puts "[#{stamp}] #{message}" - @logs[where_str][0] = now - #debug "[#{stamp}] <#{where}> #{message}" + @queue.push [message, where] end def cleanup - @logs.keys.each { |w| logfile_close(w, 'rescan or shutdown') } + @queue << nil + @thread.join + @thread = nil end def sent(m) @@ -64,7 +85,25 @@ class IrcLogModule < CoreBotModule when NoticeMessage irclog "-#{m.source}- #{m.message}", m.target when PrivMessage - irclog "<#{m.source}> #{m.message}", m.target + 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 @@ -114,7 +153,7 @@ class IrcLogModule < CoreBotModule irclog "@ #{m.source} asked #{who} about #{[m.ctcp, m.message].join(' ')}", logtarget end else - if m.public? + if m.public? irclog "<#{m.source}> #{m.logmessage}", m.target else irclog "<#{m.source}(#{m.sourceaddress})> #{m.logmessage}", m.source @@ -137,13 +176,13 @@ class IrcLogModule < CoreBotModule end def log_nick(m) - m.is_on.each { |ch| + (m.is_on & @bot.myself.channels).each { |ch| irclog "@ #{m.oldnick} is now known as #{m.newnick}", ch } end def log_quit(m) - m.was_on.each { |ch| + (m.was_on & @bot.myself.channels).each { |ch| irclog "@ Quit: #{m.source}: #{m.logmessage}", ch } end @@ -205,6 +244,85 @@ class IrcLogModule < CoreBotModule def unknown_message(m) irclog m.logmessage, ".unknown" end + + def logfilepath(where_str, now) + @bot.path('logs', now.strftime(@fn_format) % { :where => where_str }) + end + + protected + 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 end ilm = IrcLogModule.new