diff --git a/code/__defines/_compile_options.dm b/code/__defines/_compile_options.dm index dfdcffdd929..9a6cb12560e 100644 --- a/code/__defines/_compile_options.dm +++ b/code/__defines/_compile_options.dm @@ -20,3 +20,5 @@ #define USING_MAP_DATUM /datum/map #define MAP_OVERRIDE 1 #endif + +#define USE_CUSTOM_ERROR_HANDLER diff --git a/code/__defines/admin.dm b/code/__defines/admin.dm index 84e0656c976..26b5624a9be 100644 --- a/code/__defines/admin.dm +++ b/code/__defines/admin.dm @@ -65,6 +65,7 @@ #define ADMIN_JMP(src) "(JMP)" #define COORD(src) "[src ? "([src.x],[src.y],[src.z])" : "nonexistent location"]" #define ADMIN_COORDJMP(src) "[src ? "[COORD(src)] [ADMIN_JMP(src)]" : "nonexistent location"]" +#define AREACOORD(src) "[src ? "[get_area_name(src, TRUE)] ([src.x], [src.y], [src.z])" : "nonexistent location"]" #define AHELP_ACTIVE 1 #define AHELP_CLOSED 2 diff --git a/code/_helpers/_logging.dm b/code/_helpers/_logging.dm new file mode 100644 index 00000000000..ef552947300 --- /dev/null +++ b/code/_helpers/_logging.dm @@ -0,0 +1,26 @@ +//wrapper macros for easier grepping +#define DIRECT_OUTPUT(A, B) A << B +#define SEND_IMAGE(target, image) DIRECT_OUTPUT(target, image) +#define SEND_SOUND(target, sound) DIRECT_OUTPUT(target, sound) +#define SEND_TEXT(target, text) DIRECT_OUTPUT(target, text) +#define WRITE_FILE(file, text) DIRECT_OUTPUT(file, text) +#define WRITE_LOG(log, text) DIRECT_OUTPUT(log, text) + +//not an error or a warning, but worth to mention on the world log, just in case. +#define NOTICE(MSG) notice(MSG) +/proc/notice(msg) + msg = "## NOTICE: [msg]" + log_world(msg) + +/proc/loc_name(atom/A) + if(!istype(A)) + return "(INVALID LOCATION)" + + var/turf/T = A + if (!istype(T)) + T = get_turf(A) + + if(istype(T)) + return "([AREACOORD(T)])" + else if(A.loc) + return "(UNKNOWN (?, ?, ?))" diff --git a/code/_helpers/lists.dm b/code/_helpers/lists.dm index c39c9786d53..88e175e0424 100644 --- a/code/_helpers/lists.dm +++ b/code/_helpers/lists.dm @@ -250,7 +250,7 @@ proc/listclearnulls(list/list) else L[key] = temp[key] - + //Mergesort: divides up the list into halves to begin the sort /proc/sortKey(var/list/client/L, var/order = 1) if(isnull(L) || L.len < 2) diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm index 118f648cb38..22e6aef3ad8 100644 --- a/code/_helpers/unsorted.dm +++ b/code/_helpers/unsorted.dm @@ -1461,3 +1461,15 @@ var/mob/dview/dview_mob = new /proc/pass() return + +GLOBAL_REAL_VAR(list/stack_trace_storage) +/proc/gib_stack_trace() + stack_trace_storage = list() + stack_trace() + stack_trace_storage.Cut(1, min(3,stack_trace_storage.len)) + . = stack_trace_storage + stack_trace_storage = null + +//gives us the stack trace from CRASH() without ending the current proc. +/proc/stack_trace(msg) + CRASH(msg) diff --git a/code/controllers/ProcessScheduler/core/process.dm b/code/controllers/ProcessScheduler/core/process.dm index 2965a52abea..e841e52e2cf 100644 --- a/code/controllers/ProcessScheduler/core/process.dm +++ b/code/controllers/ProcessScheduler/core/process.dm @@ -361,7 +361,7 @@ /datum/controller/process/proc/catchException(var/exception/e, var/thrower) if(istype(e)) // Real runtimes go to the real error handler - log_runtime(e, thrower, "Caught by process: [name]") + world.Error(e, thrower, "Caught by process: [name]") return var/etext = "[e]" var/eid = "[e]" // Exception ID, for tracking repeated exceptions diff --git a/code/controllers/subsystems/mobs.dm b/code/controllers/subsystems/mobs.dm index a42caad0d98..26dd8c19206 100644 --- a/code/controllers/subsystems/mobs.dm +++ b/code/controllers/subsystems/mobs.dm @@ -52,7 +52,7 @@ SUBSYSTEM_DEF(mobs) continue M.Life(times_fired) catch(var/exception/e) - log_runtime(e, M, "Caught by [name] subsystem") + world.Error(e, M, "Caught by [name] subsystem") if (MC_TICK_CHECK) return diff --git a/code/modules/error_handler/error_handler.dm b/code/modules/error_handler/error_handler.dm index 3369fbf5129..b4c838d4d48 100644 --- a/code/modules/error_handler/error_handler.dm +++ b/code/modules/error_handler/error_handler.dm @@ -1,117 +1,116 @@ GLOBAL_VAR_INIT(total_runtimes, GLOB.total_runtimes || 0) GLOBAL_VAR_INIT(total_runtimes_skipped, 0) -// The ifdef needs to be down here, since the error viewer references total_runtimes -#ifdef DEBUG -/world/Error(var/exception/e, var/datum/e_src) - if(!istype(e)) // Something threw an unusual exception - log_error("\[[time_stamp()]] Uncaught exception: [e]") - return ..() - var/static/list/error_last_seen = list() - // error_cooldown items will either be positive (cooldown time) or negative (silenced error) - // If negative, starts at -1, and goes down by 1 each time that error gets skipped - var/static/list/error_cooldown = list() +#ifdef USE_CUSTOM_ERROR_HANDLER +#define ERROR_USEFUL_LEN 2 + +/world/Error(exception/E, datum/e_src) GLOB.total_runtimes++ - var/erroruid = "[e.file][e.line]" + if(!istype(E)) //Something threw an unusual exception + log_world("uncaught runtime error: [E]") + return ..() + + //this is snowflake because of a byond bug (ID:2306577), do not attempt to call non-builtin procs in this if + if(copytext(E.name,1,32) == "Maximum recursion level reached") + //log to world while intentionally triggering the byond bug. + log_world("runtime error: [E.name]\n[E.desc]") + //if we got to here without silently ending, the byond bug has been fixed. + log_world("The bug with recursion runtimes has been fixed. Please remove the snowflake check from world/Error in [__FILE__]:[__LINE__]") + return //this will never happen. + + if (islist(stack_trace_storage)) + for (var/line in splittext(E.desc, "\n")) + if (text2ascii(line) != 32) + stack_trace_storage += line + + var/static/list/error_last_seen = list() + var/static/list/error_cooldown = list() /* Error_cooldown items will either be positive(cooldown time) or negative(silenced error) + If negative, starts at -1, and goes down by 1 each time that error gets skipped*/ + + if(!error_last_seen) // A runtime is occurring too early in start-up initialization + return ..() + + var/erroruid = "[E.file][E.line]" var/last_seen = error_last_seen[erroruid] var/cooldown = error_cooldown[erroruid] || 0 - if(last_seen == null) // A new error! + + if(last_seen == null) error_last_seen[erroruid] = world.time last_seen = world.time - if(cooldown < 0) - error_cooldown[erroruid]-- // Used to keep track of skip count for this error - GLOB.total_runtimes_skipped++ - return // Error is currently silenced, skip handling it - // Handle cooldowns and silencing spammy errors - var/silencing = 0 - // Each occurrence of a unique error adds to its "cooldown" time... - cooldown = max(0, cooldown - (world.time - last_seen)) + ERROR_COOLDOWN + if(cooldown < 0) + error_cooldown[erroruid]-- //Used to keep track of skip count for this error + GLOB.total_runtimes_skipped++ + return //Error is currently silenced, skip handling it + //Handle cooldowns and silencing spammy errors + var/silencing = FALSE + + var/configured_error_cooldown = ERROR_COOLDOWN + var/configured_error_limit = ERROR_LIMIT + var/configured_error_silence_time = ERROR_SILENCE_TIME + + + //Each occurence of a unique error adds to its cooldown time... + cooldown = max(0, cooldown - (world.time - last_seen)) + configured_error_cooldown // ... which is used to silence an error if it occurs too often, too fast - if(cooldown > ERROR_MAX_COOLDOWN) + if(cooldown > configured_error_cooldown * configured_error_limit) cooldown = -1 - silencing = 1 + silencing = TRUE spawn(0) usr = null - sleep(ERROR_SILENCE_TIME) + sleep(configured_error_silence_time) var/skipcount = abs(error_cooldown[erroruid]) - 1 error_cooldown[erroruid] = 0 if(skipcount > 0) - log_error("\[[time_stamp()]] Skipped [skipcount] runtimes in [e.file],[e.line].") - error_cache.logError(e, skipCount = skipcount) + SEND_TEXT(world.log, "\[[time_stamp()]] Skipped [skipcount] runtimes in [E.file],[E.line].") + error_cache.logError(E, skip_count = skipcount) + error_last_seen[erroruid] = world.time error_cooldown[erroruid] = cooldown - // The detailed error info needs some tweaking to make it look nice - var/list/srcinfo = null var/list/usrinfo = null var/locinfo - // First, try to make better src/usr info lines - if(istype(e_src)) - srcinfo = list(" src: [log_info_line(e_src)]") - locinfo = log_info_line(e_src) - if(locinfo) - srcinfo += " src.loc: [locinfo]" if(istype(usr)) - usrinfo = list(" usr: [log_info_line(usr)]") - locinfo = log_info_line(usr) + usrinfo = list(" usr: [key_name(usr)]") + locinfo = loc_name(usr) if(locinfo) usrinfo += " usr.loc: [locinfo]" // The proceeding mess will almost definitely break if error messages are ever changed - // I apologize in advance - var/list/splitlines = splittext(e.desc, "\n") + var/list/splitlines = splittext(E.desc, "\n") var/list/desclines = list() - if(splitlines.len > 2) // If there aren't at least three lines, there's no info + if(LAZYLEN(splitlines) > ERROR_USEFUL_LEN) // If there aren't at least three lines, there's no info for(var/line in splitlines) - if(length(line) < 3) - continue // Blank line, skip it - if(findtext(line, "source file:")) - continue // Redundant, skip it - if(findtext(line, "usr.loc:")) - continue // Our usr.loc is better, skip it + if(LAZYLEN(line) < 3 || findtext(line, "source file:") || findtext(line, "usr.loc:")) + continue if(findtext(line, "usr:")) if(usrinfo) desclines.Add(usrinfo) usrinfo = null continue // Our usr info is better, replace it - if(srcinfo) - if(findtext(line, "src.loc:")) - continue - if(findtext(line, "src:")) - desclines.Add(srcinfo) - srcinfo = null - continue + if(copytext(line, 1, 3) != " ") desclines += (" " + line) // Pad any unpadded lines, so they look pretty else desclines += line - if(srcinfo) // If these aren't null, they haven't been added yet - desclines.Add(srcinfo) - if(usrinfo) + if(usrinfo) //If this info isn't null, it hasn't been added yet desclines.Add(usrinfo) if(silencing) - desclines += " (This error will now be silenced for [ERROR_SILENCE_TIME / 600] minutes)" - - // Now to actually output the error info... - log_error("\[[time_stamp()]] Runtime in [e.file],[e.line]: [e]") - for(var/line in desclines) - log_error(line) + desclines += " (This error will now be silenced for [DisplayTimeText(configured_error_silence_time)])" if(error_cache) - error_cache.logError(e, desclines, e_src = e_src) + error_cache.logError(E, desclines) + var/main_line = "\[[time_stamp()]] Runtime in [E.file],[E.line]: [E]" + SEND_TEXT(world.log, main_line) + for(var/line in desclines) + SEND_TEXT(world.log, line) + +#ifdef UNIT_TESTS + if(GLOB.current_test) + //good day, sir + GLOB.current_test.Fail("[main_line]\n[desclines.Join("\n")]") #endif -/proc/log_runtime(exception/e, datum/e_src, extra_info) - if(!istype(e)) - world.Error(e, e_src) - return - - if(extra_info) - // Adding extra info adds two newlines, because parsing runtimes is funky - if(islist(extra_info)) - e.desc = " [jointext(extra_info, "\n ")]\n\n" + e.desc - else - e.desc = " [extra_info]\n\n" + e.desc - - world.Error(e, e_src) + // This writes the regular format (unwrapping newlines and inserting timestamps as needed). + log_error("runtime error: [E.name]\n[E.desc]") +#endif diff --git a/code/modules/lighting/lighting_source.dm b/code/modules/lighting/lighting_source.dm index d878d8c4b6e..06669ff1b93 100644 --- a/code/modules/lighting/lighting_source.dm +++ b/code/modules/lighting/lighting_source.dm @@ -67,7 +67,7 @@ force_update() if(source_atom) if(!source_atom.light_sources) - log_runtime(EXCEPTION("Atom [source_atom] was a light source, but lacked a light source list!\n"), source_atom) + world.Error(EXCEPTION("Atom [source_atom] was a light source, but lacked a light source list!\n"), source_atom) else source_atom.light_sources -= src diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index f14d5fd4577..6be829283c6 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -916,7 +916,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() var/t_state = "[species.get_tail(src)]_once" var/used_tail_layer = tail_alt ? TAIL_LAYER_ALT : TAIL_LAYER // VOREStation Edit - Alt Tail Layer - + var/image/tail_overlay = overlays_standing[used_tail_layer] // VOREStation Edit - Alt Tail Layer if(tail_overlay && tail_overlay.icon_state == t_state) return //let the existing animation finish @@ -926,7 +926,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() spawn(20) //check that the animation hasn't changed in the meantime if(overlays_standing[used_tail_layer] == tail_overlay && tail_overlay.icon_state == t_state) // VOREStation Edit - Alt Tail Layer - animate_tail_stop() + animate_tail_stop() /mob/living/carbon/human/proc/animate_tail_start() if(QDESTROYING(src)) @@ -967,7 +967,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() if(vr_wing_image) vr_wing_image.layer = BODY_LAYER+WING_LAYER overlays_standing[WING_LAYER] = vr_wing_image - + apply_layer(WING_LAYER) // VOREStation Edit end diff --git a/vorestation.dme b/vorestation.dme index e864c560da7..003fa28ddb3 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -86,6 +86,7 @@ #include "code\_global_vars\sensitive.dm" #include "code\_global_vars\lists\mapping.dm" #include "code\_helpers\_global_objects.dm" +#include "code\_helpers\_logging.dm" #include "code\_helpers\atmospherics.dm" #include "code\_helpers\events.dm" #include "code\_helpers\files.dm"