Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add verbs for downloading debug/runtime logs #2298

Merged
merged 2 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -5712,6 +5712,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 @@ -5844,6 +5845,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 @@ -6539,6 +6541,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
Loading