Skip to content

Commit

Permalink
Add verbs for downloading debug/runtime logs (#2298)
Browse files Browse the repository at this point in the history
* Add verbs for downloading debug/runtime logs

* fix a runtime error
  • Loading branch information
Absolucy authored Jun 20, 2024
1 parent b219d26 commit bf4ff58
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 1 deletion.
2 changes: 2 additions & 0 deletions code/__HELPERS/files.dm
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
GLOBAL_VAR_INIT(fileaccess_timer, 0)

/* monkestation edit: replaced in [monkestation\code\__HELPERS\files.dm]
/client/proc/browse_files(root_type=BROWSE_ROOT_ALL_LOGS, max_iterations=10, list/valid_extensions=list("txt","log","htm", "html", "gz", "json"))
// wow why was this ever a parameter
var/root = "data/logs/"
Expand Down Expand Up @@ -51,6 +52,7 @@ GLOBAL_VAR_INIT(fileaccess_timer, 0)
return
return path
*/

#define FTPDELAY 200 //200 tick delay to discourage spam
#define ADMIN_FTPDELAY_MODIFIER 0.5 //Admins get to spam files faster since we ~trust~ them!
Expand Down
4 changes: 4 additions & 0 deletions code/modules/admin/admin_verbs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ GLOBAL_PROTECT(admin_verbs_debug)
/client/proc/validate_cards,
/client/proc/validate_puzzgrids,
/client/proc/view_runtimes,
// monkestation verbs: debugger tools
/client/proc/log_viewer_new,
/client/proc/getserverlogs_debug,
/client/proc/getcurrentlogs_debug,
)
GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release))
GLOBAL_PROTECT(admin_verbs_possess)
Expand Down
4 changes: 3 additions & 1 deletion code/modules/admin/verbs/getlogs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
set desc = "View/retrieve logfiles for the current round."
set category = "Admin.Logging"

browseserverlogs(current=TRUE)
browseserverlogs(current = TRUE)

/* monkestation edit: replaced in [monkestation\code\modules\admin\verbs\getlogs.dm]
/client/proc/browseserverlogs(current=FALSE)
var/path = browse_files(current ? BROWSE_ROOT_CURRENT_LOGS : BROWSE_ROOT_ALL_LOGS)
if(!path)
Expand All @@ -33,3 +34,4 @@
return
to_chat(src, "Attempting to send [path], this may take a fair few minutes if the file is very large.", confidential = TRUE)
return
*/
63 changes: 63 additions & 0 deletions monkestation/code/__HELPERS/files.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/client/proc/browse_files(root_type = BROWSE_ROOT_ALL_LOGS, max_iterations = 10, list/valid_extensions = list("txt", "log", "htm", "html", "gz", "json"), list/whitelist = null, allow_folder = TRUE)
var/regex/valid_ext_regex = new("\\.(?:[regex_quote_list(valid_extensions)])$", "i")
var/regex/whitelist_regex
if(whitelist)
// try not to look at it too hard. yes i wrote this by hand.
whitelist_regex = new("(?:\[\\/\\\\\]$|(?:^|\\\\|\\/)(?:[regex_quote_list(whitelist)])\\.(?:[regex_quote_list(valid_extensions)])$)", "i")

// wow why was this ever a parameter
var/root = "data/logs/"
switch(root_type)
if(BROWSE_ROOT_ALL_LOGS)
root = "data/logs/"
if(BROWSE_ROOT_CURRENT_LOGS)
root = "[GLOB.log_directory]/"
var/path = root

for(var/i in 1 to max_iterations)
var/list/choices
if(whitelist_regex)
choices = list()
for(var/listed_path in flist(path))
if(whitelist_regex.Find(listed_path))
choices += listed_path
else
choices = flist(path)
if(path != root)
choices.Insert(1, "/")
choices = sort_list(choices)
if(allow_folder)
choices += "Download Folder"

var/choice = tgui_input_list(src, "Choose a file to access", "Download", choices)
if(!choice)
return
switch(choice)
if("/")
path = root
continue
if("Download Folder")
if(!allow_folder)
return
var/list/comp_flist = flist(path)
var/confirmation = input(src, "Are you SURE you want to download all the files in this folder? (This will open [length(comp_flist)] prompt[length(comp_flist) == 1 ? "" : "s"])", "Confirmation") in list("Yes", "No")
if(confirmation != "Yes")
continue
for(var/file in comp_flist)
src << ftp(path + file)
return
path += choice

if(copytext_char(path, -1) != "/") //didn't choose a directory, no need to iterate again
break
if(!fexists(path) || !valid_ext_regex.Find(path))
to_chat(src, "<font color='red'>Error: browse_files(): File not found/Invalid file([path]).</font>")
return

return path

/proc/regex_quote_list(list/input) as text
var/list/sanitized = list()
for(var/thingy in input)
sanitized += REGEX_QUOTE(thingy)
return jointext(sanitized, "|")
53 changes: 53 additions & 0 deletions monkestation/code/modules/admin/verbs/getlogs.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
GLOBAL_LIST(debug_logfile_names)
GLOBAL_PROTECT(debug_logfile_names)

/client/proc/getserverlogs_debug()
set name = "Get Server Logs (Debug)"
set desc = "View/retrieve debug-related logfiles."
set category = "Debug"
if(!check_rights_for(src, R_DEBUG))
return
get_debug_logfiles()
if(!GLOB.debug_logfile_names)
return
browseserverlogs(whitelist = GLOB.debug_logfile_names, allow_folder = FALSE)

/client/proc/getcurrentlogs_debug()
set name = "Get Current Logs (Debug)"
set desc = "View/retrieve debug-related logfiles for the current round."
set category = "Debug"
if(!check_rights_for(src, R_DEBUG))
return
get_debug_logfiles()
if(!GLOB.debug_logfile_names)
return
browseserverlogs(current = TRUE, whitelist = GLOB.debug_logfile_names, allow_folder = FALSE)

/client/proc/browseserverlogs(current = FALSE, list/whitelist = null, allow_folder = TRUE)
var/path = browse_files(current ? BROWSE_ROOT_CURRENT_LOGS : BROWSE_ROOT_ALL_LOGS, whitelist = whitelist, allow_folder = allow_folder)
if(!path || !fexists(path))
return

if(file_spam_check())
return

message_admins("[key_name_admin(src)] accessed file: [path]")
switch(tgui_alert(usr, "View (in game), Open (in your system's text editor), or Download?", path, list("View", "Open", "Download")))
if ("View")
src << browse("<pre style='word-wrap: break-word;'>[html_encode(file2text(file(path)))]</pre>", list2params(list("window" = "viewfile.[path]")))
if ("Open")
src << run(file(path))
if ("Download")
src << ftp(file(path))
else
return
to_chat(src, span_boldnotice("Attempting to send [path], this may take a fair few minutes if the file is very large."), confidential = TRUE)
return

/proc/get_debug_logfiles()
if(!logger.initialized || GLOB.debug_logfile_names)
return
for(var/datum/log_category/category as anything in logger.log_categories)
category = logger.log_categories[category]
if(is_category_debug_visible(category))
LAZYOR(GLOB.debug_logfile_names, get_category_logfile(category))
49 changes: 49 additions & 0 deletions monkestation/code/modules/logging/log_category.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/datum/log_category
/// If non-admin debuggers (+DEBUG without +ADMIN) can see logs from this category or not.
/// When set to null, it will assume the value of the parent category (which will default to FALSE if not set).
var/debugger_visible = null

/datum/log_category/debug
debugger_visible = TRUE

/datum/log_category/debug_sql
debugger_visible = FALSE

/datum/log_category/debug_href
debugger_visible = FALSE

/datum/log_category/debug_runtime
debugger_visible = TRUE

/// Recursively checks to see if a log category or any of its parent categories is marked as "debugger_visible" or not.
/// Caches the result.
/proc/is_category_debug_visible(datum/log_category/category)
var/static/list/cached_visibility
if(!cached_visibility)
cached_visibility = list(/datum/log_category = FALSE)
var/datum/log_category/category_path = ispath(category) ? category : (istext(category) ? logger.log_categories[category]?.type : category?.type)
if(!category)
return FALSE
if(!isnull(cached_visibility[category_path]))
. = cached_visibility[category_path]
else if(!isnull(category_path::debugger_visible))
. = cached_visibility[category_path] = category_path::debugger_visible
else if(!isnull(category_path::master_category) && category_path::master_category != category_path) // safety check to prevent infinite recursion, prolly not needed but better safe than sorry
. = cached_visibility[category_path] = is_category_debug_visible(category_path::master_category)
else
. = cached_visibility[category_path] = FALSE

/proc/get_category_logfile(datum/log_category/category)
var/static/list/cached_filenames
if(!cached_filenames)
cached_filenames = list()
if(!category)
return FALSE
var/datum/log_category/category_path = ispath(category) ? category : (istext(category) ? logger.log_categories[category].type : category.type)
if(!isnull(cached_filenames[category_path]))
. = cached_filenames[category_path]
else if(!isnull(category_path::master_category))
var/datum/log_category/master_category = category_path::master_category
. = cached_filenames[master_category] = cached_filenames[category_path] = master_category::category
else
. = cached_filenames[category_path] = category_path::category
85 changes: 85 additions & 0 deletions monkestation/code/modules/logging/log_holder.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/datum/log_holder
/// Cached ui_data for debuggers
var/list/debug_data_cache = list()

/datum/log_holder/ui_interact(mob/user, datum/tgui/ui)
if(!check_rights_for(user.client, R_ADMIN | R_DEBUG))
return

ui = SStgui.try_update_ui(user, src, ui)
if(isnull(ui))
ui = new(user, src, "LogViewer")
ui.set_autoupdate(FALSE)
ui.open()

/datum/log_holder/ui_status(mob/user, datum/ui_state/state)
return check_rights_for(user.client, R_ADMIN | R_DEBUG) ? UI_INTERACTIVE : UI_CLOSE

/datum/log_holder/ui_static_data(mob/user)
var/list/data = list(
"round_id" = GLOB.round_id,
"logging_start_timestamp" = logging_start_timestamp,
)
var/debug_user = is_user_debug_only(user)

var/list/tree = list()
data["tree"] = tree
var/list/enabled_categories = list()
for(var/enabled in log_categories)
if(debug_user && !is_category_debug_visible(log_categories[enabled]))
continue
enabled_categories += enabled
tree["enabled"] = enabled_categories

var/list/disabled_categories = list()
for(var/disabled in src.disabled_categories)
if(debug_user && !is_category_debug_visible(disabled))
continue
disabled_categories += disabled
tree["disabled"] = disabled_categories

return data

/datum/log_holder/ui_data(mob/user)
if(!last_data_update || (world.time - last_data_update) > LOG_UPDATE_TIMEOUT)
cache_ui_data()
return is_user_debug_only(user) ? debug_data_cache : data_cache

/datum/log_holder/cache_ui_data()
var/list/category_map = list()
var/list/debug_category_map = list()
for(var/datum/log_category/category as anything in log_categories)
category = log_categories[category]
var/list/category_data = list()

var/list/entries = list()
for(var/datum/log_entry/entry as anything in category.entries)
entries += list(list(
"id" = entry.id,
"message" = entry.message,
"timestamp" = entry.timestamp,
"data" = entry.data,
"semver" = entry.semver_store,
))
category_data["entries"] = entries
category_data["entry_count"] = category.entry_count

category_map[category.category] = category_data
if(is_category_debug_visible(category))
debug_category_map[category.category] = category_data

data_cache.Cut()
debug_data_cache.Cut()
last_data_update = world.time

data_cache["categories"] = category_map
data_cache["last_data_update"] = last_data_update

debug_data_cache["categories"] = debug_category_map
debug_data_cache["last_data_update"] = last_data_update

/// Checks to see if a user has +DEBUG without +ADMIN permissions.
/// Used to give +DEBUG holders "limited" versions of some admin commands for debugging purposes.
/proc/is_user_debug_only(mob/user)
var/client/client = user.client
return client.holder && check_rights_for(client, R_DEBUG) && !check_rights_for(client, R_ADMIN)
4 changes: 4 additions & 0 deletions tgstation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -5713,6 +5713,7 @@
#include "monkestation\code\__DEFINES\projectile.dm"
#include "monkestation\code\__HELPERS\_lists.dm"
#include "monkestation\code\__HELPERS\anime.dm"
#include "monkestation\code\__HELPERS\files.dm"
#include "monkestation\code\__HELPERS\reagents.dm"
#include "monkestation\code\__HELPERS\turfs.dm"
#include "monkestation\code\_onclick\hud\alert.dm"
Expand Down Expand Up @@ -5848,6 +5849,7 @@
#include "monkestation\code\modules\admin\smites\dagothstripsmite.dm"
#include "monkestation\code\modules\admin\smites\phobia_christian_minecraft.dm"
#include "monkestation\code\modules\admin\smites\where_are_your_fingers.dm"
#include "monkestation\code\modules\admin\verbs\getlogs.dm"
#include "monkestation\code\modules\admin\verbs\kick_player_by_ckey.dm"
#include "monkestation\code\modules\aesthetics\airlock\airlock.dm"
#include "monkestation\code\modules\aesthetics\items\clothing.dm"
Expand Down Expand Up @@ -6543,6 +6545,8 @@
#include "monkestation\code\modules\loadouts\items\under\under.dm"
#include "monkestation\code\modules\loafing\code\loaf.dm"
#include "monkestation\code\modules\loafing\code\loafer.dm"
#include "monkestation\code\modules\logging\log_category.dm"
#include "monkestation\code\modules\logging\log_holder.dm"
#include "monkestation\code\modules\logging\categories\log_category_game.dm"
#include "monkestation\code\modules\mapping\access_helpers.dm"
#include "monkestation\code\modules\mapping\mapping_helpers.dm"
Expand Down

0 comments on commit bf4ff58

Please sign in to comment.