# --------------------------------------------------------
# ▼ Exception Logger [VX Ace] - v1.0.1
#    by Krosk
# --------------------------------------------------------
#
# This script will read the backtrace of any exception 
# occurring within the game, and write it back in a file
# for an easier debugging experience.
# 
# Any exception triggered by a script error within a script, 
# or an event will be caught. 
# 
# If a script error happens when reading a file
# (e.g. external script), this script can also be used
# to catch and log the exception. In this case,
# catch the exception with the following block.
# begin
#   file = File.open("path/to/file.rb", r)
#   ...
# rescue Exception => exception
#   EXCLOG::error_handler(exception, file)
# end
#
# The default log file is Log.txt, and can be edited with
# the EXCLOG_FILE parameter.
#
# --------------------------------------------------------

module EXCLOG
  
  EXCLOG_FILE = "Log.txt"
  
#--------------------------------------------------------------------------
# ▼ No need to edit below this line
#--------------------------------------------------------------------------
  Call = ""
#--------------------------------------------------------------------------
# Auto detect script name
#--------------------------------------------------------------------------
  EXCLOG_NAME = ""
  $RGSS_SCRIPTS.each do |script_entry|
    break unless script_entry[3].lines.each do |line|
      if line[/EXCLOG_NAME/]
        EXCLOG_NAME.replace(script_entry[1])
        break
      end
    end
  end
#--------------------------------------------------------------------------
# Backtrace logging
#--------------------------------------------------------------------------
  def self.error_handler(exception, file_arg = nil)
    source = $RGSS_SCRIPTS[exception.backtrace[0].split(':')[0][1..-2].to_i][1]
    source_line = exception.backtrace[0].split(":")[1]
    
    if file_arg != nil
      file = file_arg
      source = file.path
    end
    
    if source == EXCLOG_NAME # Probably an event interruption
      source = "event"
    end
    
    logfile = File.open(EXCLOG_FILE, "w")
    
    logfile.write("---------- Script error : #{source} ----------\n")
    logfile.write("----- Error class\n")
    logfile.write("#{exception.class}\n\n")
    
    logfile.write("----- Message\n")
    if exception.class == NoMethodError
      logfile.write("- ARGS : #{exception.args.inspect}\n")
    end
    logfile.write(exception.message + "\n\n")
    
    if file_arg != nil
      logfile.write("----- Error location in #{file.path}\n")
      logfile.write("Line #{file.lineno}\n")
      logfile.write(IO.readlines(file.path)[file.lineno-1] + "\n")
    elsif source == "event"
      logfile.write("----- Event Script error location\n")
      logfile.write(Call + "\n\n")
    else
      logfile.write("----- Common script error location at '#{source}'\n")
      logfile.write("Line #{source_line}\n\n")
    end
    
    logfile.write("----- Backtrace\n")    
    for trace in exception.backtrace
      location = trace.split(":")
      script_id = location[0][1..-2].to_i
      script_name = $RGSS_SCRIPTS[script_id][1]
      logfile.write("Script : #{script_name} | Line : #{location[1]}")
      if location[2] != nil
        logfile.write(" | Method : #{location[2]}")
      end
      logfile.write("\n>\t")
      logfile.write($RGSS_SCRIPTS[script_id][3].split("\n")[location[1].to_i-1])
      logfile.write("\n")
    end
    logfile.close
    
    raise
  end
  
  def self.register_call(arg, map_id, event_id, depth)
    string = ""
    if map_id == 0 # very likely to be in a battle
      string << "BATTLE: #{$game_troop.troop.id}\n" if $game_party.in_battle
      string << "COMMONT_EVENT (?) - " if depth > 0
      string << "SCRIPT:\n#{arg}"
    else # common map
      event = $game_map.events[event_id]
      string << "MAP:#{map_id} EVENT:#{event_id} X:#{event.x},Y:#{event.y}\n"
      string << "COMMONT_EVENT (?) - " if depth > 0
      string << "SCRIPT:\n#{arg}"
    end
    Call.replace(string)
  end
end
#--------------------------------------------------------------------------
# Game_Interpreter hook, logging last script call
#   If an in-game exception points here, the error 
#   comes from a script inside an event (map, common or battle)
#--------------------------------------------------------------------------
class Game_Interpreter
  alias exc_eval eval
  def eval(arg)
    EXCLOG::register_call(arg, map_id, event_id, @depth)
    exc_eval(arg)
  end
end
#--------------------------------------------------------------------------
# Main hook
#--------------------------------------------------------------------------
alias exc_rgss_main rgss_main
def rgss_main(*args, &block)
  exc_rgss_main(*args, &block)
rescue Exception => exception
  EXCLOG::error_handler(exception)
end
#==========================================================================
# End of file
#==========================================================================