diff --git a/code/controllers/ProcessScheduler/core/process.dm b/code/controllers/ProcessScheduler/core/process.dm
index 836d2124bd..2d5b8b1b7d 100644
--- a/code/controllers/ProcessScheduler/core/process.dm
+++ b/code/controllers/ProcessScheduler/core/process.dm
@@ -327,9 +327,7 @@
/datum/controller/process/proc/catchException(var/exception/e, var/thrower)
if(istype(e)) // Real runtimes go to the real error handler
- // There are two newlines here, because handling desc sucks
- e.desc = " Caught by process: [name]\n\n" + e.desc
- world.Error(e, e_src = thrower)
+ log_runtime(e, thrower, "Caught by process: [name]")
return
var/etext = "[e]"
var/eid = "[e]" // Exception ID, for tracking repeated exceptions
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 335241d15e..eaadf431bc 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1176,6 +1176,18 @@
sleep(2)
C.jumptomob(M)
+ else if(href_list["adminplayerobservefollow"])
+ if(!check_rights(R_MENTOR|R_MOD|R_ADMIN|R_SERVER))
+ return
+
+ var/mob/M = locate(href_list["adminplayerobservefollow"])
+
+ var/client/C = usr.client
+ if(!isobserver(usr)) C.admin_ghost()
+ var/mob/observer/dead/G = C.mob
+ sleep(2)
+ G.ManualFollow(M)
+
else if(href_list["check_antagonist"])
check_antagonists()
diff --git a/code/modules/error_handler/error_handler.dm b/code/modules/error_handler/error_handler.dm
index 51fabd76fe..de71bfc1ca 100644
--- a/code/modules/error_handler/error_handler.dm
+++ b/code/modules/error_handler/error_handler.dm
@@ -8,7 +8,7 @@ var/total_runtimes_skipped = 0
#ifdef DEBUG
/world/Error(var/exception/e, var/datum/e_src)
if(!istype(e)) // Something threw an unusual exception
- log_to_dd("\[[time2text(world.timeofday, "hh:mm")]] Uncaught exception: [e]")
+ log_to_dd("\[[time_stamp()]] Uncaught exception: [e]")
return ..()
if(!error_last_seen) // A runtime is occurring too early in start-up initialization
return ..()
@@ -39,7 +39,7 @@ var/total_runtimes_skipped = 0
var/skipcount = abs(error_cooldown[erroruid]) - 1
error_cooldown[erroruid] = 0
if(skipcount > 0)
- log_to_dd("\[[time2text(world.timeofday, "hh:mm")]] Skipped [skipcount] runtimes in [e.file],[e.line].")
+ log_to_dd("\[[time_stamp()]] Skipped [skipcount] runtimes in [e.file],[e.line].")
error_cache.logError(e, skipCount = skipcount)
error_last_seen[erroruid] = world.time
error_cooldown[erroruid] = cooldown
@@ -95,10 +95,24 @@ var/total_runtimes_skipped = 0
desclines += " (This error will now be silenced for [ERROR_SILENCE_TIME / 600] minutes)"
// Now to actually output the error info...
- log_to_dd("\[[time2text(world.timeofday, "hh:mm")]] Runtime in [e.file],[e.line]: [e]")
+ log_to_dd("\[[time_stamp()]] Runtime in [e.file],[e.line]: [e]")
for(var/line in desclines)
log_to_dd(line)
if(error_cache)
error_cache.logError(e, desclines, e_src = e_src)
#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)
diff --git a/code/modules/error_handler/error_viewer.dm b/code/modules/error_handler/error_viewer.dm
index 9bbab68b63..0f1fd0ba96 100644
--- a/code/modules/error_handler/error_viewer.dm
+++ b/code/modules/error_handler/error_viewer.dm
@@ -14,7 +14,7 @@ var/global/datum/ErrorViewer/ErrorCache/error_cache = new()
var/global/datum/ErrorViewer/ErrorCache/error_cache = null
#endif
-// - ErrorSource datums exist for each line (of code) that generates an error,
+// - ErrorSource datums exist for each line (of code) that generates an error,
// and keep track of all errors generated by that line.
//
// - ErrorEntry datums exist for each logged error, and keep track of all
@@ -71,7 +71,15 @@ var/global/datum/ErrorViewer/ErrorCache/error_cache = null
back_to_param = ";viewruntime_backto=\ref[back_to]"
if(linear)
back_to_param += ";viewruntime_linear=1"
- return "[html_encode(linktext)]"
+ return "[html_encode(linktext)]"
+
+/datum/ErrorViewer/Topic(href, href_list)
+ if(..())
+ return 1
+ if(href_list["viewruntime_backto"])
+ showTo(usr, locate(href_list["viewruntime_backto"]), href_list["viewruntime_linear"])
+ else
+ showTo(usr, null, href_list["viewruntime_linear"])
/datum/ErrorViewer/ErrorCache
var/list/errors = list()