From 603f7e60b4a536a1c5229b54e70fe8c8b40b8c2c Mon Sep 17 00:00:00 2001 From: Flleeppyy Date: Wed, 30 Jul 2025 20:29:47 -0700 Subject: [PATCH 1/5] this is just gonna sit here until i fix it. --- cev_eris.dme | 3 +- code/__DEFINES/rust_g.dm | 5 + code/__HELPERS/time.dm | 4 + .../configuration/entries/general.dm | 23 ++ code/datums/topic/admin.dm | 6 +- code/modules/admin/admin_ranks.dm | 9 +- code/modules/admin/verbs/debug.dm | 2 +- code/modules/error_handler/error_handler.dm | 17 +- code/modules/error_handler/error_viewer.dm | 271 +++++++++--------- code/modules/error_handler/glitchtip.dm | 238 +++++++++++++++ code/modules/http.dm | 121 ++++++++ code/modules/text_to_speech/tts_html.dm | 81 ------ config/example/config.txt | 6 + dependencies.sh | 2 +- tools/tgs4_scripts/PreCompile.sh | 2 +- 15 files changed, 557 insertions(+), 233 deletions(-) create mode 100644 code/modules/error_handler/glitchtip.dm create mode 100644 code/modules/http.dm delete mode 100644 code/modules/text_to_speech/tts_html.dm diff --git a/cev_eris.dme b/cev_eris.dme index 56682cb444a..3b912956f91 100644 --- a/cev_eris.dme +++ b/cev_eris.dme @@ -1408,6 +1408,7 @@ #include "code\game\verbs\who.dm" #include "code\js\byjax.dm" #include "code\js\menus.dm" +#include "code\modules\http.dm" #include "code\modules\aberrants\_defines.dm" #include "code\modules\aberrants\_modification.dm" #include "code\modules\aberrants\organs\holders.dm" @@ -1818,6 +1819,7 @@ #include "code\modules\emoji\emoji_parse.dm" #include "code\modules\error_handler\error_handler.dm" #include "code\modules\error_handler\error_viewer.dm" +#include "code\modules\error_handler\glitchtip.dm" #include "code\modules\examine\examine.dm" #include "code\modules\ext_scripts\irc.dm" #include "code\modules\ext_scripts\python.dm" @@ -2862,7 +2864,6 @@ #include "code\modules\telesci\circuits.dm" #include "code\modules\telesci\telepads.dm" #include "code\modules\telesci\telesci_computer.dm" -#include "code\modules\text_to_speech\tts_html.dm" #include "code\modules\text_to_speech\tts_main.dm" #include "code\modules\tgchat\message.dm" #include "code\modules\tgchat\to_chat.dm" diff --git a/code/__DEFINES/rust_g.dm b/code/__DEFINES/rust_g.dm index 16e901c7013..21c3c7503ef 100644 --- a/code/__DEFINES/rust_g.dm +++ b/code/__DEFINES/rust_g.dm @@ -198,6 +198,9 @@ #define rustg_http_request_blocking(method, url, body, headers, options) RUSTG_CALL(RUST_G, "http_request_blocking")(method, url, body, headers, options) #define rustg_http_request_async(method, url, body, headers, options) RUSTG_CALL(RUST_G, "http_request_async")(method, url, body, headers, options) #define rustg_http_check_request(req_id) RUSTG_CALL(RUST_G, "http_check_request")(req_id) +/// This is basically just `rustg_http_request_async` if you don't care about the response. +/// This will either return "ok" or an error, as this does not create a job. +#define rustg_http_request_fire_and_forget(method, url, body, headers, options) RUSTG_CALL(RUST_G, "http_request_fire_and_forget")(method, url, body, headers, options) /// Generates a spritesheet at: [file_path][spritesheet_name]_[size_id].[png or dmi] /// The resulting spritesheet arranges icons in a random order, with the position being denoted in the "sprites" return value. @@ -438,3 +441,5 @@ #define url_decode(text) rustg_url_decode(text) #endif +/// Generates a version 4 UUID. +#define rustg_generate_uuid_v4(...) RUSTG_CALL(RUST_G, "uuid_v4")() diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm index 72339daa840..b46fac15687 100644 --- a/code/__HELPERS/time.dm +++ b/code/__HELPERS/time.dm @@ -41,6 +41,10 @@ var/next_station_date_change = 1 DAYS /proc/time_stamp() return time2text(world.timeofday, "hh:mm:ss") +/proc/time_stamp_metric() + var/date_portion = time2text(world.timeofday, "YYYY-MM-DD") + var/time_portion = time2text(world.timeofday, "hh:mm:ss") + return "[date_portion]T[time_portion]" //Returns the world time in english /proc/worldtime2text(time = world.time, timeshift = 1) diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 5717a6d230d..dddfaed3826 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -272,6 +272,29 @@ /datum/config_entry/flag/log_world_topic + +/*****************/ +/*ERROR REPORTING*/ +/*****************/ + +/datum/config_entry/string/glitchtip_dsn + config_entry_value = "" + +/datum/config_entry/string/glitchtip_dsn/ValidateAndSet(str_val) + var/dsn_clean = replacetext(replacetext(str_val, "http://", ""), "https://", "") + var/at_pos = findtext(dsn_clean, "@") + var/slash_pos = findtext(dsn_clean, "/", at_pos) + + if(!at_pos || !slash_pos) + return FALSE + return ..() + +/datum/config_entry/string/glitchtip_environment + config_entry_value = "production" + +/datum/config_entry/flag/glitchtip_enabled + + /*****************/ /* VOTES */ /*****************/ diff --git a/code/datums/topic/admin.dm b/code/datums/topic/admin.dm index 941ac991f83..12824920956 100644 --- a/code/datums/topic/admin.dm +++ b/code/datums/topic/admin.dm @@ -980,14 +980,14 @@ require_perms = list(R_DEBUG) /datum/admin_topic/viewruntime/Run(list/input) - var/datum/ErrorViewer/error_viewer = locate(input["viewruntime"]) + var/datum/error_viewer/error_viewer = locate(input["viewruntime"]) if(!istype(error_viewer)) to_chat(usr, span_warning("That runtime viewer no longer exists.")) return if(input["viewruntime_backto"]) - error_viewer.showTo(usr, locate(input["viewruntime_backto"]), input["viewruntime_linear"]) + error_viewer.show_to(usr, locate(input["viewruntime_backto"]), input["viewruntime_linear"]) else - error_viewer.showTo(usr, null, input["viewruntime_linear"]) + error_viewer.show_to(usr, null, input["viewruntime_linear"]) /datum/admin_topic/admincaster diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm index 89079cfce4a..6fb1589a061 100644 --- a/code/modules/admin/admin_ranks.dm +++ b/code/modules/admin/admin_ranks.dm @@ -2,12 +2,17 @@ GLOBAL_LIST_EMPTY(admin_ranks) //list of all admin_rank datums GLOBAL_PROTECT(admin_ranks) +#define ADMINS_TXT_PATH "config/admins.txt" // This proc is using only without database connection /proc/load_admins_legacy() load_admin_ranks_legacy() + if (!rustg_file_exists(ADMINS_TXT_PATH)) + warning("[ADMINS_TXT_PATH] not present! No admins will be loaded this round!") + return + //load text from file - var/list/Lines = file2list("config/admins.txt") + var/list/Lines = file2list(ADMINS_TXT_PATH) //process each line seperately for(var/line in Lines) @@ -172,3 +177,5 @@ GLOBAL_PROTECT(admin_ranks) flag |= admin_permissions[key] return flag + +#undef ADMINS_TXT_PATH diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index e1027ddf322..c029c1d3336 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -390,7 +390,7 @@ set category = "Debug" set name = "View Runtimes" set desc = "Open the Runtime Viewer" - GLOB.error_cache.showTo(usr) + GLOB.error_cache.show_to(usr) /client/proc/spawn_disciple() set category = "Debug" diff --git a/code/modules/error_handler/error_handler.dm b/code/modules/error_handler/error_handler.dm index 7922f614992..4bc419c875c 100644 --- a/code/modules/error_handler/error_handler.dm +++ b/code/modules/error_handler/error_handler.dm @@ -4,11 +4,6 @@ GLOBAL_VAR_INIT(total_runtimes_skipped, 0) #ifdef USE_CUSTOM_ERROR_HANDLER #define ERROR_USEFUL_LEN 2 -var/regex/stack_workaround = regex("[WORKAROUND_IDENTIFIER](.+?)[WORKAROUND_IDENTIFIER]") -var/list/error_last_seen = list() -var/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*/ - /world/Error(exception/E, datum/e_src) GLOB.total_runtimes++ @@ -26,11 +21,16 @@ var/list/error_cooldown = list() /* Error_cooldown items will either be positive else if(copytext(E.name, 1, 18) == "Out of resources!")//18 == length() of that string + 1 log_world("BYOND out of memory. Restarting ([E?.file]:[E?.line])") + // SSplexora.notify_shutdown(PLEXORA_SHUTDOWN_KILLDD) TgsEndProcess() . = ..() Reboot(reason = 1) return + var/static/regex/stack_workaround = regex("[WORKAROUND_IDENTIFIER](.+?)[WORKAROUND_IDENTIFIER]") + 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 ..() @@ -38,7 +38,7 @@ var/list/error_cooldown = list() /* Error_cooldown items will either be positive if(!islist(error_last_seen)) return ..() //how the fuck? - if(stack_workaround?.Find(E.name)) + if(stack_workaround.Find(E.name)) var/list/data = json_decode(stack_workaround.group[1]) E.file = data[1] E.line = data[2] @@ -90,7 +90,7 @@ var/list/error_cooldown = list() /* Error_cooldown items will either be positive error_cooldown[erroruid] = 0 if(skipcount > 0) SEND_TEXT(world.log, "\[[time_stamp()]] Skipped [skipcount] runtimes in [E.file],[E.line].") - GLOB.error_cache.logError(E, skipCount = skipcount) + GLOB.error_cache.log_error(E, skip_count = skipcount) error_last_seen[erroruid] = world.time error_cooldown[erroruid] = cooldown @@ -136,7 +136,7 @@ var/list/error_cooldown = list() /* Error_cooldown items will either be positive if(silencing) desclines += " (This error will now be silenced for [DisplayTimeText(configured_error_silence_time)])" if(GLOB.error_cache) - GLOB.error_cache.logError(E, desclines) + GLOB.error_cache.log_error(E, desclines) var/main_line = "\[[time_stamp()]] Runtime in [E.file],[E.line]: [E]" SEND_TEXT(world.log, main_line) @@ -159,6 +159,7 @@ var/list/error_cooldown = list() /* Error_cooldown items will either be positive "name" = "[E.name]", "desc" = "[E.desc]" )) + send_to_glitchtip(E) #endif #undef ERROR_USEFUL_LEN diff --git a/code/modules/error_handler/error_viewer.dm b/code/modules/error_handler/error_viewer.dm index d3b17a910e4..8080f3005dd 100644 --- a/code/modules/error_handler/error_viewer.dm +++ b/code/modules/error_handler/error_viewer.dm @@ -3,194 +3,193 @@ // There are 3 different types used here: // -// - ErrorCache keeps track of all error sources, as well as all individually +// - 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 -GLOBAL_DATUM_INIT(error_cache, /datum/ErrorViewer/ErrorCache, new) +#ifdef USE_CUSTOM_ERROR_HANDLER +GLOBAL_DATUM_INIT(error_cache, /datum/error_viewer/error_cache, new) #else // If debugging is disabled, there's nothing useful to log, so don't bother. -GLOBAL_DATUM(error_cache, /datum/ErrorViewer/ErrorCache) +GLOBAL_DATUM(error_cache, /datum/error_viewer/error_cache) #endif -// - ErrorSource datums exist for each line (of code) that generates an error, +// - error_source 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 +// - 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 ErrorViewer level -/datum/ErrorViewer/ +// Common vars and procs are kept at the error_viewer level +/datum/error_viewer var/name = "" -/datum/ErrorViewer/proc/browseTo(user, html) - if(user) - var/datum/browser/popup = new(user, "error_viewer", "Runtime Viewer", 700, 500) - popup.add_head_content({""}) - popup.set_content(html) - popup.open(0) - -/datum/ErrorViewer/proc/buildHeader(datum/ErrorViewer/back_to, linear, refreshable) - // Common starter HTML for showTo - var/html = "" - - if(istype(back_to)) - html += "[back_to.makeLink("<<<", null, linear)] " - if(refreshable) - html += "[makeLink("Refresh", null, linear)]" - if(html) - html += "

" - return html - -/datum/ErrorViewer/proc/showTo(user, datum/ErrorViewer/back_to, linear) +/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({" + + "}) + 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("<<<", null, linear) + + . += "[make_link("Refresh")]

" + +/datum/error_viewer/proc/show_to(user, datum/error_viewer/back_to, linear) // Specific to each child type return -/datum/ErrorViewer/proc/makeLink(linktext, datum/ErrorViewer/back_to, linear) +/datum/error_viewer/proc/make_link(linktext, datum/error_viewer/back_to, linear) var/back_to_param = "" - if(!linktext) + if (!linktext) linktext = name - if(istype(back_to)) - back_to_param = ";viewruntime_backto=\ref[back_to]" - if(linear) + + if (istype(back_to)) + back_to_param = ";viewruntime_backto=[REF(back_to)]" + + if (linear) back_to_param += ";viewruntime_linear=1" - return "[html_encode(linktext)]" -/datum/ErrorViewer/ErrorCache + return "[linktext]" + +/datum/error_viewer/error_cache var/list/errors = list() var/list/error_sources = list() var/list/errors_silenced = list() -/datum/ErrorViewer/ErrorCache/showTo(user, datum/ErrorViewer/back_to, linear) - var/html = buildHeader(null, linear, refreshable=1) - html += "[GLOB.total_runtimes] runtimes, [GLOB.total_runtimes_skipped] skipped

" - if(!linear) - html += "organized | [makeLink("linear", null, 1)]
" - var/datum/ErrorViewer/ErrorSource/error_source - for(var/erroruid in error_sources) +/datum/error_viewer/error_cache/show_to(user, datum/error_viewer/back_to, linear) + var/html = build_header() + html += "[GLOB.total_runtimes] runtimes, [GLOB.total_runtimes_skipped] skipped

" + if (!linear) + html += "organized | [make_link("linear", null, 1)]
" + var/datum/error_viewer/error_source/error_source + for (var/erroruid in error_sources) error_source = error_sources[erroruid] - html += "

[error_source.makeLink(null, src)]

" + html += "[error_source.make_link(null, src)]
" + else - html += "[makeLink("organized", null)] | linear
" - for(var/datum/ErrorViewer/ErrorEntry/error_entry in errors) - html += "

[error_entry.makeLink(null, src, 1)]

" - browseTo(user, html) + html += "[make_link("organized", null)] | linear
" + for (var/datum/error_viewer/error_entry/error_entry in errors) + html += "[error_entry.make_link(null, src, 1)]
" + + browse_to(user, html) -/datum/ErrorViewer/ErrorCache/proc/logError(exception/e, list/desclines, skipCount, datum/e_src) - if(!istype(e)) +/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/ErrorViewer/ErrorSource/error_source = error_sources[erroruid] - if(!error_source) + 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/ErrorViewer/ErrorEntry/error_entry = new(e, desclines, skipCount, e_src) + 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(skipCount) - return // Skip notifying admins about skipped errors + 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) + if (error_source.next_message_at <= world.time) var/const/viewtext = "\[view]" // Nesting these in other brackets went poorly - log_debug("Runtime in [e.file],[e.line]: [html_encode(e.name)] [error_entry.makeLink(viewtext)]") - error_source.next_message_at = world.time + ERROR_MSG_DELAY - -/datum/ErrorViewer/ErrorSource + //log_debug("Runtime in [e.file], line [e.line]: [html_encode(e.name)] [error_entry.make_link(viewtext)]") + var/err_msg_delay + if(config?.loaded) + err_msg_delay = CONFIG_GET(number/error_msg_delay) + else + var/datum/config_entry/CE = /datum/config_entry/number/error_msg_delay + err_msg_delay = initial(CE.default) + 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/ErrorViewer/ErrorSource/New(exception/e) - if(!istype(e)) +/datum/error_viewer/error_source/New(exception/e) + if (!istype(e)) name = "\[[time_stamp()]] Uncaught exceptions" return - name = "\[[time_stamp()]] Runtime in [e.file],[e.line]: [e]" -/datum/ErrorViewer/ErrorSource/showTo(user, datum/ErrorViewer/back_to, linear) - if(!istype(back_to)) + name = "\[[time_stamp()]] Runtime in [e.file], line [e.line]: [html_encode(e.name)]" + +/datum/error_viewer/error_source/show_to(user, datum/error_viewer/back_to, linear) + if (!istype(back_to)) back_to = GLOB.error_cache - var/html = buildHeader(back_to, refreshable=1) - for(var/datum/ErrorViewer/ErrorEntry/error_entry in errors) - html += "

[error_entry.makeLink(null, src)]

" - browseTo(user, html) -/datum/ErrorViewer/ErrorEntry - var/datum/ErrorViewer/ErrorSource/error_source + var/html = build_header(back_to) + for (var/datum/error_viewer/error_entry/error_entry in errors) + html += "[error_entry.make_link(null, src)]
" + + browse_to(user, html) + +/datum/error_viewer/error_entry + var/datum/error_viewer/error_source/error_source var/exception/exc var/desc = "" - var/srcRef - var/srcType - var/turf/srcLoc - var/usrRef - var/turf/usrLoc - var/isSkipCount - -/datum/ErrorViewer/ErrorEntry/New(exception/e, list/desclines, skipCount, datum/e_src) - if(!istype(e)) - name = "\[[time_stamp()]] Uncaught exception: [e]" + 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 = "\[[time_stamp()]] Uncaught exception: [html_encode(e.name)]" return - if(skipCount) - name = "\[[time_stamp()]] Skipped [skipCount] runtimes in [e.file],[e.line]." - isSkipCount = TRUE + + if(skip_count) + name = "\[[time_stamp()]] Skipped [skip_count] runtimes in [e.file],[e.line]." + is_skip_count = TRUE return - name = "\[[time_stamp()]] Runtime in [e.file],[e.line]: [e]" + + name = "\[[time_stamp()]] Runtime in [e.file], line [e.line]: [html_encode(e.name)]" exc = e - if(istype(desclines)) - for(var/line in desclines) + if (istype(desclines)) + for (var/line in desclines) // There's probably a better way to do this than non-breaking spaces... - desc += "  " + html_encode(line) + "
" - if(istype(e_src)) - srcRef = "\ref[e_src]" - srcType = e_src.type - srcLoc = get_turf(e_src) - if(usr) - usrRef = "\ref[usr]" - usrLoc = get_turf(usr) - -/datum/ErrorViewer/ErrorEntry/showTo(user, datum/ErrorViewer/back_to, linear) - if(!istype(back_to)) + desc += "[html_encode(line)]
" + + 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 = buildHeader(back_to, linear) - html += "
[html_encode(name)]
[desc]
" - if(srcRef) - html += "
src: VV" - if(ispath(srcType, /mob)) - html += " [ADMIN_PP(srcRef)]" - html += " [ADMIN_FLW(srcRef)]" - if(istype(srcLoc)) - html += "
src.loc: [ADMIN_VV(srcLoc)]" - html += " [ADMIN_JMP(srcLoc)]" - if(usrRef) - html += "
usr: [ADMIN_VV(usrRef)]" - html += " [ADMIN_PP(usrRef)]" - html += " [ADMIN_FLW(usrRef)]" - if(istype(usrLoc)) - html += "
usr.loc: VV" - html += " [ADMIN_JMP(usrLoc)]" - browseTo(user, html) - -/datum/ErrorViewer/ErrorEntry/makeLink(linktext, datum/ErrorViewer/back_to, linear) - if(isSkipCount) - return html_encode(name) - return ..() + + var/html = build_header(back_to, linear) + html += "[name]
[desc]
" + if (usr_ref) + html += "
usr: VV" + html += " PP" + html += " Follow" + if (istype(usr_loc)) + html += "
usr.loc: VV" + html += " JMP" + + browse_to(user, html) + +/datum/error_viewer/error_entry/make_link(linktext, datum/error_viewer/back_to, linear) + return is_skip_count ? name : ..() diff --git a/code/modules/error_handler/glitchtip.dm b/code/modules/error_handler/glitchtip.dm new file mode 100644 index 00000000000..4160f53b322 --- /dev/null +++ b/code/modules/error_handler/glitchtip.dm @@ -0,0 +1,238 @@ +// This might be compatible with sentry, I'm not sure, my trial period expired so I can't test lol +// Configuration options are in entries/general.dm + +/proc/send_to_glitchtip(exception/E, list/extra_data = null) + if(!CONFIG_GET(flag/glitchtip_enabled) || !CONFIG_GET(string/glitchtip_dsn)) + return + + var/glitchtip_dsn = CONFIG_GET(string/glitchtip_dsn) + + // parse DSN to get the key, host and project id + // Format: https://key@host/project_id + var/dsn_clean = replacetext(replacetext(glitchtip_dsn, "http://", ""), "https://", "") + var/at_pos = findtext(dsn_clean, "@") + var/slash_pos = findtext(dsn_clean, "/", at_pos) + + if(!at_pos || !slash_pos) + log_runtime("Invalid Glitchtip DSN format") + return + + var/key = copytext(dsn_clean, 1, at_pos) + var/host = copytext(dsn_clean, at_pos + 1, slash_pos) + var/project_id = copytext(dsn_clean, slash_pos + 1) + + var/list/event_data = list() + event_data["event_id"] = rustg_generate_uuid_v4() + event_data["timestamp"] = time_stamp_metric() + event_data["level"] = "error" + event_data["platform"] = world.system_type + event_data["server_name"] = world.name + event_data["environment"] = CONFIG_GET(string/glitchtip_environment) + + event_data["sdk"] = list( + "name" = "byond-glitchtip", + "version" = "1.0.0" + ) + + var/list/exception_data = list() + exception_data["type"] = "BYOND Runtime Error" + exception_data["value"] = E.name + exception_data["module"] = E.file + + // Build stack trace using caller/callee chain + var/list/frames = list() + + // Add the error location as the first frame + var/list/error_frame = list() + error_frame["filename"] = E.file || "unknown" + error_frame["lineno"] = E.line || 0 + error_frame["function"] = "runtime_error" + error_frame["in_app"] = TRUE + frames += list(error_frame) + + // Walk the call stack using callee objects + var/frame_count = 0 + var/max_frames = 35 // Prevent infinite loops or excessive data. Realistically, this should not exceed 30~ + for(var/callee/p = caller; p && frame_count < max_frames; p = p.caller) + frame_count++ + var/proc_name = "unknown" + var/file_name = "unknown" + var/line_num = 0 + + if(p.proc) + proc_name = "[p.proc.type]" + // Clean up the proc name if it has path separators + var/slash_pos_inner = findtext(proc_name, "/", -1) + if(slash_pos_inner && slash_pos_inner < length(proc_name)) + proc_name = copytext(proc_name, slash_pos_inner + 1) + + if(findtext(file_name, "master.dm") && (proc_name == "Loop" || proc_name == "StartProcessing")) + break + + // Get file and line information if available + if(p.file) + file_name = p.file + line_num = p.line || 0 + + var/list/frame = list() + frame["filename"] = file_name + frame["lineno"] = line_num + frame["function"] = proc_name + frame["in_app"] = TRUE + + // Collect all available variables for this frame + var/list/frame_vars = list() + + // Add context variables + if(p.src) + frame_vars["src"] = "[p.src]" + if(p.usr) + frame_vars["usr"] = "[p.usr]" + + // Add procedure arguments + if(p.args && length(p.args)) + for(var/i = 1; i <= length(p.args); i++) + var/datum/arg_value = p.args[i] + var/arg_string = "null" + + // Not so sanely convert argument to string representation + try + if(arg_value == null) + arg_string = "null" + else if(isnum(arg_value)) + arg_string = "[arg_value]" + else if(istext(arg_value)) + // URL decode if it looks like URL-encoded data + var/decoded_value = arg_value + if(findtext(arg_value, "%") || findtext(arg_value, "&") || findtext(arg_value, "=")) + decoded_value = url_decode(arg_value) + + if(length(decoded_value) > 200) + arg_string = "\"[copytext(decoded_value, 1, 198)]...\"" + else + arg_string = "\"[decoded_value]\"" + else if(islist(arg_value)) + // Handle lists by showing summary and contents + var/list/L = arg_value + if(length(L) == 0) + arg_string = "list(empty)" + else + arg_string = "list([length(L)] items)" + + // Build contents string + var/list/content_items = list() + var/max_list_items = 20 // Prevent too long contents + var/items_to_show = min(length(L), max_list_items) + + for(var/j = 1; j <= items_to_show; j++) + var/datum/item = L[j] + var/item_string = "null" + + try + if(item == null) + item_string = "null" + else if(isnum(item)) + item_string = "[item]" + else if(istext(item)) + // URL decode as a treat + var/decoded_item = item + if(findtext(item, "%") || findtext(item, "&") || findtext(item, "=")) + decoded_item = url_decode(item) + + if(length(decoded_item) > 50) + item_string = "\"[copytext(decoded_item, 1, 48)]...\"" + else + item_string = "\"[decoded_item]\"" + else if(istype(item)) + var/item_type_name = "[item.type]" + var/slash_pos_item = findtext(item_type_name, "/", -1) + if(slash_pos_item && slash_pos_item < length(item_type_name)) + item_type_name = copytext(item_type_name, slash_pos_item + 1) + item_string = "[item_type_name]([item])" + else + item_string = "[item]" + catch + item_string = "" + + content_items += item_string + + var/contents_string = jointext(content_items, ", ") + if(length(L) > max_list_items) + contents_string += ", ... and [length(L) - max_list_items] more" + + frame_vars["arg[i]_contents"] = contents_string + else if(istype(arg_value)) + var/type_name = "[arg_value.type]" + var/slash_pos_obj = findtext(type_name, "/", -1) + if(slash_pos_obj && slash_pos_obj < length(type_name)) + type_name = copytext(type_name, slash_pos_obj + 1) + arg_string = "[type_name]: [arg_value]" + else + arg_string = "[arg_value]" + catch + arg_string = "" + + frame_vars["arg[i]"] = arg_string + + if(length(frame_vars)) + frame["vars"] = frame_vars + + + frames += list(frame) + + exception_data["stacktrace"] = list("frames" = frames) + event_data["exception"] = list("values" = list(exception_data)) + + // User context + if(istype(usr)) + var/list/user_data = list() + user_data["key"] = usr.key + user_data["character_name"] = usr.name + user_data["character_realname"] = usr.real_name + user_data["character_mobtype"] = usr.type + user_data["character_job"] = usr.GetJob() + if(usr.client) + user_data["byond_version"] = usr.client.byond_version + user_data["byond_build"] = usr.client.byond_build + // user_data["ip_address"] = usr.client.address + // user_data["computer_id"] = usr.client.computer_id + user_data["holder"] = usr.client.holder?.name + event_data["user"] = user_data + + // Add location context + var/locinfo = loc_name(usr) + if(locinfo) + if(!extra_data) + extra_data = list() + extra_data["user_location"] = locinfo + + if(extra_data) + event_data["extra"] = extra_data + + // Tags for filtering in Glitchtip + event_data["tags"] = list( + "round_id" = GLOB.round_id, + "file" = E.file, + "line" = "[E.line]", + "byond_version" = DM_VERSION, + "byond_build" = DM_BUILD, + ) + + event_data["fingerprint"] = list("[E.file]:[E.line]", E.name) + + send_glitchtip_request(event_data, host, project_id, key) + +/proc/send_glitchtip_request(list/event_data, host, project_id, key) + var/glitchtip_url = "https://[host]/api/[project_id]/store/" + var/json_payload = json_encode(event_data) + + // Glitchtip/Sentry auth header - According to docs this needs to be like this + var/auth_header = "Sentry sentry_version=7, sentry_client=byond-glitchtip/1.0.0, sentry_key=[key], sentry_timestamp=[time_stamp_metric()]" + + var/datum/http_request/request = new() + request.prepare(RUSTG_HTTP_METHOD_POST, glitchtip_url, json_payload, list( + "X-Sentry-Auth" = auth_header, + "Content-Type" = "application/json", + "User-Agent" = get_useragent("Glitchtip-Implementation") + )) + request.fire_and_forget() diff --git a/code/modules/http.dm b/code/modules/http.dm new file mode 100644 index 00000000000..cfc0d5615f7 --- /dev/null +++ b/code/modules/http.dm @@ -0,0 +1,121 @@ +/datum/http_request + var/id + var/in_progress = FALSE + + var/method + var/body + var/headers + var/url + /// If present response body will be saved to this file. + var/output_file + + var/_raw_response + +/datum/http_request/New(...) + . = ..() + if(length(args)) + src.prepare(arglist(args)) + +/datum/http_request/proc/prepare(method, url, body = "", list/headers, output_file) + if (!length(headers)) + headers = json_encode(list("User-Agent" = get_useragent())) + else + if (!headers["User-Agent"]) + headers["User-Agent"] = get_useragent() + headers = json_encode(headers) + + src.method = method + src.url = url + src.body = body + src.headers = headers + src.output_file = output_file + +/datum/http_request/proc/fire_and_forget() + var/result = rustg_http_request_fire_and_forget(method, url, body, headers, build_options()) + if(result != "ok") + CRASH("[result]") + +/datum/http_request/proc/execute_blocking() + _raw_response = rustg_http_request_blocking(method, url, body, headers, build_options()) + +/datum/http_request/proc/begin_async() + if (in_progress) + CRASH("Attempted to re-use a request object.") + + id = rustg_http_request_async(method, url, body, headers, build_options()) + + if (isnull(text2num(id))) + stack_trace("Proc error: [id]") + _raw_response = "Proc error: [id]" + else + in_progress = TRUE + +/datum/http_request/proc/build_options() + if(output_file) + return json_encode(list("output_filename"=output_file,"body_filename"=null)) + return null + +/datum/http_request/proc/is_complete() + if (isnull(id)) + return TRUE + + if (!in_progress) + return TRUE + + var/r = rustg_http_check_request(id) + + if (r == RUSTG_JOB_NO_RESULTS_YET) + return FALSE + else + _raw_response = r + in_progress = FALSE + return TRUE + +/datum/http_request/proc/into_response() + var/datum/http_response/R = new() + + try + var/list/L = json_decode(_raw_response) + R.status_code = L["status_code"] + R.headers = L["headers"] + R.body = L["body"] + catch + R.errored = TRUE + R.error = _raw_response + + return R + +/datum/http_response + var/status_code + var/body + var/list/headers + + var/errored = FALSE + var/error + +/** + * Returns a user-agent for http(s) requests + * * comment - {str || list} String or list, comments to be applied to the user-agent + * + * ``` + * // returns `BYOND 516.1666 ss13-monkestation/deadbeef (Comment-One; Comment-Two)` + * get_useragent(list("Comment-One", "Comment-Two")) + * // returns `BYOND 516.1666 ss13-monkestation/deadbeef (My-comment)` + * get_useragent("My-comment") + * ``` + */ +/proc/get_useragent(comment) + . = "BYOND/[DM_VERSION].[DM_BUILD] ss13-monkestation/[copytext(GLOB.revdata.commit, 0, 8) || "NOCOMMIT"] " + + if (istext(comment)) + . += " ([comment])" + else if (islist(comment)) + var/list/comments = comment + if (length(comments)) + . += " (" + for (var/i = 1; i <= length(comments); i++) + . += "[comments[i]]" + if (i == length(comments)) + . += ")" + break + . += ";" diff --git a/code/modules/text_to_speech/tts_html.dm b/code/modules/text_to_speech/tts_html.dm deleted file mode 100644 index b24b9148f2f..00000000000 --- a/code/modules/text_to_speech/tts_html.dm +++ /dev/null @@ -1,81 +0,0 @@ -/datum/http_request - var/id - var/in_progress = FALSE - var/method - var/body - var/headers - var/url - /// If present response body will be saved to this file. - var/output_file - var/_raw_response - - -/datum/http_request/proc/prepare(_method, _url, _body = "", list/_headers, _output_file) - headers = LAZYLEN(_headers) ? json_encode(_headers) : "" - method = _method - url = _url - body = _body - output_file = _output_file - - -/datum/http_request/proc/execute_blocking() - _raw_response = rustg_http_request_blocking(method, url, body, headers, build_options()) - - -/datum/http_request/proc/begin_async() - if(in_progress) - CRASH("Attempted to re-use a request object.") - - id = rustg_http_request_async(method, url, body, headers, build_options()) - - if(isnull(text2num(id))) - stack_trace("Proc error: [id]") - _raw_response = "Proc error: [id]" - else - in_progress = TRUE - - -/datum/http_request/proc/build_options() - if(output_file) - return json_encode(list("output_filename" = output_file, "body_filename" = null)) - return null - - -/datum/http_request/proc/is_complete() - if(isnull(id)) - return TRUE - - if(!in_progress) - return TRUE - - var/r = rustg_http_check_request(id) - - if(r == RUSTG_JOB_NO_RESULTS_YET) - return FALSE - else - _raw_response = r - in_progress = FALSE - return TRUE - - -/datum/http_request/proc/into_response() - var/datum/http_response/R = new() - - try - var/list/L = json_decode(_raw_response) - R.status_code = L["status_code"] - R.headers = L["headers"] - R.body = L["body"] - catch - R.errored = TRUE - R.error = _raw_response - - return R - - -/datum/http_response - var/status_code - var/body - var/list/headers - var/errored = FALSE - var/error diff --git a/config/example/config.txt b/config/example/config.txt index 6f123fa92ad..86cf46859e7 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -316,6 +316,12 @@ CONFIG_ERRORS_RUNTIME ##How long to wait between messaging admins about occurences of a unique error #ERROR_MSG_DELAY 50 +## Error reporting +# Your Sentry/Glitchtip DSN url +#GLITCHTIP_DSN +# Environment this is running in, "production", "development" and "testing" are most common +#GLITCHTIP_ENVIRONMENT development +#GLITCHTIP_ENABLED ## Uncomment to allow admins with +DEBUG to start the byond-tracy profiler during the round. #ALLOW_TRACY_START diff --git a/dependencies.sh b/dependencies.sh index fcf2c327004..7784b1b58cf 100644 --- a/dependencies.sh +++ b/dependencies.sh @@ -8,7 +8,7 @@ export BYOND_MAJOR=516 export BYOND_MINOR=1659 #rust_g git tag -export RUST_G_VERSION=3.11.0 +export RUST_G_VERSION=4.0.0 #node version export NODE_VERSION=14 diff --git a/tools/tgs4_scripts/PreCompile.sh b/tools/tgs4_scripts/PreCompile.sh index f21036ab191..50cb68e46bd 100644 --- a/tools/tgs4_scripts/PreCompile.sh +++ b/tools/tgs4_scripts/PreCompile.sh @@ -62,7 +62,7 @@ fi echo "Deploying rust-g..." git checkout "$RUST_G_VERSION" -env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo build --release --target=i686-unknown-linux-gnu +env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo build --release --target=i686-unknown-linux-gnu --features uuid mv target/i686-unknown-linux-gnu/release/librust_g.so "$1/librust_g.so" cd .. From d9fa1fd9b87306b1b46757d64294f70d7634e048 Mon Sep 17 00:00:00 2001 From: Flleeppyy Date: Mon, 27 Oct 2025 13:50:40 -0700 Subject: [PATCH 2/5] asdfas --- .../configuration/entries/general.dm | 2 -- code/modules/error_handler/glitchtip.dm | 17 +++++++++-------- config/example/config.txt | 1 - 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 7110b1e80ae..9fae7b93515 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -331,8 +331,6 @@ /datum/config_entry/string/glitchtip_environment config_entry_value = "production" -/datum/config_entry/flag/glitchtip_enabled - /*****************/ /* VOTES */ diff --git a/code/modules/error_handler/glitchtip.dm b/code/modules/error_handler/glitchtip.dm index 4160f53b322..592ed792e47 100644 --- a/code/modules/error_handler/glitchtip.dm +++ b/code/modules/error_handler/glitchtip.dm @@ -2,9 +2,10 @@ // Configuration options are in entries/general.dm /proc/send_to_glitchtip(exception/E, list/extra_data = null) - if(!CONFIG_GET(flag/glitchtip_enabled) || !CONFIG_GET(string/glitchtip_dsn)) + #ifndef SPACEMAN_DMM + #ifndef OPENDREAM + if(!CONFIG_GET(string/glitchtip_dsn)) return - var/glitchtip_dsn = CONFIG_GET(string/glitchtip_dsn) // parse DSN to get the key, host and project id @@ -12,11 +13,9 @@ var/dsn_clean = replacetext(replacetext(glitchtip_dsn, "http://", ""), "https://", "") var/at_pos = findtext(dsn_clean, "@") var/slash_pos = findtext(dsn_clean, "/", at_pos) - if(!at_pos || !slash_pos) log_runtime("Invalid Glitchtip DSN format") return - var/key = copytext(dsn_clean, 1, at_pos) var/host = copytext(dsn_clean, at_pos + 1, slash_pos) var/project_id = copytext(dsn_clean, slash_pos + 1) @@ -52,7 +51,7 @@ // Walk the call stack using callee objects var/frame_count = 0 - var/max_frames = 35 // Prevent infinite loops or excessive data. Realistically, this should not exceed 30~ + var/max_frames = 50 // Prevent infinite loops or excessive data for(var/callee/p = caller; p && frame_count < max_frames; p = p.caller) frame_count++ var/proc_name = "unknown" @@ -66,14 +65,14 @@ if(slash_pos_inner && slash_pos_inner < length(proc_name)) proc_name = copytext(proc_name, slash_pos_inner + 1) - if(findtext(file_name, "master.dm") && (proc_name == "Loop" || proc_name == "StartProcessing")) - break - // Get file and line information if available if(p.file) file_name = p.file line_num = p.line || 0 + if(findtext(file_name, "master.dm") && (proc_name == "Loop" || proc_name == "StartProcessing")) + break + var/list/frame = list() frame["filename"] = file_name frame["lineno"] = line_num @@ -221,6 +220,8 @@ event_data["fingerprint"] = list("[E.file]:[E.line]", E.name) send_glitchtip_request(event_data, host, project_id, key) + #endif + #endif /proc/send_glitchtip_request(list/event_data, host, project_id, key) var/glitchtip_url = "https://[host]/api/[project_id]/store/" diff --git a/config/example/config.txt b/config/example/config.txt index 46ba5573980..d8bf8ecc998 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -345,7 +345,6 @@ CONFIG_ERRORS_RUNTIME #GLITCHTIP_DSN # Environment this is running in, "production", "development" and "testing" are most common #GLITCHTIP_ENVIRONMENT development -#GLITCHTIP_ENABLED ## Uncomment to allow admins with +DEBUG to start the byond-tracy profiler during the round. #ALLOW_TRACY_START From bec3b512288f533d1bf9e1e0b31281a50d731e76 Mon Sep 17 00:00:00 2001 From: Flleeppyy Date: Mon, 27 Oct 2025 14:25:12 -0700 Subject: [PATCH 3/5] fixed --- code/datums/topic/admin.dm | 6 +++--- code/modules/admin/verbs/debug.dm | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/code/datums/topic/admin.dm b/code/datums/topic/admin.dm index 29853379de4..616d21b80a9 100644 --- a/code/datums/topic/admin.dm +++ b/code/datums/topic/admin.dm @@ -929,12 +929,12 @@ /datum/admin_topic/viewruntime/Run(list/input) var/datum/error_viewer/error_viewer = locate(input["viewruntime"]) if(!istype(error_viewer)) - to_chat(usr, span_warning("That runtime viewer no longer exists.")) + to_chat(usr.client, span_warning("That runtime viewer no longer exists.")) return if(input["viewruntime_backto"]) - error_viewer.show_to(usr, locate(input["viewruntime_backto"]), input["viewruntime_linear"]) + error_viewer.show_to(usr.client, locate(input["viewruntime_backto"]), input["viewruntime_linear"]) else - error_viewer.show_to(usr, null, input["viewruntime_linear"]) + error_viewer.show_to(usr.client, null, input["viewruntime_linear"]) /datum/admin_topic/admincaster diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 0c7e8115c01..386d6fcc268 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -390,7 +390,7 @@ set category = "Debug" set name = "View Runtimes" set desc = "Open the Runtime Viewer" - GLOB.error_cache.show_to(usr) + GLOB.error_cache.show_to(src) /client/proc/spawn_disciple() set category = "Debug" @@ -449,7 +449,7 @@ /proc/cmp_playtime_asc(client/a, client/b) return cmp_numeric_asc(a.get_exp_living(TRUE), b.get_exp_living(TRUE)) - + /client/proc/get(atom/movable/A) set category = "Debug" set name = "Get" From fdb5c1b610b78b5ba97fdb3b64531bcd781e9bc8 Mon Sep 17 00:00:00 2001 From: Flleeppyy Date: Tue, 16 Dec 2025 02:56:05 -0800 Subject: [PATCH 4/5] buhbubuh --- dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.sh b/dependencies.sh index 0863bcf0a52..90566cf322a 100644 --- a/dependencies.sh +++ b/dependencies.sh @@ -8,7 +8,7 @@ export BYOND_MAJOR=516 export BYOND_MINOR=1666 #rust_g git tag -export RUST_G_VERSION=4.0.0 +export RUST_G_VERSION=3.11.0 #node version export NODE_VERSION=14 From 286a4430e762c8ed37a4a426a3f4267ee4f56d4c Mon Sep 17 00:00:00 2001 From: Flleeppyy Date: Tue, 16 Dec 2025 03:55:47 -0800 Subject: [PATCH 5/5] bruh --- code/__DEFINES/rust_g.dm | 3 --- 1 file changed, 3 deletions(-) diff --git a/code/__DEFINES/rust_g.dm b/code/__DEFINES/rust_g.dm index 556db50d384..9471b05b564 100644 --- a/code/__DEFINES/rust_g.dm +++ b/code/__DEFINES/rust_g.dm @@ -435,6 +435,3 @@ #define url_encode(text) rustg_url_encode(text) #define url_decode(text) rustg_url_decode(text) #endif - -/// Generates a version 4 UUID. -#define rustg_generate_uuid_v4(...) RUSTG_CALL(RUST_G, "uuid_v4")()