mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 09:42:29 +00:00
Ports Paradise error handler, with in game runtime viewer! (#24036)
* Ports paradise error handler, with in game runtime viewer! * Changes to the old runtime error and removes inerror reference * Oops * Adds a wrapper for world.log so it displays both in the runtime diary and in DD window
This commit is contained in:
114
code/modules/error_handler/error_handler.dm
Normal file
114
code/modules/error_handler/error_handler.dm
Normal file
@@ -0,0 +1,114 @@
|
||||
var/global/list/error_last_seen = list()
|
||||
var/global/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*/
|
||||
var/global/total_runtimes = 0
|
||||
var/global/total_runtimes_skipped = 0
|
||||
|
||||
#ifdef DEBUG
|
||||
/world/Error(exception/E, datum/e_src)
|
||||
if(!istype(E)) //Something threw an unusual exception
|
||||
log_world("\[[time_stamp()]] Uncaught exception: [E]")
|
||||
return ..()
|
||||
if(!error_last_seen) // A runtime is occurring too early in start-up initialization
|
||||
return ..()
|
||||
|
||||
total_runtimes++
|
||||
|
||||
var/erroruid = "[E.file][E.line]"
|
||||
var/last_seen = error_last_seen[erroruid]
|
||||
var/cooldown = error_cooldown[erroruid] || 0
|
||||
|
||||
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
|
||||
total_runtimes_skipped++
|
||||
return //Error is currently silenced, skip handling it
|
||||
//Handle cooldowns and silencing spammy errors
|
||||
var/silencing = FALSE
|
||||
|
||||
// We can runtime before config is initialized because BYOND initialize objs/map before a bunch of other stuff happens.
|
||||
// This is a bunch of workaround code for that. Hooray!
|
||||
|
||||
var/configured_error_cooldown = initial(config.error_cooldown)
|
||||
var/configured_error_limit = initial(config.error_limit)
|
||||
var/configured_error_silence_time = initial(config.error_silence_time)
|
||||
if(config)
|
||||
configured_error_cooldown = config.error_cooldown
|
||||
configured_error_limit = config.error_limit
|
||||
configured_error_silence_time = config.error_silence_time
|
||||
|
||||
|
||||
//Each occurence of an 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 > configured_error_cooldown * configured_error_limit)
|
||||
cooldown = -1
|
||||
silencing = TRUE
|
||||
spawn(0)
|
||||
usr = null
|
||||
sleep(configured_error_silence_time)
|
||||
var/skipcount = abs(error_cooldown[erroruid]) - 1
|
||||
error_cooldown[erroruid] = 0
|
||||
if(skipcount > 0)
|
||||
world.log << "\[[time_stamp()]] Skipped [skipcount] runtimes in [E.file],[E.line]."
|
||||
error_cache.log_error(E, skip_count = skipcount)
|
||||
|
||||
error_last_seen[erroruid] = world.time
|
||||
error_cooldown[erroruid] = cooldown
|
||||
|
||||
var/list/usrinfo = null
|
||||
var/locinfo
|
||||
if(istype(usr))
|
||||
usrinfo = list(" usr: [datum_info_line(usr)]")
|
||||
locinfo = atom_loc_line(usr)
|
||||
if(locinfo)
|
||||
usrinfo += " usr.loc: [locinfo]"
|
||||
// The proceeding mess will almost definitely break if error messages are ever changed
|
||||
var/list/splitlines = splittext(E.desc, "\n")
|
||||
var/list/desclines = list()
|
||||
if(LAZYLEN(splitlines) > ERROR_USEFUL_LEN) // If there aren't at least three lines, there's no info
|
||||
for(var/line in splitlines)
|
||||
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(copytext(line, 1, 3) != " ")
|
||||
desclines += (" " + line) // Pad any unpadded lines, so they look pretty
|
||||
else
|
||||
desclines += line
|
||||
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 [configured_error_silence_time / 600] minutes)"
|
||||
if(error_cache)
|
||||
error_cache.log_error(E, desclines)
|
||||
|
||||
world.log << "\[[time_stamp()]] Runtime in [E.file],[E.line]: [E]"
|
||||
for(var/line in desclines)
|
||||
world.log << line
|
||||
|
||||
/* This logs the runtime in the old format */
|
||||
|
||||
E.name = "\n\[[time2text(world.timeofday,"hh:mm:ss")]\][E.name]"
|
||||
|
||||
//Original
|
||||
//
|
||||
var/list/split = splittext(E.desc, "\n")
|
||||
for (var/i in 1 to split.len)
|
||||
if (split[i] != "")
|
||||
split[i] = "\[[time2text(world.timeofday,"hh:mm:ss")]\][split[i]]"
|
||||
E.desc = jointext(split, "\n")
|
||||
if(config && config.log_runtimes)
|
||||
world.log = runtime_diary
|
||||
..(E)
|
||||
|
||||
world.log = null
|
||||
|
||||
#endif
|
||||
194
code/modules/error_handler/error_viewer.dm
Normal file
194
code/modules/error_handler/error_viewer.dm
Normal file
@@ -0,0 +1,194 @@
|
||||
// Error viewing datums, responsible for storing error info, notifying admins
|
||||
// when errors occur, and showing them to admins on demand.
|
||||
|
||||
// There are 3 different types used here:
|
||||
//
|
||||
// - error_cache keeps track of all error sources, as well as all individually
|
||||
// logged errors. Only one instance of this datum should ever exist, and it's
|
||||
// right here:
|
||||
|
||||
#ifdef DEBUG
|
||||
/var/datum/error_viewer/error_cache/error_cache = new()
|
||||
#else
|
||||
// If debugging is disabled, there's nothing useful to log, so don't bother.
|
||||
/var/datum/error_viewer/error_cache/error_cache = null
|
||||
#endif
|
||||
|
||||
// - error_source datums exist for each line (of code) that generates an error,
|
||||
// and keep track of all errors generated by that line.
|
||||
//
|
||||
// - error_entry datums exist for each logged error, and keep track of all
|
||||
// relevant info about that error.
|
||||
|
||||
// Common vars and procs are kept at the error_viewer level
|
||||
/datum/error_viewer
|
||||
var/name = ""
|
||||
|
||||
/datum/error_viewer/proc/browse_to(client/user, html)
|
||||
var/datum/browser/browser = new(user.mob, "error_viewer", null, 600, 400)
|
||||
browser.set_content(html)
|
||||
browser.add_head_content({"
|
||||
<style>
|
||||
.runtime
|
||||
{
|
||||
background-color: #171717;
|
||||
border: solid 1px #202020;
|
||||
font-family: "Courier New";
|
||||
padding-left: 10px;
|
||||
color: #CCCCCC;
|
||||
}
|
||||
.runtime_line
|
||||
{
|
||||
margin-bottom: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
"})
|
||||
browser.open()
|
||||
|
||||
/datum/error_viewer/proc/build_header(datum/error_viewer/back_to, linear)
|
||||
// Common starter HTML for show_to
|
||||
|
||||
. = ""
|
||||
|
||||
if (istype(back_to))
|
||||
. += back_to.make_link("<b><<<</b>", null, linear)
|
||||
|
||||
. += "[make_link("Refresh")]<br><br>"
|
||||
|
||||
/datum/error_viewer/proc/show_to(user, datum/error_viewer/back_to, linear)
|
||||
// Specific to each child type
|
||||
return
|
||||
|
||||
/datum/error_viewer/proc/make_link(linktext, datum/error_viewer/back_to, linear)
|
||||
var/back_to_param = ""
|
||||
if (!linktext)
|
||||
linktext = name
|
||||
|
||||
if (istype(back_to))
|
||||
back_to_param = ";viewruntime_backto=\ref[back_to]"
|
||||
|
||||
if (linear)
|
||||
back_to_param += ";viewruntime_linear=1"
|
||||
|
||||
return "<a href='?_src_=holder;viewruntime=\ref[src][back_to_param]'>[linktext]</a>"
|
||||
|
||||
/datum/error_viewer/error_cache
|
||||
var/list/errors = list()
|
||||
var/list/error_sources = list()
|
||||
var/list/errors_silenced = list()
|
||||
|
||||
/datum/error_viewer/error_cache/show_to(user, datum/error_viewer/back_to, linear)
|
||||
var/html = build_header()
|
||||
html += "<b>[global.total_runtimes]</b> runtimes, <b>[global.total_runtimes_skipped]</b> skipped<br><br>"
|
||||
if (!linear)
|
||||
html += "organized | [make_link("linear", null, 1)]<hr>"
|
||||
var/datum/error_viewer/error_source/error_source
|
||||
for (var/erroruid in error_sources)
|
||||
error_source = error_sources[erroruid]
|
||||
html += "[error_source.make_link(null, src)]<br>"
|
||||
|
||||
else
|
||||
html += "[make_link("organized", null)] | linear<hr>"
|
||||
for (var/datum/error_viewer/error_entry/error_entry in errors)
|
||||
html += "[error_entry.make_link(null, src, 1)]<br>"
|
||||
|
||||
browse_to(user, html)
|
||||
|
||||
/datum/error_viewer/error_cache/proc/log_error(exception/e, list/desclines, skip_count)
|
||||
if (!istype(e))
|
||||
return // Abnormal exception, don't even bother
|
||||
|
||||
var/erroruid = "[e.file][e.line]"
|
||||
var/datum/error_viewer/error_source/error_source = error_sources[erroruid]
|
||||
if (!error_source)
|
||||
error_source = new(e)
|
||||
error_sources[erroruid] = error_source
|
||||
|
||||
var/datum/error_viewer/error_entry/error_entry = new(e, desclines, skip_count)
|
||||
error_entry.error_source = error_source
|
||||
errors += error_entry
|
||||
error_source.errors += error_entry
|
||||
if (skip_count)
|
||||
return // Skip notifying admins about skipped errors.
|
||||
|
||||
// Show the error to admins with debug messages turned on, but only if one
|
||||
// from the same source hasn't been shown too recently
|
||||
if (error_source.next_message_at <= world.time)
|
||||
var/const/viewtext = "\[view]" // Nesting these in other brackets went poorly
|
||||
//log_debug("Runtime in <b>[e.file]</b>, line <b>[e.line]</b>: <b>[html_encode(e.name)]</b> [error_entry.make_link(viewtext)]")
|
||||
var/err_msg_delay
|
||||
if(config)
|
||||
err_msg_delay = config.error_msg_delay
|
||||
else
|
||||
err_msg_delay = initial(config.error_msg_delay)
|
||||
error_source.next_message_at = world.time + err_msg_delay
|
||||
|
||||
/datum/error_viewer/error_source
|
||||
var/list/errors = list()
|
||||
var/next_message_at = 0
|
||||
|
||||
/datum/error_viewer/error_source/New(exception/e)
|
||||
if (!istype(e))
|
||||
name = "\[[time_stamp()]] Uncaught exceptions"
|
||||
return
|
||||
|
||||
name = "<b>\[[time_stamp()]]</b> Runtime in <b>[e.file]</b>, line <b>[e.line]</b>: <b>[html_encode(e.name)]</b>"
|
||||
|
||||
/datum/error_viewer/error_source/show_to(user, datum/error_viewer/back_to, linear)
|
||||
if (!istype(back_to))
|
||||
back_to = error_cache
|
||||
|
||||
var/html = build_header(back_to)
|
||||
for (var/datum/error_viewer/error_entry/error_entry in errors)
|
||||
html += "[error_entry.make_link(null, src)]<br>"
|
||||
|
||||
browse_to(user, html)
|
||||
|
||||
/datum/error_viewer/error_entry
|
||||
var/datum/error_viewer/error_source/error_source
|
||||
var/exception/exc
|
||||
var/desc = ""
|
||||
var/usr_ref
|
||||
var/turf/usr_loc
|
||||
var/is_skip_count
|
||||
|
||||
/datum/error_viewer/error_entry/New(exception/e, list/desclines, skip_count)
|
||||
if (!istype(e))
|
||||
name = "<b>\[[time_stamp()]]</b> Uncaught exception: <b>[html_encode(e.name)]</b>"
|
||||
return
|
||||
|
||||
if(skip_count)
|
||||
name = "\[[time_stamp()]] Skipped [skip_count] runtimes in [e.file],[e.line]."
|
||||
is_skip_count = TRUE
|
||||
return
|
||||
|
||||
name = "<b>\[[time_stamp()]]</b> Runtime in <b>[e.file]</b>, line <b>[e.line]</b>: <b>[html_encode(e.name)]</b>"
|
||||
exc = e
|
||||
if (istype(desclines))
|
||||
for (var/line in desclines)
|
||||
// There's probably a better way to do this than non-breaking spaces...
|
||||
desc += "<span class='runtime_line'>[html_encode(line)]</span><br>"
|
||||
|
||||
if (usr)
|
||||
usr_ref = "\ref[usr]"
|
||||
usr_loc = get_turf(usr)
|
||||
|
||||
/datum/error_viewer/error_entry/show_to(user, datum/error_viewer/back_to, linear)
|
||||
if (!istype(back_to))
|
||||
back_to = error_source
|
||||
|
||||
var/html = build_header(back_to, linear)
|
||||
html += "[name]<div class='runtime'>[desc]</div>"
|
||||
if (usr_ref)
|
||||
html += "<br><b>usr</b>: <a href='?_src_=vars;Vars=[usr_ref]'>VV</a>"
|
||||
html += " <a href='?_src_=holder;adminplayeropts=[usr_ref]'>PP</a>"
|
||||
html += " <a href='?_src_=holder;adminplayerobservefollow=[usr_ref]'>Follow</a>"
|
||||
if (istype(usr_loc))
|
||||
html += "<br><b>usr.loc</b>: <a href='?_src_=vars;Vars=\ref[usr_loc]'>VV</a>"
|
||||
html += " <a href='?_src_=holder;adminplayerobservecoodjump=1;X=[usr_loc.x];Y=[usr_loc.y];Z=[usr_loc.z]'>JMP</a>"
|
||||
|
||||
browse_to(user, html)
|
||||
|
||||
/datum/error_viewer/error_entry/make_link(linktext, datum/error_viewer/back_to, linear)
|
||||
return is_skip_count ? name : ..()
|
||||
Reference in New Issue
Block a user