From fbec9c14e99e08027820cb3d42cff28d830eb782 Mon Sep 17 00:00:00 2001 From: Zephyr <12817816+ZephyrTFA@users.noreply.github.com> Date: Sun, 21 May 2023 22:51:00 -0400 Subject: [PATCH] JSON Logging Take Two (#73604) ## About The Pull Request Converts all logging, excluding perf and investigate, to json. I focused on making the system as easy to use and as easy to add new categories as possible. Due to issues related to logging to world at global creation logger is now a byond real, which is created directly before Master Log categories support versioning, secret flagging, and sub-category filtering. Although all of this is entirely optional for coders. If you ever want to add a new category and use it, all you need to do is make the barebones category datum and the define. I've kept existing procs such as log_game, and simply turned them into a wrapper for Logger.Log(xxx, ...) ## Why It's Good For The Game Makes processing and filtering logs much easier in the future, while only minimally downgrading log crawling experience. I am also working on a log viewer frontend for admin usage however that will take a little bit longer to finish up. Also makes special logging and data tracking much easier thanks to a data list processing implementation and handling ## Changelog :cl: server: All logs are now formatted in json, excluding perf and investigations /:cl: --------- Signed-off-by: GitHub Co-authored-by: tattle <66640614+dragomagol@users.noreply.github.com> Co-authored-by: Kyle Spier-Swenson Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> --- code/__DEFINES/_protect.dm | 8 +- code/__DEFINES/logging.dm | 97 +++++++- code/__HELPERS/files.dm | 2 +- code/__HELPERS/logging/_logging.dm | 27 ++- code/__HELPERS/logging/admin.dm | 38 +-- code/__HELPERS/logging/antagonists.dm | 30 +-- code/__HELPERS/logging/attack.dm | 5 +- code/__HELPERS/logging/debug.dm | 52 ++-- code/__HELPERS/logging/dynamic.dm | 4 +- code/__HELPERS/logging/economy.dm | 5 +- code/__HELPERS/logging/game.dm | 40 ++-- code/__HELPERS/logging/manifest.dm | 5 +- code/__HELPERS/logging/mecha.dm | 5 +- code/__HELPERS/logging/mob.dm | 9 +- code/__HELPERS/logging/paper.dm | 4 +- code/__HELPERS/logging/pda.dm | 17 +- code/__HELPERS/logging/shuttle.dm | 5 +- code/__HELPERS/logging/talk.dm | 20 +- code/__HELPERS/logging/tool.dm | 3 +- code/__HELPERS/logging/ui.dm | 6 +- code/__HELPERS/logging/virus.dm | 5 +- code/__HELPERS/text.dm | 12 + code/_globalvars/logging.dm | 37 +-- .../configuration/entries/general.dm | 4 + code/controllers/subsystem/dbcore.dm | 16 +- code/datums/datum.dm | 22 +- code/datums/json_savefile.dm | 4 +- code/datums/martial/_martial.dm | 10 + code/datums/memory/_memory.dm | 16 ++ code/datums/mind/_mind.dm | 19 ++ code/game/world.dm | 13 +- code/modules/admin/topic.dm | 8 +- .../logging/categories/log_category_admin.dm | 26 ++ .../categories/log_category_compats.dm | 7 + .../logging/categories/log_category_debug.dm | 45 ++++ .../logging/categories/log_category_game.dm | 58 +++++ .../logging/categories/log_category_misc.dm | 58 +++++ .../logging/categories/log_category_pda.dm | 11 + .../logging/categories/log_category_uplink.dm | 23 ++ code/modules/logging/log_category.dm | 54 +++-- code/modules/logging/log_entry.dm | 114 +++++++++ code/modules/logging/log_holder.dm | 222 ++++++++++++++++-- code/modules/mining/machine_silo.dm | 2 +- code/modules/mob/mob.dm | 11 + code/modules/mob/mob_helpers.dm | 2 +- code/modules/photography/_pictures.dm | 18 +- config/config.txt | 98 +------- config/logging.txt | 103 ++++++++ tgstation.dme | 8 + 49 files changed, 1055 insertions(+), 353 deletions(-) create mode 100644 code/modules/logging/categories/log_category_admin.dm create mode 100644 code/modules/logging/categories/log_category_compats.dm create mode 100644 code/modules/logging/categories/log_category_debug.dm create mode 100644 code/modules/logging/categories/log_category_game.dm create mode 100644 code/modules/logging/categories/log_category_misc.dm create mode 100644 code/modules/logging/categories/log_category_pda.dm create mode 100644 code/modules/logging/categories/log_category_uplink.dm create mode 100644 code/modules/logging/log_entry.dm create mode 100644 config/logging.txt diff --git a/code/__DEFINES/_protect.dm b/code/__DEFINES/_protect.dm index dbe25203c0b..22fad2f6385 100644 --- a/code/__DEFINES/_protect.dm +++ b/code/__DEFINES/_protect.dm @@ -1,4 +1,4 @@ -///Protects a datum from being VV'd +///Protects a datum from being VV'd or spawned through admin manipulation #define GENERAL_PROTECT_DATUM(Path)\ ##Path/can_vv_get(var_name){\ return FALSE;\ @@ -8,4 +8,10 @@ }\ ##Path/CanProcCall(procname){\ return FALSE;\ +}\ +##Path/Read(savefile/savefile){\ + qdel(src);\ +}\ +##Path/Write(savefile/savefile){\ + return;\ } diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm index d086fa2ae62..9cfe197c0ca 100644 --- a/code/__DEFINES/logging.dm +++ b/code/__DEFINES/logging.dm @@ -55,24 +55,95 @@ #define LOGSRC_MOB "Mob" // Log header keys -#define LOG_HEADER_CATEGORY "category" -#define LOG_HEADER_INIT_TIMESTAMP "timestamp" -#define LOG_HEADER_ROUND_ID "round_id" - -// Log data keys -#define LOG_ENTRY_MESSAGE "message" -#define LOG_ENTRY_TIMESTAMP "timestamp" -#define LOG_ENTRY_DATA "data" +#define LOG_HEADER_CATEGORY "cat" +#define LOG_HEADER_CATEGORY_LIST "cat-list" +#define LOG_HEADER_INIT_TIMESTAMP "ts" +#define LOG_HEADER_ROUND_ID "round-id" +#define LOG_HEADER_SECRET "secret" // Log json keys -#define LOG_JSON_CATEGORY "category" +#define LOG_JSON_CATEGORY "cat" #define LOG_JSON_ENTRIES "entries" -#define LOG_JSON_LOGGING_START "log_start" +#define LOG_JSON_LOGGING_START "log-start" -// Log categories -#define LOG_CATEGORY_NOT_FOUND "invalid_category" -#define LOG_CATEGORY_TARGET_ZONE_SWITCH "target_zone_switch" +// Category for invalid/missing categories +#define LOG_CATEGORY_NOT_FOUND "invalid-category" + +// Misc categories +#define LOG_CATEGORY_ATTACK "attack" +#define LOG_CATEGORY_CONFIG "config" +#define LOG_CATEGORY_DYNAMIC "dynamic" +#define LOG_CATEGORY_ECONOMY "economy" +#define LOG_CATEGORY_FILTER "filter" +#define LOG_CATEGORY_MANIFEST "manifest" +#define LOG_CATEGORY_MECHA "mecha" +#define LOG_CATEGORY_PAPER "paper" +#define LOG_CATEGORY_RUNTIME "runtime" +#define LOG_CATEGORY_SHUTTLE "shuttle" +#define LOG_CATEGORY_SILICON "silicon" #define LOG_CATEGORY_SILO "silo" +#define LOG_CATEGORY_SIGNAL "signal" +#define LOG_CATEGORY_SPEECH_INDICATOR "speech-indiciator" +#define LOG_CATEGORY_SUSPICIOUS_LOGIN "supicious-login" +#define LOG_CATEGORY_TARGET_ZONE_SWITCH "target-zone-switch" +#define LOG_CATEGORY_TELECOMMS "telecomms" +#define LOG_CATEGORY_TOOL "tool" +#define LOG_CATEGORY_VIRUS "virus" + +// Admin categories +#define LOG_CATEGORY_ADMIN "admin" +#define LOG_CATEGORY_ADMIN_CIRCUIT "admin-circuit" +#define LOG_CATEGORY_ADMIN_DSAY "admin-dsay" + +// Admin private categories +#define LOG_CATEGORY_ADMIN_PRIVATE "adminprivate" +#define LOG_CATEGORY_ADMIN_PRIVATE_ASAY "adminprivate-asay" + +// Debug categories +#define LOG_CATEGORY_DEBUG "debug" +#define LOG_CATEGORY_DEBUG_ASSET "debug-asset" +#define LOG_CATEGORY_DEBUG_HREF "debug-href" +#define LOG_CATEGORY_DEBUG_JOB "debug-job" +#define LOG_CATEGORY_DEBUG_LUA "debug-lua" +#define LOG_CATEGORY_DEBUG_MAPPING "debug-mapping" +#define LOG_CATEGORY_DEBUG_MOBTAG "debug-mobtag" +#define LOG_CATEGORY_DEBUG_QDEL "debug-qdel" +#define LOG_CATEGORY_DEBUG_SQL "debug-sql" +#define LOG_CATEGORY_DEBUG_TGUI "debug-tgui" + +// Compatibility categories, for when stuff is changed and you need existing functionality to work +#define LOG_CATEGORY_COMPAT_GAME "game-compat" + +// Game categories +#define LOG_CATEGORY_GAME "game" +#define LOG_CATEGORY_GAME_ACCESS "game-access" +#define LOG_CATEGORY_GAME_EMOTE "game-emote" +#define LOG_CATEGORY_GAME_INTERNET_REQUEST "game-internet-request" +#define LOG_CATEGORY_GAME_OOC "game-ooc" +#define LOG_CATEGORY_GAME_PRAYER "game-prayer" +#define LOG_CATEGORY_GAME_RADIO_EMOTE "game-radio-emote" +#define LOG_CATEGORY_GAME_SAY "game-say" +#define LOG_CATEGORY_GAME_TOPIC "game-topic" +#define LOG_CATEGORY_GAME_TRAITOR "game-traitor" +#define LOG_CATEGORY_GAME_VOTE "game-vote" +#define LOG_CATEGORY_GAME_WHISPER "game-whisper" + +// Uplink categories +#define LOG_CATEGORY_UPLINK "uplink" +#define LOG_CATEGORY_UPLINK_CHANGELING "uplink-changeling" +#define LOG_CATEGORY_UPLINK_HERETIC "uplink-heretic" +#define LOG_CATEGORY_UPLINK_MALF "uplink-malf" +#define LOG_CATEGORY_UPLINK_SPELL "uplink-spell" + +// PDA categories +#define LOG_CATEGORY_PDA "pda" +#define LOG_CATEGORY_PDA_CHAT "pda-chat" +#define LOG_CATEGORY_PDA_COMMENT "pda-comment" + +#define SCHEMA_VERSION "schema-version" + +// Default log schema version +#define LOG_CATEGORY_SCHEMA_VERSION_NOT_SET "0.0.1" //wrapper macros for easier grepping #define DIRECT_OUTPUT(A, B) A << B diff --git a/code/__HELPERS/files.dm b/code/__HELPERS/files.dm index 12ae584db5f..c773b8e9b76 100644 --- a/code/__HELPERS/files.dm +++ b/code/__HELPERS/files.dm @@ -5,7 +5,7 @@ */ GLOBAL_VAR_INIT(fileaccess_timer, 0) -/client/proc/browse_files(root_type=BROWSE_ROOT_ALL_LOGS, max_iterations=10, list/valid_extensions=list("txt","log","htm", "html", "gz")) +/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/" switch(root_type) diff --git a/code/__HELPERS/logging/_logging.dm b/code/__HELPERS/logging/_logging.dm index c1e65aad4d2..6e2f7cd0773 100644 --- a/code/__HELPERS/logging/_logging.dm +++ b/code/__HELPERS/logging/_logging.dm @@ -10,6 +10,31 @@ msg = "## NOTICE: [msg]" log_world(msg) +#define SET_SERIALIZATION_SEMVER(semver_list, semver) semver_list[type] = semver +#define CHECK_SERIALIZATION_SEMVER(wanted, actual) (__check_serialization_semver(wanted, actual)) + +/// Checks if the actual semver is equal or later than the wanted semver +/// Must be passed as TEXT; you're probably looking for CHECK_SERIALIZATION_SEMVER, look right above +/proc/__check_serialization_semver(wanted, actual) + if(wanted == actual) + return TRUE + + var/list/wanted_versions = semver_to_list(wanted) + var/list/actual_versions = semver_to_list(actual) + + if(!wanted_versions || !actual_versions) + stack_trace("Invalid semver string(s) passed to __check_serialization_semver: '[wanted]' and '[actual]'") + return FALSE + + if(wanted_versions[1] != actual_versions[1]) + return FALSE // major must always + + if(wanted_versions[2] > actual_versions[2]) + return FALSE // actual must be later than wanted + + // patch is not checked + return TRUE + //print a testing-mode debug message to world.log and world #ifdef TESTING #define testing(msg) log_world("## TESTING: [msg]"); to_chat(world, "## TESTING: [msg]") @@ -128,7 +153,7 @@ GLOBAL_LIST_INIT(testing_global_profiler, list("_PROFILE_NAME" = "Global")) /* Close open log handles. This should be called as late as possible, and no logging should hapen after. */ /proc/shutdown_logging() rustg_log_close_all() - GLOB.logger.shutdown_logging() + logger.shutdown_logging() /* Helper procs for building detailed log lines */ /proc/key_name(whom, include_link = null, include_name = TRUE) diff --git a/code/__HELPERS/logging/admin.dm b/code/__HELPERS/logging/admin.dm index 6c659e4ea01..91f774c5346 100644 --- a/code/__HELPERS/logging/admin.dm +++ b/code/__HELPERS/logging/admin.dm @@ -1,33 +1,36 @@ /* Items with ADMINPRIVATE prefixed are stripped from public logs. */ +// For backwards compatibility these are currently also added to LOG_CATEGORY_GAME with their respective prefix +// This is to avoid breaking any existing tools that rely on the old format, but should be removed in the future + /// General logging for admin actions -/proc/log_admin(text) +/proc/log_admin(text, list/data) GLOB.admin_activities.Add(text) - if (CONFIG_GET(flag/log_admin)) - WRITE_LOG(GLOB.world_game_log, "ADMIN: [text]") + logger.Log(LOG_CATEGORY_ADMIN, text, data) + logger.Log(LOG_CATEGORY_COMPAT_GAME, "ADMIN: [text]") /// Logging for admin actions on or with circuits -/proc/log_admin_circuit(text) +/proc/log_admin_circuit(text, list/data) GLOB.admin_activities.Add(text) - if(CONFIG_GET(flag/log_admin)) - WRITE_LOG(GLOB.world_game_log, "ADMIN: CIRCUIT: [text]") + logger.Log(LOG_CATEGORY_ADMIN_CIRCUIT, text, data) + logger.Log(LOG_CATEGORY_COMPAT_GAME, "ADMIN: CIRCUIT: [text]") /// General logging for admin actions -/proc/log_admin_private(text) +/proc/log_admin_private(text, list/data) GLOB.admin_activities.Add(text) - if (CONFIG_GET(flag/log_admin)) - WRITE_LOG(GLOB.world_game_log, "ADMINPRIVATE: [text]") + logger.Log(LOG_CATEGORY_ADMIN_PRIVATE, text, data) + logger.Log(LOG_CATEGORY_COMPAT_GAME, "ADMINPRIVATE: [text]") /// Logging for AdminSay (ASAY) messages -/proc/log_adminsay(text) +/proc/log_adminsay(text, list/data) GLOB.admin_activities.Add(text) - if (CONFIG_GET(flag/log_adminchat)) - WRITE_LOG(GLOB.world_game_log, "ADMINPRIVATE: ASAY: [text]") + logger.Log(LOG_CATEGORY_ADMIN_PRIVATE_ASAY, text, data) + logger.Log(LOG_CATEGORY_COMPAT_GAME, "ADMINPRIVATE: ASAY: [text]") /// Logging for DeachatSay (DSAY) messages -/proc/log_dsay(text) - if (CONFIG_GET(flag/log_adminchat)) - WRITE_LOG(GLOB.world_game_log, "ADMIN: DSAY: [text]") +/proc/log_dsay(text, list/data) + logger.Log(LOG_CATEGORY_ADMIN_DSAY, text, data) + logger.Log(LOG_CATEGORY_COMPAT_GAME, "ADMIN: DSAY: [text]") /** * Writes to a special log file if the log_suspicious_login config flag is set, @@ -36,8 +39,7 @@ * Mirrors this log entry to log_access when access_log_mirror is TRUE, so this proc * doesn't need to be used alongside log_access and can replace it where appropriate. */ -/proc/log_suspicious_login(text, access_log_mirror = TRUE) - if (CONFIG_GET(flag/log_suspicious_login)) - WRITE_LOG(GLOB.world_suspicious_login_log, "SUSPICIOUS_ACCESS: [text]") +/proc/log_suspicious_login(text, list/data, access_log_mirror = TRUE) + logger.Log(LOG_CATEGORY_SUSPICIOUS_LOGIN, text) if(access_log_mirror) log_access(text) diff --git a/code/__HELPERS/logging/antagonists.dm b/code/__HELPERS/logging/antagonists.dm index 38f88b3b4d6..3d06bb325ec 100644 --- a/code/__HELPERS/logging/antagonists.dm +++ b/code/__HELPERS/logging/antagonists.dm @@ -1,29 +1,23 @@ /// Logging for traitor objectives -/proc/log_traitor(text) - if (CONFIG_GET(flag/log_traitor)) - WRITE_LOG(GLOB.world_game_log, "TRAITOR: [text]") +/proc/log_traitor(text, list/data) + logger.Log(LOG_CATEGORY_GAME_TRAITOR, text, data) /// Logging for items purchased from a traitor uplink -/proc/log_uplink(text) - if (CONFIG_GET(flag/log_uplink)) - WRITE_LOG(GLOB.world_uplink_log, "UPLINK: [text]") +/proc/log_uplink(text, list/data) + logger.Log(LOG_CATEGORY_UPLINK, text, data) /// Logging for upgrades purchased by a malfunctioning (or combat upgraded) AI -/proc/log_malf_upgrades(text) - if (CONFIG_GET(flag/log_uplink)) - WRITE_LOG(GLOB.world_uplink_log, "MALF UPGRADE: [text]") +/proc/log_malf_upgrades(text, list/data) + logger.Log(LOG_CATEGORY_UPLINK_MALF, text, data) /// Logging for changeling powers purchased -/proc/log_changeling_power(text) - if (CONFIG_GET(flag/log_uplink)) - WRITE_LOG(GLOB.world_uplink_log, "CHANGELING: [text]") +/proc/log_changeling_power(text, list/data) + logger.Log(LOG_CATEGORY_UPLINK_CHANGELING, text, data) /// Logging for heretic powers learned -/proc/log_heretic_knowledge(text) - if (CONFIG_GET(flag/log_uplink)) - WRITE_LOG(GLOB.world_uplink_log, "HERETIC RESEARCH: [text]") +/proc/log_heretic_knowledge(text, list/data) + logger.Log(LOG_CATEGORY_UPLINK_HERETIC, text, data) /// Logging for wizard powers learned -/proc/log_spellbook(text) - if (CONFIG_GET(flag/log_uplink)) - WRITE_LOG(GLOB.world_uplink_log, "SPELLBOOK: [text]") +/proc/log_spellbook(text, list/data) + logger.Log(LOG_CATEGORY_UPLINK_SPELL, text, data) diff --git a/code/__HELPERS/logging/attack.dm b/code/__HELPERS/logging/attack.dm index e804c07e9f0..598882a9ac1 100644 --- a/code/__HELPERS/logging/attack.dm +++ b/code/__HELPERS/logging/attack.dm @@ -1,7 +1,6 @@ /// Generic attack logging -/proc/log_attack(text) - if (CONFIG_GET(flag/log_attack)) - WRITE_LOG(GLOB.world_attack_log, "ATTACK: [text]") +/proc/log_attack(text, list/data) + logger.Log(LOG_CATEGORY_ATTACK, text) /** * Log a combat message in the attack log diff --git a/code/__HELPERS/logging/debug.dm b/code/__HELPERS/logging/debug.dm index e7ba69b1eb1..61b686811c8 100644 --- a/code/__HELPERS/logging/debug.dm +++ b/code/__HELPERS/logging/debug.dm @@ -1,32 +1,30 @@ /// Logging for loading and caching assets -/proc/log_asset(text) - if(CONFIG_GET(flag/log_asset)) - WRITE_LOG(GLOB.world_asset_log, "ASSET: [text]") +/proc/log_asset(text, list/data) + logger.Log(LOG_CATEGORY_DEBUG_ASSET, text, data) /// Logging for config errors /// Rarely gets called; just here in case the config breaks. -/proc/log_config(text) - WRITE_LOG(GLOB.config_error_log, text) +/proc/log_config(text, list/data) + logger.Log(LOG_CATEGORY_CONFIG, text, data) SEND_TEXT(world.log, text) -/proc/log_filter_raw(text) - WRITE_LOG(GLOB.filter_log, "FILTER: [text]") +/proc/log_filter_raw(text, list/data) + logger.Log(LOG_CATEGORY_FILTER, text, data) /// Logging for job slot changes -/proc/log_job_debug(text) - if (CONFIG_GET(flag/log_job_debug)) - WRITE_LOG(GLOB.world_job_debug_log, "JOB: [text]") +/proc/log_job_debug(text, list/data) + logger.Log(LOG_CATEGORY_DEBUG_JOB, text, data) /// Logging for lua scripting -/proc/log_lua(text) - WRITE_LOG(GLOB.lua_log, text) +/proc/log_lua(text, list/data) + logger.Log(LOG_CATEGORY_DEBUG_LUA, text, data) /// Logging for mapping errors /proc/log_mapping(text, skip_world_log) #ifdef UNIT_TESTS GLOB.unit_test_mapping_logs += text #endif - WRITE_LOG(GLOB.world_map_error_log, text) + logger.Log(LOG_CATEGORY_DEBUG_MAPPING, text) if(skip_world_log) return SEND_TEXT(world.log, text) @@ -37,31 +35,27 @@ WRITE_LOG_NO_FORMAT(GLOB.perf_log, .) /// Logging for hard deletes -/proc/log_qdel(text) - WRITE_LOG(GLOB.world_qdel_log, "QDEL: [text]") - -/// Logging for SQL errors -/proc/log_query_debug(text) - WRITE_LOG(GLOB.query_debug_log, "SQL: [text]") +/proc/log_qdel(text, list/data) + logger.Log(LOG_CATEGORY_DEBUG_QDEL, text, data) /* Log to the logfile only. */ -/proc/log_runtime(text) - WRITE_LOG(GLOB.world_runtime_log, text) +/proc/log_runtime(text, list/data) + logger.Log(LOG_CATEGORY_RUNTIME, text, data) -/proc/log_signal(text) - WRITE_LOG(GLOB.signals_log, text) +/proc/log_signal(text, list/data) + logger.Log(LOG_CATEGORY_SIGNAL, text, data) /// Logging for DB errors -/proc/log_sql(text) - WRITE_LOG(GLOB.sql_error_log, "SQL: [text]") +/proc/log_sql(text, list/data) + logger.Log(LOG_CATEGORY_DEBUG_SQL, text, data) /// Logging for world/Topic -/proc/log_topic(text) - WRITE_LOG(GLOB.world_game_log, "TOPIC: [text]") +/proc/log_topic(text, list/data) + logger.Log(LOG_CATEGORY_GAME_TOPIC, text, data) /// Log to both DD and the logfile. -/proc/log_world(text) +/proc/log_world(text, list/data) #ifdef USE_CUSTOM_ERROR_HANDLER - WRITE_LOG(GLOB.world_runtime_log, text) + logger.Log(LOG_CATEGORY_RUNTIME, text, data) #endif SEND_TEXT(world.log, text) diff --git a/code/__HELPERS/logging/dynamic.dm b/code/__HELPERS/logging/dynamic.dm index ff1f4fd1f7a..51bba86ca05 100644 --- a/code/__HELPERS/logging/dynamic.dm +++ b/code/__HELPERS/logging/dynamic.dm @@ -4,5 +4,5 @@ log_dynamic("[text]") /// Logging for dynamic procs -/proc/log_dynamic(text) - WRITE_LOG(GLOB.dynamic_log, "DYNAMIC: [text]") +/proc/log_dynamic(text, list/data) + logger.Log(LOG_CATEGORY_DYNAMIC, text, data) diff --git a/code/__HELPERS/logging/economy.dm b/code/__HELPERS/logging/economy.dm index 8f190cb934c..59fb0eee3ed 100644 --- a/code/__HELPERS/logging/economy.dm +++ b/code/__HELPERS/logging/economy.dm @@ -1,3 +1,2 @@ -/proc/log_econ(text) - if (CONFIG_GET(flag/log_econ)) - WRITE_LOG(GLOB.world_econ_log, "MONEY: [text]") +/proc/log_econ(text, list/data) + logger.Log(LOG_CATEGORY_ECONOMY, text, data) diff --git a/code/__HELPERS/logging/game.dm b/code/__HELPERS/logging/game.dm index 40fd458d1a9..fd0fb533d31 100644 --- a/code/__HELPERS/logging/game.dm +++ b/code/__HELPERS/logging/game.dm @@ -1,40 +1,32 @@ /// Logging for generic/unsorted game messages -/proc/log_game(text) - if (CONFIG_GET(flag/log_game)) - WRITE_LOG(GLOB.world_game_log, "GAME: [text]") +/proc/log_game(text, list/data) + logger.Log(LOG_CATEGORY_GAME, text, data) /// Logging for emotes -/proc/log_emote(text) - if (CONFIG_GET(flag/log_emote)) - WRITE_LOG(GLOB.world_game_log, "EMOTE: [text]") +/proc/log_emote(text, list/data) + logger.Log(LOG_CATEGORY_GAME_EMOTE, text, data) /// Logging for emotes sent over the radio -/proc/log_radio_emote(text) - if (CONFIG_GET(flag/log_emote)) - WRITE_LOG(GLOB.world_game_log, "RADIOEMOTE: [text]") +/proc/log_radio_emote(text, list/data) + logger.Log(LOG_CATEGORY_GAME_RADIO_EMOTE, text, data) /// Logging for messages sent in OOC -/proc/log_ooc(text) - if (CONFIG_GET(flag/log_ooc)) - WRITE_LOG(GLOB.world_game_log, "OOC: [text]") +/proc/log_ooc(text, list/data) + logger.Log(LOG_CATEGORY_GAME_OOC, text, data) /// Logging for prayed messages -/proc/log_prayer(text) - if (CONFIG_GET(flag/log_prayer)) - WRITE_LOG(GLOB.world_game_log, "PRAY: [text]") +/proc/log_prayer(text, list/data) + logger.Log(LOG_CATEGORY_GAME_PRAYER, text, data) /// Logging for music requests -/proc/log_internet_request(text) - if (CONFIG_GET(flag/log_internet_request)) - WRITE_LOG(GLOB.world_game_log, "INTERNET REQUEST: [text]") +/proc/log_internet_request(text, list/data) + logger.Log(LOG_CATEGORY_GAME_INTERNET_REQUEST, text, data) /// Logging for logging in & out of the game, with error messages. -/proc/log_access(text) - if (CONFIG_GET(flag/log_access)) - WRITE_LOG(GLOB.world_game_log, "ACCESS: [text]") +/proc/log_access(text, list/data) + logger.Log(LOG_CATEGORY_GAME_ACCESS, text, data) /// Logging for OOC votes -/proc/log_vote(text) - if (CONFIG_GET(flag/log_vote)) - WRITE_LOG(GLOB.world_game_log, "VOTE: [text]") +/proc/log_vote(text, list/data) + logger.Log(LOG_CATEGORY_GAME_VOTE, text, data) diff --git a/code/__HELPERS/logging/manifest.dm b/code/__HELPERS/logging/manifest.dm index 7275b1cc73d..6009195e411 100644 --- a/code/__HELPERS/logging/manifest.dm +++ b/code/__HELPERS/logging/manifest.dm @@ -1,4 +1,5 @@ /// Logging for player manifest (ckey, name, job, special role, roundstart/latejoin) /proc/log_manifest(ckey, datum/mind/mind, mob/body, latejoin = FALSE) - if (CONFIG_GET(flag/log_manifest)) - WRITE_LOG(GLOB.world_manifest_log, "[ckey] \\ [body.real_name] \\ [mind.assigned_role.title] \\ [mind.special_role ? mind.special_role : "NONE"] \\ [latejoin ? "LATEJOIN":"ROUNDSTART"]") + logger.Log(LOG_CATEGORY_MANIFEST, "manifest entry added", list( + "mind" = mind, "body" = body, "latejoin" = latejoin + )) diff --git a/code/__HELPERS/logging/mecha.dm b/code/__HELPERS/logging/mecha.dm index 45c6a445dd2..68c62bad8af 100644 --- a/code/__HELPERS/logging/mecha.dm +++ b/code/__HELPERS/logging/mecha.dm @@ -1,7 +1,6 @@ /// Logging for mech actions -/proc/log_mecha(text) - if (CONFIG_GET(flag/log_mecha)) - WRITE_LOG(GLOB.world_mecha_log, "MECHA: [text]") +/proc/log_mecha(text, list/data) + logger.Log(LOG_CATEGORY_MECHA, text, data) /// Logging for equipment installed in a mecha /obj/item/mecha_parts/mecha_equipment/log_message(message, message_type = LOG_MECHA, color = null, log_globally) diff --git a/code/__HELPERS/logging/mob.dm b/code/__HELPERS/logging/mob.dm index 02281ab54e1..a5951a9a524 100644 --- a/code/__HELPERS/logging/mob.dm +++ b/code/__HELPERS/logging/mob.dm @@ -3,12 +3,11 @@ * Arguments: * * text - text to log. */ -/mob/proc/log_mob_tag(text) - WRITE_LOG(GLOB.world_mob_tag_log, "TAG: \[[tag]\] [text]") +/mob/proc/log_mob_tag(text, list/data) + logger.Log(LOG_CATEGORY_DEBUG_MOBTAG, text, data) -/proc/log_silicon(text) - if (CONFIG_GET(flag/log_silicon)) - WRITE_LOG(GLOB.world_silicon_log, "SILICON: [text]") +/proc/log_silicon(text, list/data) + logger.Log(LOG_CATEGORY_SILICON, text, data) /// Logs a message in a mob's individual log, and in the global logs as well if log_globally is true diff --git a/code/__HELPERS/logging/paper.dm b/code/__HELPERS/logging/paper.dm index b748faf217e..edd2835772b 100644 --- a/code/__HELPERS/logging/paper.dm +++ b/code/__HELPERS/logging/paper.dm @@ -1,3 +1,3 @@ /// Logging for writing made on paper -/proc/log_paper(text) - WRITE_LOG(GLOB.world_paper_log, "PAPER: [text]") +/proc/log_paper(text, list/data) + logger.Log(LOG_CATEGORY_PAPER, text, data) diff --git a/code/__HELPERS/logging/pda.dm b/code/__HELPERS/logging/pda.dm index 01c8d98d95a..acd5b611183 100644 --- a/code/__HELPERS/logging/pda.dm +++ b/code/__HELPERS/logging/pda.dm @@ -1,16 +1,11 @@ /// Logging for PDA messages sent -/proc/log_pda(text) - if (CONFIG_GET(flag/log_pda)) - WRITE_LOG(GLOB.world_pda_log, "PDA: [text]") +/proc/log_pda(text, list/data) + logger.Log(LOG_CATEGORY_PDA, text, data) /// Logging for newscaster comments -/proc/log_comment(text) - //reusing the PDA option because I really don't think news comments are worth a config option - if (CONFIG_GET(flag/log_pda)) - WRITE_LOG(GLOB.world_pda_log, "COMMENT: [text]") +/proc/log_comment(text, list/data) + logger.Log(LOG_CATEGORY_PDA_COMMENT, text, data) /// Logging for chatting on modular computer channels -/proc/log_chat(text) - //same thing here - if (CONFIG_GET(flag/log_pda)) - WRITE_LOG(GLOB.world_pda_log, "CHAT: [text]") +/proc/log_chat(text, list/data) + logger.Log(LOG_CATEGORY_PDA_CHAT, text, data) diff --git a/code/__HELPERS/logging/shuttle.dm b/code/__HELPERS/logging/shuttle.dm index 62a5fcd3e6e..605ed903cfc 100644 --- a/code/__HELPERS/logging/shuttle.dm +++ b/code/__HELPERS/logging/shuttle.dm @@ -1,4 +1,3 @@ /// Logging for shuttle actions -/proc/log_shuttle(text) - if (CONFIG_GET(flag/log_shuttle)) - WRITE_LOG(GLOB.world_shuttle_log, "SHUTTLE: [text]") +/proc/log_shuttle(text, list/data) + logger.Log(LOG_CATEGORY_SHUTTLE, text, data) diff --git a/code/__HELPERS/logging/talk.dm b/code/__HELPERS/logging/talk.dm index bed13b6c350..6cfae068cb6 100644 --- a/code/__HELPERS/logging/talk.dm +++ b/code/__HELPERS/logging/talk.dm @@ -17,14 +17,12 @@ log_message("[prefix][custom_say_emote ? "*[custom_say_emote]*, " : ""]\"[message]\"[suffix]", message_type, log_globally = log_globally) /// Logging for generic spoken messages -/proc/log_say(text) - if (CONFIG_GET(flag/log_say)) - WRITE_LOG(GLOB.world_game_log, "SAY: [text]") +/proc/log_say(text, list/data) + logger.Log(LOG_CATEGORY_GAME_SAY, text, data) /// Logging for whispered messages -/proc/log_whisper(text) - if (CONFIG_GET(flag/log_whisper)) - WRITE_LOG(GLOB.world_game_log, "WHISPER: [text]") +/proc/log_whisper(text, list/data) + logger.Log(LOG_CATEGORY_GAME_WHISPER, text, data) /// Helper for logging of messages with only one sender and receiver (i.e. mind links) /proc/log_directed_talk(atom/source, atom/target, message, message_type, tag) @@ -37,11 +35,9 @@ target.log_talk(message, LOG_VICTIM, tag = "[tag] from [key_name(source)]", log_globally = FALSE) /// Logging for speech taking place over comms, as well as tcomms equipment -/proc/log_telecomms(text) - if (CONFIG_GET(flag/log_telecomms)) - WRITE_LOG(GLOB.world_telecomms_log, "TCOMMS: [text]") +/proc/log_telecomms(text, list/data) + logger.Log(LOG_CATEGORY_TELECOMMS, text, data) /// Logging for speech indicators. -/proc/log_speech_indicators(text) - if (CONFIG_GET(flag/log_speech_indicators)) - WRITE_LOG(GLOB.world_speech_indicators_log, "SPEECH INDICATOR: [text]") +/proc/log_speech_indicators(text, list/data) + logger.Log(LOG_CATEGORY_SPEECH_INDICATOR, text, data) diff --git a/code/__HELPERS/logging/tool.dm b/code/__HELPERS/logging/tool.dm index 4fd354eb58d..b43d8a4b704 100644 --- a/code/__HELPERS/logging/tool.dm +++ b/code/__HELPERS/logging/tool.dm @@ -1,4 +1,3 @@ /// Logging for tool usage /proc/log_tool(text, mob/initiator) - if(CONFIG_GET(flag/log_tools)) - WRITE_LOG(GLOB.world_tool_log, "TOOL: [text]") + logger.Log(LOG_CATEGORY_TOOL, text, initiator ? list("initiator" = initiator) : null) diff --git a/code/__HELPERS/logging/ui.dm b/code/__HELPERS/logging/ui.dm index 3a0432d35f7..607f22a507b 100644 --- a/code/__HELPERS/logging/ui.dm +++ b/code/__HELPERS/logging/ui.dm @@ -1,5 +1,5 @@ -/proc/log_href(text) - WRITE_LOG(GLOB.world_href_log, "HREF: [text]") +/proc/log_href(text, list/data) + logger.Log(LOG_CATEGORY_DEBUG_HREF, text, data) /** * Appends a tgui-related log entry. All arguments are optional. @@ -31,4 +31,4 @@ // Insert message if(message) entry += "\n[message]" - WRITE_LOG(GLOB.tgui_log, entry) + logger.Log(LOG_CATEGORY_DEBUG_TGUI, entry) diff --git a/code/__HELPERS/logging/virus.dm b/code/__HELPERS/logging/virus.dm index 26d0d374e02..050ae9434e4 100644 --- a/code/__HELPERS/logging/virus.dm +++ b/code/__HELPERS/logging/virus.dm @@ -1,7 +1,6 @@ /// Logging for the creation and contraction of viruses -/proc/log_virus(text) - if (CONFIG_GET(flag/log_virus)) - WRITE_LOG(GLOB.world_virus_log, "VIRUS: [text]") +/proc/log_virus(text, list/data) + logger.Log(LOG_CATEGORY_VIRUS, text, data) /// Returns a string for admin logging uses, should describe the disease in detail /datum/disease/proc/admin_details() diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index 91837dfb3ea..ed430a86708 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -1193,3 +1193,15 @@ GLOBAL_LIST_INIT(binary, list("0","1")) /proc/sanitize_css_class_name(name) var/static/regex/regex = new(@"[^a-zA-Z0-9]","g") return replacetext(name, regex, "") + +/// Converts a semver string into a list of numbers +/proc/semver_to_list(semver_string) + var/static/regex/semver_regex = regex(@"(\d+)\.(\d+)\.(\d+)", "") + if(!semver_regex.Find(semver_string)) + return null + + return list( + text2num(semver_regex.group[1]), + text2num(semver_regex.group[2]), + text2num(semver_regex.group[3]), + ) diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm index a930f75a3e5..4d0b0698679 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -28,47 +28,18 @@ GLOBAL_PROTECT(##log_var_name);\ SHOULD_CALL_PARENT(TRUE) return -// All individual log files +// All individual log files. +// These should be used where the log category cannot easily be a json log file. DECLARE_LOG(config_error_log, DONT_START_LOG) -DECLARE_LOG(dynamic_log, DONT_START_LOG) -DECLARE_LOG(lua_log, DONT_START_LOG) DECLARE_LOG(perf_log, DONT_START_LOG) // Declared here but name is set in time_track subsystem -DECLARE_LOG(query_debug_log, DONT_START_LOG) -DECLARE_LOG(signals_log, DONT_START_LOG) -DECLARE_LOG(tgui_log, START_LOG) + #ifdef REFERENCE_DOING_IT_LIVE DECLARE_LOG_NAMED(harddel_log, "harddels", START_LOG) #endif + #if defined(UNIT_TESTS) || defined(SPACEMAN_DMM) DECLARE_LOG_NAMED(test_log, "tests", START_LOG) #endif -DECLARE_LOG_NAMED(filter_log, "filters", DONT_START_LOG) -DECLARE_LOG_NAMED(sql_error_log, "sql", DONT_START_LOG) -DECLARE_LOG_NAMED(world_asset_log, "asset", DONT_START_LOG) -DECLARE_LOG_NAMED(world_attack_log, "attack", START_LOG) -DECLARE_LOG_NAMED(world_econ_log, "econ", START_LOG) -DECLARE_LOG_NAMED(world_game_log, "game", START_LOG) -DECLARE_LOG_NAMED(world_href_log, "hrefs", START_LOG) -DECLARE_LOG_NAMED(world_job_debug_log, "job_debug", START_LOG) -DECLARE_LOG_NAMED(world_manifest_log, "manifest", START_LOG) -DECLARE_LOG_NAMED(world_map_error_log, "map_errors", DONT_START_LOG) -DECLARE_LOG_NAMED(world_mecha_log, "mecha", DONT_START_LOG) -DECLARE_LOG_NAMED(world_mob_tag_log, "mob_tags", START_LOG) -DECLARE_LOG_NAMED(world_paper_log, "paper", DONT_START_LOG) -DECLARE_LOG_NAMED(world_pda_log, "pda", START_LOG) -DECLARE_LOG_NAMED(world_qdel_log, "qdel", START_LOG) -DECLARE_LOG_NAMED(world_runtime_log, "runtime", START_LOG) -DECLARE_LOG_NAMED(world_shuttle_log, "shuttle", START_LOG) -DECLARE_LOG_NAMED(world_silicon_log, "silicon", DONT_START_LOG) -DECLARE_LOG_NAMED(world_speech_indicators_log, "speech_indicators", DONT_START_LOG) -DECLARE_LOG_NAMED(world_telecomms_log, "telecomms", START_LOG) -DECLARE_LOG_NAMED(world_tool_log, "tools", DONT_START_LOG) -DECLARE_LOG_NAMED(world_uplink_log, "uplink", START_LOG) -DECLARE_LOG_NAMED(world_virus_log, "virus", DONT_START_LOG) -/// Log associated with [/proc/log_suspicious_login()] -/// Intended to hold all logins that failed due to suspicious circumstances such as ban detection, CID randomisation etc. -DECLARE_LOG_NAMED(world_suspicious_login_log, "suspicious_logins", DONT_START_LOG) - /// Picture logging diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 752918e3c63..cac60fadec8 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -158,6 +158,10 @@ /// logs all timers in buckets on automatic bucket reset (Useful for timer debugging) /datum/config_entry/flag/log_timers_on_bucket_reset +/// Log human readable versions of json log entries +/datum/config_entry/flag/log_as_human_readable + default = TRUE + /// allows admins with relevant permissions to have their own ooc colour /datum/config_entry/flag/allow_admin_ooccolor diff --git a/code/controllers/subsystem/dbcore.dm b/code/controllers/subsystem/dbcore.dm index 7afd62fb87f..909d72bb85e 100644 --- a/code/controllers/subsystem/dbcore.dm +++ b/code/controllers/subsystem/dbcore.dm @@ -569,12 +569,18 @@ Delayed insert mode was removed in mysql 7 and only works with MyISAM type table . = (status != DB_QUERY_BROKEN) var/timed_out = !. && findtext(last_error, "Operation timed out") if(!. && log_error) - log_sql("[last_error] | Query used: [sql] | Arguments: [json_encode(arguments)]") + logger.Log(LOG_CATEGORY_DEBUG_SQL, "sql query failed", list( + "query" = sql, + "arguments" = json_encode(arguments), + "error" = last_error, + )) + if(!async && timed_out) - log_query_debug("Query execution started at [start_time]") - log_query_debug("Query execution ended at [REALTIMEOFDAY]") - log_query_debug("Slow query timeout detected.") - log_query_debug("Query used: [sql]") + logger.Log(LOG_CATEGORY_DEBUG_SQL, "slow query timeout", list( + "query" = sql, + "start_time" = start_time, + "end_time" = REALTIMEOFDAY, + )) slow_query_check() /// Sleeps until execution of the query has finished. diff --git a/code/datums/datum.dm b/code/datums/datum.dm index 8b4dd37aa7b..97d5f83f0d8 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -183,13 +183,20 @@ to_chat(target, txt_changed_vars()) #endif -///Return a LIST for serialize_datum to encode! Not the actual json! -/datum/proc/serialize_list(list/options) - CRASH("Attempted to serialize datum [src] of type [type] without serialize_list being implemented!") +/// Return a list of data which can be used to investigate the datum, also ensure that you set the semver in the options list +/datum/proc/serialize_list(list/options, list/semvers) + SHOULD_CALL_PARENT(TRUE) -///Accepts a LIST from deserialize_datum. Should return src or another datum. + . = list() + .["tag"] = tag + + SET_SERIALIZATION_SEMVER(semvers, "1.0.0") + return . + +///Accepts a LIST from deserialize_datum. Should return whether or not the deserialization was successful. /datum/proc/deserialize_list(json, list/options) - CRASH("Attempted to deserialize datum [src] of type [type] without deserialize_list being implemented!") + SHOULD_CALL_PARENT(TRUE) + return TRUE ///Serializes into JSON. Does not encode type. /datum/proc/serialize_json(list/options) @@ -241,11 +248,10 @@ return var/typeofdatum = jsonlist["DATUM_TYPE"] //BYOND won't directly read if this is just put in the line below, and will instead runtime because it thinks you're trying to make a new list? var/datum/D = new typeofdatum - var/datum/returned = D.deserialize_list(jsonlist, options) - if(!isdatum(returned)) + if(!D.deserialize_list(jsonlist, options)) qdel(D) else - return returned + return D /** * Callback called by a timer to end an associative-list-indexed cooldown. diff --git a/code/datums/json_savefile.dm b/code/datums/json_savefile.dm index 83054258aeb..69e5a2ef7cf 100644 --- a/code/datums/json_savefile.dm +++ b/code/datums/json_savefile.dm @@ -59,7 +59,9 @@ GENERAL_PROTECT_DATUM(/datum/json_savefile) if(path) rustg_file_write(json_encode(tree), path) -/datum/json_savefile/serialize_list(list/options) +/datum/json_savefile/serialize_list(list/options, list/semvers) + SHOULD_CALL_PARENT(FALSE) + SET_SERIALIZATION_SEMVER(semvers, "1.0.0") return tree.Copy() /// Traverses the entire dir tree of the given savefile and dynamically assembles the tree from it diff --git a/code/datums/martial/_martial.dm b/code/datums/martial/_martial.dm index a6cfbb3d725..a3c62f5ed3f 100644 --- a/code/datums/martial/_martial.dm +++ b/code/datums/martial/_martial.dm @@ -16,6 +16,16 @@ /// If set to true this style allows you to punch people despite being a pacifist (for instance Boxing, which does no damage) var/pacifist_style = FALSE +/datum/martial_art/serialize_list(list/options, list/semvers) + . = ..() + + .["name"] = name + .["id"] = id + .["pacifist_style"] = pacifist_style + + SET_SERIALIZATION_SEMVER(semvers, "1.0.0") + return . + /datum/martial_art/proc/help_act(mob/living/attacker, mob/living/defender) return MARTIAL_ATTACK_INVALID diff --git a/code/datums/memory/_memory.dm b/code/datums/memory/_memory.dm index 88d8f4a6247..45710f07067 100644 --- a/code/datums/memory/_memory.dm +++ b/code/datums/memory/_memory.dm @@ -75,6 +75,22 @@ memorizer_mind = null return ..() +/datum/memory/serialize_list(list/options, list/semvers) + . = ..() + + .["name"] = name + .["memorizer"] = memorizer + .["story_value"] = story_value + .["memory_flags"] = memory_flags + .["mood_verb"] = mood_verb + .["protagonist_name"] = protagonist_name + .["deuteragonist_name"] = deuteragonist_name + .["antagonist_name"] = antagonist_name + .["where"] = where + + SET_SERIALIZATION_SEMVER(semvers, "1.0.0") + return . + /** * Generates a name for the memory. */ diff --git a/code/datums/mind/_mind.dm b/code/datums/mind/_mind.dm index 5407513712a..2e74de07a51 100644 --- a/code/datums/mind/_mind.dm +++ b/code/datums/mind/_mind.dm @@ -122,6 +122,25 @@ set_current(null) return ..() +/datum/mind/serialize_list(list/options, list/semvers) + . = ..() + + .["key"] = key + .["name"] = name + .["ghostname"] = ghostname + .["memories"] = memories + .["martial_art"] = martial_art + .["antag_datums"] = antag_datums + .["holy_role"] = holy_role + .["special_role"] = special_role + .["assigned_role"] = assigned_role.title + .["current"] = current + + var/mob/enslaved_to = src.enslaved_to?.resolve() + .["enslaved_to"] = enslaved_to + + SET_SERIALIZATION_SEMVER(semvers, "1.0.0") + return . /datum/mind/vv_edit_var(var_name, var_value) switch(var_name) diff --git a/code/game/world.dm b/code/game/world.dm index e2f380c2cfe..181ff3c9098 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -76,6 +76,9 @@ GLOBAL_VAR(restart_counter) // Init the debugger first so we can debug Master init_debugger() + // Create the logger + logger = new + // THAT'S IT, WE'RE DONE, THE. FUCKING. END. Master = new @@ -176,6 +179,14 @@ GLOBAL_VAR(restart_counter) #endif SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(_addtimer), cb, 10 SECONDS)) +/// Returns a list of data about the world state, don't clutter +/world/proc/get_world_state_for_logging() + var/data = list() + data["tick_usage"] = world.tick_usage + data["tick_lag"] = world.tick_lag + data["time"] = world.time + data["timestamp"] = logger.unix_timestamp_string() + return data /world/proc/SetupLogs() var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER] @@ -199,7 +210,7 @@ GLOBAL_VAR(restart_counter) GLOB.picture_logging_prefix = "O_[override_dir]_" GLOB.picture_log_directory = "data/picture_logs/[override_dir]" - GLOB.logger.init_logging() + logger.init_logging() var/latest_changelog = file("[global.config.directory]/../html/changelogs/archive/" + time2text(world.timeofday, "YYYY-MM") + ".yml") GLOB.changelog_hash = fexists(latest_changelog) ? md5(latest_changelog) : 0 //for telling if the changelog has changed recently diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 17fbc1adc69..6491135b5fd 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1474,15 +1474,17 @@ else if(href_list["slowquery"]) if(!check_rights(R_ADMIN)) return + + var/data = list("key" = usr.key) var/answer = href_list["slowquery"] if(answer == "yes") - log_query_debug("[usr.key] | Reported a server hang") if(tgui_alert(usr, "Did you just press any admin buttons?", "Query server hang report", list("Yes", "No")) == "Yes") var/response = input(usr,"What were you just doing?","Query server hang report") as null|text if(response) - log_query_debug("[usr.key] | [response]") + data["response"] = response + logger.Log(LOG_CATEGORY_DEBUG_SQL, "server hang", data) else if(answer == "no") - log_query_debug("[usr.key] | Reported no server hang") + logger.Log(LOG_CATEGORY_DEBUG_SQL, "no server hang", data) else if(href_list["ctf_toggle"]) if(!check_rights(R_ADMIN)) diff --git a/code/modules/logging/categories/log_category_admin.dm b/code/modules/logging/categories/log_category_admin.dm new file mode 100644 index 00000000000..6bc4cbc827d --- /dev/null +++ b/code/modules/logging/categories/log_category_admin.dm @@ -0,0 +1,26 @@ +/datum/log_category/admin + category = LOG_CATEGORY_ADMIN + config_flag = /datum/config_entry/flag/log_admin + +/datum/log_category/admin_dsay + category = LOG_CATEGORY_ADMIN_DSAY + master_category = /datum/log_category/admin + config_flag = /datum/config_entry/flag/log_admin + +/datum/log_category/admin_circuit + category = LOG_CATEGORY_ADMIN_CIRCUIT + master_category = /datum/log_category/admin + config_flag = /datum/config_entry/flag/log_admin + +// private categories // + +/datum/log_category/admin_private + category = LOG_CATEGORY_ADMIN_PRIVATE + config_flag = /datum/config_entry/flag/log_admin + secret = TRUE + +/datum/log_category/admin_asay + category = LOG_CATEGORY_ADMIN_PRIVATE_ASAY + master_category = /datum/log_category/admin_private + config_flag = /datum/config_entry/flag/log_adminchat + secret = TRUE diff --git a/code/modules/logging/categories/log_category_compats.dm b/code/modules/logging/categories/log_category_compats.dm new file mode 100644 index 00000000000..2cf7343f01e --- /dev/null +++ b/code/modules/logging/categories/log_category_compats.dm @@ -0,0 +1,7 @@ +/datum/log_category/game_compat + category = LOG_CATEGORY_COMPAT_GAME + master_category = /datum/log_category/game + config_flag = /datum/config_entry/flag/logging_compat_adminprivate + +/datum/config_entry/flag/logging_compat_adminprivate + default = FALSE diff --git a/code/modules/logging/categories/log_category_debug.dm b/code/modules/logging/categories/log_category_debug.dm new file mode 100644 index 00000000000..417a56773d6 --- /dev/null +++ b/code/modules/logging/categories/log_category_debug.dm @@ -0,0 +1,45 @@ +/datum/log_category/debug + category = LOG_CATEGORY_DEBUG + +/datum/log_category/debug_tgui + category = LOG_CATEGORY_DEBUG_TGUI + master_category = /datum/log_category/debug + +/datum/log_category/debug_sql + category = LOG_CATEGORY_DEBUG_SQL + master_category = /datum/log_category/debug + +/datum/log_category/debug_lua + category = LOG_CATEGORY_DEBUG_LUA + master_category = /datum/log_category/debug + +/datum/log_category/debug_href + category = LOG_CATEGORY_DEBUG_HREF + master_category = /datum/log_category/debug + +// This is not in the debug master category on purpose, do not add it +/datum/log_category/debug_runtime + category = LOG_CATEGORY_RUNTIME + internal_formatting = FALSE + +/datum/log_category/debug_mapping + category = LOG_CATEGORY_DEBUG_MAPPING + master_category = /datum/log_category/debug + +/datum/log_category/debug_job + category = LOG_CATEGORY_DEBUG_JOB + config_flag = /datum/config_entry/flag/log_job_debug + master_category = /datum/log_category/debug + +/datum/log_category/debug_mobtag + category = LOG_CATEGORY_DEBUG_MOBTAG + master_category = /datum/log_category/debug + +/datum/log_category/debug_asset + category = LOG_CATEGORY_DEBUG_ASSET + config_flag = /datum/config_entry/flag/log_asset + master_category = /datum/log_category/debug + +/datum/log_category/debug_qdel + category = LOG_CATEGORY_DEBUG_QDEL + master_category = /datum/log_category/debug diff --git a/code/modules/logging/categories/log_category_game.dm b/code/modules/logging/categories/log_category_game.dm new file mode 100644 index 00000000000..1d29f288984 --- /dev/null +++ b/code/modules/logging/categories/log_category_game.dm @@ -0,0 +1,58 @@ +/datum/log_category/game + category = LOG_CATEGORY_GAME + config_flag = /datum/config_entry/flag/log_game + +/datum/log_category/game_vote + category = LOG_CATEGORY_GAME_VOTE + config_flag = /datum/config_entry/flag/log_vote + master_category = /datum/log_category/game + +/datum/log_category/game_emote + category = LOG_CATEGORY_GAME_EMOTE + config_flag = /datum/config_entry/flag/log_emote + master_category = /datum/log_category/game + +/datum/log_category/game_internet_request + category = LOG_CATEGORY_GAME_INTERNET_REQUEST + config_flag = /datum/config_entry/flag/log_internet_request + master_category = /datum/log_category/game + +/datum/log_category/game_radio_emote + category = LOG_CATEGORY_GAME_RADIO_EMOTE + config_flag = /datum/config_entry/flag/log_emote + master_category = /datum/log_category/game + +/datum/log_category/game_traitor + category = LOG_CATEGORY_GAME_TRAITOR + config_flag = /datum/config_entry/flag/log_traitor + master_category = /datum/log_category/game + +/datum/log_category/game_topic + category = LOG_CATEGORY_GAME_TOPIC + config_flag = /datum/config_entry/flag/log_world_topic + master_category = /datum/log_category/game + +/datum/log_category/game_say + category = LOG_CATEGORY_GAME_SAY + config_flag = /datum/config_entry/flag/log_say + master_category = /datum/log_category/game + +/datum/log_category/game_whisper + category = LOG_CATEGORY_GAME_WHISPER + config_flag = /datum/config_entry/flag/log_whisper + master_category = /datum/log_category/game + +/datum/log_category/game_ooc + category = LOG_CATEGORY_GAME_OOC + config_flag = /datum/config_entry/flag/log_ooc + master_category = /datum/log_category/game + +/datum/log_category/game_prayer + category = LOG_CATEGORY_GAME_PRAYER + config_flag = /datum/config_entry/flag/log_prayer + master_category = /datum/log_category/game + +/datum/log_category/game_access + category = LOG_CATEGORY_GAME_ACCESS + config_flag = /datum/config_entry/flag/log_access + master_category = /datum/log_category/game diff --git a/code/modules/logging/categories/log_category_misc.dm b/code/modules/logging/categories/log_category_misc.dm new file mode 100644 index 00000000000..5cc77fed203 --- /dev/null +++ b/code/modules/logging/categories/log_category_misc.dm @@ -0,0 +1,58 @@ +/datum/log_category/virus + category = LOG_CATEGORY_VIRUS + config_flag = /datum/config_entry/flag/log_virus + +/datum/log_category/tool + category = LOG_CATEGORY_TOOL + config_flag = /datum/config_entry/flag/log_tools + +/datum/log_category/mecha + category = LOG_CATEGORY_MECHA + config_flag = /datum/config_entry/flag/log_mecha + +/datum/log_category/silicon + category = LOG_CATEGORY_SILICON + config_flag = /datum/config_entry/flag/log_silicon + +/datum/log_category/paper + category = LOG_CATEGORY_PAPER + +/datum/log_category/attack + category = LOG_CATEGORY_ATTACK + config_flag = /datum/config_entry/flag/log_attack + +/datum/log_category/economy + category = LOG_CATEGORY_ECONOMY + config_flag = /datum/config_entry/flag/log_econ + +/datum/log_category/manifest + category = LOG_CATEGORY_MANIFEST + config_flag = /datum/config_entry/flag/log_manifest + +/datum/log_category/shuttle + category = LOG_CATEGORY_SHUTTLE + config_flag = /datum/config_entry/flag/log_shuttle + +/datum/log_category/supicious_login + category = LOG_CATEGORY_SUSPICIOUS_LOGIN + config_flag = /datum/config_entry/flag/log_suspicious_login + +/datum/log_category/config + category = LOG_CATEGORY_CONFIG + +/datum/log_category/filter + category = LOG_CATEGORY_FILTER + +/datum/log_category/signal + category = LOG_CATEGORY_SIGNAL + +/datum/log_category/dynamic + category = LOG_CATEGORY_DYNAMIC + +/datum/log_category/telecomms + category = LOG_CATEGORY_TELECOMMS + config_flag = /datum/config_entry/flag/log_telecomms + +/datum/log_category/speech_indicator + category = LOG_CATEGORY_SPEECH_INDICATOR + config_flag = /datum/config_entry/flag/log_speech_indicators diff --git a/code/modules/logging/categories/log_category_pda.dm b/code/modules/logging/categories/log_category_pda.dm new file mode 100644 index 00000000000..aefb8776f20 --- /dev/null +++ b/code/modules/logging/categories/log_category_pda.dm @@ -0,0 +1,11 @@ +/datum/log_category/pda + category = LOG_CATEGORY_PDA + config_flag = /datum/config_entry/flag/log_pda + +/datum/log_category/pda_chat + category = LOG_CATEGORY_PDA_CHAT + master_category = /datum/log_category/pda + +/datum/log_category/pda_comment + category = LOG_CATEGORY_PDA_COMMENT + master_category = /datum/log_category/pda diff --git a/code/modules/logging/categories/log_category_uplink.dm b/code/modules/logging/categories/log_category_uplink.dm new file mode 100644 index 00000000000..f88d224ad3b --- /dev/null +++ b/code/modules/logging/categories/log_category_uplink.dm @@ -0,0 +1,23 @@ +/datum/log_category/uplink + category = LOG_CATEGORY_UPLINK + config_flag = /datum/config_entry/flag/log_uplink + +/datum/log_category/uplink_malf + category = LOG_CATEGORY_UPLINK_MALF + config_flag = /datum/config_entry/flag/log_uplink + master_category = /datum/log_category/uplink + +/datum/log_category/uplink_heretic + category = LOG_CATEGORY_UPLINK_HERETIC + config_flag = /datum/config_entry/flag/log_uplink + master_category = /datum/log_category/uplink + +/datum/log_category/uplink_changeling + category = LOG_CATEGORY_UPLINK_CHANGELING + config_flag = /datum/config_entry/flag/log_uplink + master_category = /datum/log_category/uplink + +/datum/log_category/uplink_spell + category = LOG_CATEGORY_UPLINK_SPELL + config_flag = /datum/config_entry/flag/log_uplink + master_category = /datum/log_category/uplink diff --git a/code/modules/logging/log_category.dm b/code/modules/logging/log_category.dm index 943ebabf3d3..82dcc57d0fe 100644 --- a/code/modules/logging/log_category.dm +++ b/code/modules/logging/log_category.dm @@ -1,32 +1,56 @@ /// The main datum that contains all log entries for a category /datum/log_category - /// The category this datum contains + /// The category name var/category + + /// The schema version of this log category. + /// Expected format of "Major.Minor.Patch" + var/schema_version = LOG_CATEGORY_SCHEMA_VERSION_NOT_SET + + /// The master category that contains this category + var/datum/log_category/master_category + /// If set this config flag is checked to enable this log category var/config_flag - /// List of all entries, in chronological order of when they were added - var/list/entries = list() + + /// Whether or not this log should not be publically visible + var/secret = FALSE + + /// Whether the readable version of the log message is formatted internally instead of by rustg + var/internal_formatting = TRUE + +GENERAL_PROTECT_DATUM(/datum/log_category) /// Backup log category to catch attempts to log to a category that doesn't exist /datum/log_category/backup_category_not_found category = LOG_CATEGORY_NOT_FOUND /// Add an entry to this category. It is very important that any data you provide doesn't hold references to anything! -/datum/log_category/proc/add_entry(message, list/data) - var/list/entry = list( - LOG_ENTRY_MESSAGE = message, - LOG_ENTRY_TIMESTAMP = big_number_to_text(rustg_unix_timestamp()), +/datum/log_category/proc/create_entry(message, list/data, list/semver_store) + var/datum/log_entry/entry = new( + // world state contains raw timestamp + timestamp = logger.human_readable_timestamp(), + category = category, + message = message, + data = data, + semver_store = semver_store, ) - if(data) - entry[LOG_ENTRY_DATA] = data - entries += list(entry) write_entry(entry) /// Allows for category specific file splitting. Needs to accept a null entry for the default file. -/datum/log_category/proc/get_output_file(list/entry) - return "[GLOB.log_directory]/[category].json" +/// If master_category it will always return the output of master_category.get_output_file(entry) +/datum/log_category/proc/get_output_file(list/entry, extension = "log.json") + if(master_category) + return master_category.get_output_file(entry, extension) + if(secret) + return "[GLOB.log_directory]/secret/[category].[extension]" + return "[GLOB.log_directory]/[category].[extension]" -/// Writes an entry to the output file for the category -/datum/log_category/proc/write_entry(list/entry) - rustg_file_append("[json_encode(entry)]\n", get_output_file(entry)) +/// Writes an entry to the output file(s) for the category +/datum/log_category/proc/write_entry(datum/log_entry/entry) + // config isn't loaded? assume we want human readable logs + if(isnull(config) || CONFIG_GET(flag/log_as_human_readable)) + entry.write_readable_entry_to_file(get_output_file(entry, "log"), format_internally = internal_formatting) + + entry.write_entry_to_file(get_output_file(entry)) diff --git a/code/modules/logging/log_entry.dm b/code/modules/logging/log_entry.dm new file mode 100644 index 00000000000..0f47cef6796 --- /dev/null +++ b/code/modules/logging/log_entry.dm @@ -0,0 +1,114 @@ + +// Schema version must always be the very last element in the array. + +// Current Schema: 1.0.0 +// [timestamp, category, message, data, world_state, semver_store, id, schema_version] + +/// A datum which contains log information. +/datum/log_entry + /// Next id to assign to a log entry. + var/static/next_id = 0 + + /// Unique id of the log entry. + var/id + + /// Schema version of the log entry. + var/schema_version = "1.0.0" + + /// Unix timestamp of the log entry. + var/timestamp + + /// Category of the log entry. + var/category + + /// Message of the log entry. + var/message + + /// Data of the log entry; optional. + var/list/data + + /// Semver store of the log entry, used to store the schema of data entries + var/list/semver_store + +GENERAL_PROTECT_DATUM(/datum/log_entry) + +/datum/log_entry/New(timestamp, category, message, list/data, list/semver_store) + ..() + + src.id = next_id++ + src.timestamp = timestamp + src.category = category + src.message = message + with_data(data) + with_semver_store(semver_store) + +/datum/log_entry/proc/with_data(list/data) + if(!isnull(data)) + if(!islist(data)) + src.data = list("data" = data) + stack_trace("Log entry data was not a list, it was [data.type].") + else + src.data = data + return src + +/datum/log_entry/proc/with_semver_store(list/semver_store) + if(isnull(semver_store)) + return + if(!islist(semver_store)) + stack_trace("Log entry semver store was not a list, it was [semver_store.type]. We cannot reliably convert it to a list.") + else + src.semver_store = semver_store + return src + +/// Converts the log entry to a human-readable string. +/datum/log_entry/proc/to_readable_text(format = TRUE) + if(format) + return "\[[timestamp]\] [uppertext(category)]: [message]" + else + return "[message]" + +/// Converts the log entry to a JSON string. +/datum/log_entry/proc/to_json_text() + // I do not trust byond's json encoder, so we're doing it manually + var/list/json_strings = list() + + json_strings += json_encode(timestamp) + + json_strings += json_encode(category) + + json_strings += json_encode(message) + + if(length(data)) + json_strings += json_encode(data) + else + json_strings += "null" + + json_strings += json_encode(world.get_world_state_for_logging()) + + if(length(semver_store)) + json_strings += json_encode(semver_store) + else + json_strings += "null" + + json_strings += "[id]" + + json_strings += json_encode(schema_version) + + return "\[[json_strings.Join(",")]\]" + +/// Writes the log entry to a file. +/datum/log_entry/proc/write_entry_to_file(file) + if(!fexists(file)) + CRASH("Attempted to log to an uninitialized file: [file]") + WRITE_LOG_NO_FORMAT(file, "[to_json_text()]\n") + +/// Writes the log entry to a file as a human-readable string. +/datum/log_entry/proc/write_readable_entry_to_file(file, format_internally = TRUE) + if(!fexists(file)) + CRASH("Attempted to log to an uninitialized file: [file]") + + // If it's being formatted internally we need to manually add a newline + if(format_internally) + WRITE_LOG_NO_FORMAT(file, "[to_readable_text(format = TRUE)]\n") + else + WRITE_LOG(file, "[to_readable_text(format = FALSE)]") diff --git a/code/modules/logging/log_holder.dm b/code/modules/logging/log_holder.dm index 0ab25ccb218..526cf1f9ccb 100644 --- a/code/modules/logging/log_holder.dm +++ b/code/modules/logging/log_holder.dm @@ -1,6 +1,5 @@ -GLOBAL_DATUM_INIT(logger, /datum/log_holder, new) -GLOBAL_PROTECT(logger) +GLOBAL_REAL(logger, /datum/log_holder) /** * Main datum to manage logging actions */ @@ -14,40 +13,58 @@ GLOBAL_PROTECT(logger) var/list/datum/log_category/log_categories /// typecache list for categories that exist but are disabled var/list/disabled_categories + /// category nesting tree for ui purposes + var/list/category_group_tree + + /// list of Log args waiting for processing pending log initialization + var/list/waiting_log_calls + + /// Whether or not logging as human readable text is enabled + var/human_readable_enabled = FALSE var/initialized = FALSE var/shutdown = FALSE +GENERAL_PROTECT_DATUM(/datum/log_holder) + /// Assembles basic information for logging, creating the log category datums and checking for config flags as required /datum/log_holder/proc/init_logging() if(initialized) CRASH("Attempted to call init_logging twice!") - initialized = TRUE round_id = GLOB.round_id - logging_start_timestamp = rustg_unix_timestamp() + logging_start_timestamp = unix_timestamp_string() log_categories = list() disabled_categories = list() - for(var/datum/log_category/category_type as anything in subtypesof(/datum/log_category)) - var/category = initial(category_type.category) - if(!category) - continue - if(category in log_categories) - stack_trace("Found two identical log category type definitions! [category_type]") - continue + human_readable_enabled = CONFIG_GET(flag/log_as_human_readable) - var/config_flag = initial(category_type.config_flag) + category_group_tree = assemble_log_category_tree() + var/config_flag + for(var/datum/log_category/master_category as anything in category_group_tree) + var/list/sub_categories = category_group_tree[master_category] + sub_categories = sub_categories.Copy() + for(var/datum/log_category/sub_category as anything in sub_categories) + config_flag = initial(sub_category.config_flag) + if(config_flag && !config.Get(config_flag)) + disabled_categories[initial(sub_category.category)] = TRUE + sub_categories -= sub_category + continue + + config_flag = initial(master_category.config_flag) if(config_flag && !config.Get(config_flag)) - disabled_categories[category] = TRUE - continue - category_type = log_categories[category] = new category_type - var/list/log_start_entry = list( - LOG_HEADER_CATEGORY = category, - LOG_HEADER_INIT_TIMESTAMP = big_number_to_text(logging_start_timestamp), - LOG_HEADER_ROUND_ID = big_number_to_text(GLOB.round_id), - ) - rustg_file_write("[json_encode(log_start_entry)]\n", category_type.get_output_file(null)) + disabled_categories[initial(master_category.category)] = TRUE + if(!length(sub_categories)) + continue + // enabled, or any of the sub categories are enabled + init_log_category(master_category, sub_categories) + + initialized = TRUE + + // process any waiting log calls and then cut the list + for(var/list/arg_list as anything in waiting_log_calls) + Log(arglist(arg_list)) + waiting_log_calls?.Cut() if(fexists(GLOB.config_error_log)) fcopy(GLOB.config_error_log, "[GLOB.log_directory]/config_error.log") @@ -61,10 +78,120 @@ GLOBAL_PROTECT(logger) CRASH("Attempted to call shutdown_logging twice!") shutdown = TRUE -/// This is Log because log is a byond internal proc +/// Iterates over all log category types to assemble them into a tree of main category -> (sub category)[] while also checking for loops and sanity errors +/datum/log_holder/proc/assemble_log_category_tree() + var/static/list/category_tree + if(category_tree) + return category_tree + + category_tree = list() + var/list/all_types = subtypesof(/datum/log_category) + var/list/known_categories = list() + var/list/sub_categories = list() + + // Assemble the master categories + for(var/datum/log_category/category_type as anything in all_types) + var/category = initial(category_type.category) + if(category in known_categories) + stack_trace("log category type '[category_type]' has duplicate category '[category]', skipping") + continue + + if(!initial(category_type.schema_version)) + stack_trace("log category type '[category_type]' does not have a valid schema version, skipping") + continue + + var/master_category = initial(category_type.master_category) + if(master_category) + sub_categories[master_category] += list(category_type) + continue + category_tree[category_type] = list() + + // Sort the sub categories + for(var/datum/log_category/master as anything in sub_categories) + if(!(master in category_tree)) + stack_trace("log category [master] is an invalid master category as it's a sub category") + continue + for(var/datum/log_category/sub_category as anything in sub_categories[master]) + if(initial(sub_category.secret) != initial(master.secret)) + stack_trace("log category [sub_category] has a secret status that differs from its master category [master]") + category_tree[master] += list(sub_category) + + return category_tree + +/// Initializes the given log category and populates the list of contained categories based on the sub category list +/datum/log_holder/proc/init_log_category(datum/log_category/category_type, list/datum/log_category/sub_categories) + var/datum/log_category/category_instance = new category_type + + var/list/contained_categories = list() + for(var/datum/log_category/sub_category as anything in sub_categories) + sub_category = new sub_category + var/sub_category_actual = sub_category.category + sub_category.master_category = category_instance + log_categories[sub_category_actual] = sub_category + + if(!semver_to_list(sub_category.schema_version)) + stack_trace("log category [sub_category_actual] has an invalid schema version '[sub_category.schema_version]'") + sub_category.schema_version = LOG_CATEGORY_SCHEMA_VERSION_NOT_SET + + contained_categories += sub_category_actual + + log_categories[category_instance.category] = category_instance + + if(!semver_to_list(category_instance.schema_version)) + stack_trace("log category [category_instance.category] has an invalid schema version '[category_instance.schema_version]'") + category_instance.schema_version = LOG_CATEGORY_SCHEMA_VERSION_NOT_SET + + contained_categories += category_instance.category + + var/list/category_header = list( + LOG_HEADER_INIT_TIMESTAMP = logging_start_timestamp, + LOG_HEADER_ROUND_ID = big_number_to_text(GLOB.round_id), + LOG_HEADER_SECRET = category_instance.secret, + LOG_HEADER_CATEGORY_LIST = contained_categories, + LOG_HEADER_CATEGORY = category_instance.category, + ) + + rustg_file_write("[json_encode(category_header)]\n", category_instance.get_output_file(null)) + if(human_readable_enabled) + rustg_file_write("\[[human_readable_timestamp()]\] Starting up round ID [round_id].\n - -------------------------\n", category_instance.get_output_file(null, "log")) + +/datum/log_holder/proc/unix_timestamp_string() // pending change to rust-g + return RUSTG_CALL(RUST_G, "unix_timestamp")() + +/datum/log_holder/proc/human_readable_timestamp(precision = 3) + var/start = time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss") + // now we grab the millis from the rustg timestamp + var/list/timestamp = splittext(unix_timestamp_string(), ".") + var/millis = timestamp[2] + if(length(millis) > precision) + millis = copytext(millis, 1, precision + 1) + return "[start].[millis]" + +/// Adds an entry to the given category, if the category is disabled it will not be logged. +/// If the category does not exist, we will CRASH and log to the error category. +/// the data list is optional and will be recursively json serialized. /datum/log_holder/proc/Log(category, message, list/data) - if(!initialized || shutdown) - CRASH("Attempted to perform logging before initializion or after shutdown!") + // This is Log because log is a byond internal proc + if(shutdown) + stack_trace("Performing logging after shutdown! This might not be functional in the future!") + // but for right now it's fine + + // do not include the message because these go into the runtime log and we might be secret! + if(!istext(message)) + message = "[message]" + stack_trace("Logging with a non-text message") + + if(!category) + category = LOG_CATEGORY_NOT_FOUND + stack_trace("Logging with a null or empty category") + + if(data && !islist(data)) + data = list("data" = data) + stack_trace("Logging with data this is not a list, it will be converted to a list with a single key 'data'") + + if(!initialized) // we are initialized during /world/proc/SetupLogging which is called in /world/New + waiting_log_calls += list(list(category, message, data)) + return if(disabled_categories[category]) return @@ -73,4 +200,49 @@ GLOBAL_PROTECT(logger) if(!log_category) Log(LOG_CATEGORY_NOT_FOUND, message, data) CRASH("Attempted to log to a category that doesn't exist! [category]") - log_category.add_entry(message, data) + + var/list/semver_store = null + if(length(data)) + semver_store = list() + data = recursive_jsonify(data, semver_store) + log_category.create_entry(message, data, semver_store) + +/// Recursively converts an associative list of datums into their jsonified(list) form +/datum/log_holder/proc/recursive_jsonify(list/data_list, list/semvers) + if(isnull(data_list)) + return null + + var/list/jsonified_list = list() + for(var/key in data_list) + var/datum/data = data_list[key] + + if(isnull(data)) + // do nothing - nulls are allowed + + else if(islist(data)) + data = recursive_jsonify(data, semvers) + + else if(isdatum(data)) + var/list/options_list = list( + SCHEMA_VERSION = LOG_CATEGORY_SCHEMA_VERSION_NOT_SET, + ) + + var/list/serialization_data = data.serialize_list(options_list, semvers) + var/current_semver = semvers[data.type] + if(!semver_to_list(current_semver)) + stack_trace("serialization of data had an invalid semver") + semvers[data.type] = LOG_CATEGORY_SCHEMA_VERSION_NOT_SET + + if(!length(serialization_data)) // serialize_list wasn't implemented, and errored + stack_trace("serialization data was empty") + continue + + data = recursive_jsonify(serialization_data, semvers) + + if(islist(data) && !length(data)) + stack_trace("recursive_jsonify got an empty list after serialization") + continue + + jsonified_list[key] = data + + return jsonified_list diff --git a/code/modules/mining/machine_silo.dm b/code/modules/mining/machine_silo.dm index 2077428bd34..f76cdcde796 100644 --- a/code/modules/mining/machine_silo.dm +++ b/code/modules/mining/machine_silo.dm @@ -241,7 +241,7 @@ GLOBAL_LIST_EMPTY(silo_access_logs) "raw_materials" = get_raw_materials(""), "direction" = amount < 0 ? "withdrawn" : "deposited", ) - GLOB.logger.Log( + logger.Log( LOG_CATEGORY_SILO, "[machine_name] in \[[AREACOORD(M)]\] [action] [abs(amount)]x [noun] | [get_raw_materials("")]", data, diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 14b2e758c68..02de4d93ae0 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -109,6 +109,17 @@ . = ..() tag = "mob_[next_mob_id++]" +/mob/serialize_list(list/options, list/semvers) + . = ..() + + .["tag"] = tag + .["name"] = name + .["ckey"] = ckey + .["key"] = key + + SET_SERIALIZATION_SEMVER(semvers, "1.0.0") + return . + /** * set every hud image in the given category active so other people with the given hud can see it. * Arguments: diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 66e2e18350b..1be76be1f35 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -513,7 +513,7 @@ "name" = offhand.name, ) - GLOB.logger.Log( + logger.Log( LOG_CATEGORY_TARGET_ZONE_SWITCH, "[key_name(src)] manually changed selected zone", data, diff --git a/code/modules/photography/_pictures.dm b/code/modules/photography/_pictures.dm index e6df79d0b6c..8c949892dbf 100644 --- a/code/modules/photography/_pictures.dm +++ b/code/modules/photography/_pictures.dm @@ -58,8 +58,9 @@ ic.Blend(small_img,ICON_OVERLAY, 13, 13) picture_icon = ic -/datum/picture/serialize_list(list/options) - . = list() +/datum/picture/serialize_list(list/options, list/semvers) + . = ..() + .["id"] = id .["desc"] = picture_desc .["name"] = picture_name @@ -69,9 +70,19 @@ .["blueprints"] = has_blueprints .["logpath"] = logpath + SET_SERIALIZATION_SEMVER(semvers, "1.0.0") + return . + /datum/picture/deserialize_list(list/input, list/options) + if((SCHEMA_VERSION in options) && (options[SCHEMA_VERSION] != "1.0.0")) + CRASH("Invalid schema version for datum/picture: [options[SCHEMA_VERSION]] (expected 1.0.0)") + . = ..() + if(!.) + return . + if(!input["logpath"] || !fexists(input["logpath"]) || !input["id"] || !input["pixel_size_x"] || !input["pixel_size_y"]) - return + return FALSE + picture_image = icon(file(input["logpath"])) logpath = input["logpath"] id = input["id"] @@ -85,7 +96,6 @@ picture_desc = input["desc"] if(input["name"]) picture_name = input["name"] - return src /proc/load_photo_from_disk(id, location) var/datum/picture/P = load_picture_from_disk(id) diff --git a/config/config.txt b/config/config.txt index cfb9391ae96..629da4ad8d7 100644 --- a/config/config.txt +++ b/config/config.txt @@ -3,6 +3,7 @@ $include game_options.txt $include dbconfig.txt $include comms.txt +$include logging.txt $include resources.txt $include interviews.txt $include lua.txt @@ -80,103 +81,6 @@ ENABLE_LOCALHOST_RANK ## If USE_LOW_LIVING_HOUR_INTERN is unhashed, players under this number of living hours have [Intern] added to their ID card. If this isn't set, uses USE_EXP_RESTRICTIONS_HEADS_HOURS instead. If that isn't set, it finally defaults to a hardcoded fallback of 15 hours. #USE_LOW_LIVING_HOUR_INTERN_HOURS 50 -## log OOC channel -LOG_OOC - -## log client Say -LOG_SAY - -## log admin actions -LOG_ADMIN - -## log admin chat -LOG_ADMINCHAT - -## log asset debug logs -#LOG_ASSET - -## log client access (logon/logoff) -LOG_ACCESS - -## Enables log entries for logins that failed due to suspicious circumstances (banned player, CID randomiser, spoofed BYOND version, etc.) to a dedicated file. -LOG_SUSPICIOUS_LOGIN - -## log game actions (start of round, results, etc.) -LOG_GAME - -## log player votes -LOG_VOTE - -## log manual target zone switching -LOG_ZONE_SWITCH - -## log client Whisper -LOG_WHISPER - -## log emotes -LOG_EMOTE - -## log attack messages -LOG_ATTACK - -## log pda messages -LOG_PDA - -## log uplink purchases -LOG_UPLINK - -## log telecomms messages -LOG_TELECOMMS - -## log prayers -LOG_PRAYER - -## Log Music requests -LOG_INTERNET_REQUEST - -## log silicons -LOG_SILICON - -## log economy actions -LOG_ECON - -## log traitor objectives -LOG_TRAITOR - -## log crew manifest to separate file -LOG_MANIFEST - -## log job divide debugging information -#LOG_JOB_DEBUG - -## log all world.Topic() calls -# LOG_WORLD_TOPIC - -## enables use of the proc twitterize() that lets you take a large list of strings and turn it into a JSON file of tweet sized strings. -## As an example of how this could be """useful""" look towards Poly (https://twitter.com/Poly_the_Parrot) -# LOG_TWITTER - -## Enable logging pictures -LOG_PICTURES - -## log mecha actions -# LOG_MECHA - -## log virus and actions -LOG_VIRUS - -## log shuttle actions -LOG_SHUTTLE - -## log tool interactions [/atom/proc/tool_act] -LOG_TOOLS - -## Log all timers on timer auto reset -# LOG_TIMERS_ON_BUCKET_RESET - -## log speech indicators -LOG_SPEECH_INDICATORS - ##Log camera pictures - Must have picture logging enabled PICTURE_LOGGING_CAMERA diff --git a/config/logging.txt b/config/logging.txt new file mode 100644 index 00000000000..2635197a796 --- /dev/null +++ b/config/logging.txt @@ -0,0 +1,103 @@ +### Config Flags for logging, compatibility flags are at the top + +## Whether or not admin/adminprivate logging is also sent to LOG_CATEGORY_GAME for legacy log parsers +LOGGING_COMPAT_ADMINPRIVATE + +## Should we also log entries as human readable +LOG_AS_HUMAN_READABLE + +### Actual logging flags + +## log client access (logon/logoff) +LOG_ACCESS + +## log admin actions +LOG_ADMIN + +## log admin chat +LOG_ADMINCHAT + +## log asset debug logs +# LOG_ASSET + +## log attack messages +LOG_ATTACK + +## log economy actions +LOG_ECON + +## log emotes +LOG_EMOTE + +## log game actions (start of round, results, etc.) +LOG_GAME + +## log job divide debugging information +# LOG_JOB_DEBUG + +## log crew manifest to separate file +LOG_MANIFEST + +## log mecha actions +# LOG_MECHA + +## log OOC channel +LOG_OOC + +## log pda messages +LOG_PDA + +## Enable logging pictures +LOG_PICTURES + +## log prayers +LOG_PRAYER + +## log client Say +LOG_SAY + +## log shuttle actions +LOG_SHUTTLE + +## log silicons +LOG_SILICON + +## log speech indicators +LOG_SPEECH_INDICATORS + +## Enables log entries for logins that failed due to suspicious circumstances (banned player, CID randomiser, spoofed BYOND version, etc.) to a dedicated file. +LOG_SUSPICIOUS_LOGIN + +## log telecomms messages +LOG_TELECOMMS + +## Log all timers on timer auto reset +# LOG_TIMERS_ON_BUCKET_RESET + +## log tool interactions [/atom/proc/tool_act] +LOG_TOOLS + +## log traitor objectives +LOG_TRAITOR + +## enables use of the proc twitterize() that lets you take a large list of strings and turn it into a JSON file of tweet sized strings. +## As an example of how this could be """useful""" look towards Poly (https://twitter.com/Poly_the_Parrot) +# LOG_TWITTER + +## log uplink purchases +LOG_UPLINK + +## log virus and actions +LOG_VIRUS + +## log player votes +LOG_VOTE + +## log client Whisper +LOG_WHISPER + +## log all world.Topic() calls +# LOG_WORLD_TOPIC + +## log manual target zone switching +LOG_ZONE_SWITCH diff --git a/tgstation.dme b/tgstation.dme index 9520e54f474..2be4a8edcc7 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3734,9 +3734,17 @@ #include "code\modules\lighting\lighting_turf.dm" #include "code\modules\lighting\static_lighting_area.dm" #include "code\modules\logging\log_category.dm" +#include "code\modules\logging\log_entry.dm" #include "code\modules\logging\log_holder.dm" +#include "code\modules\logging\categories\log_category_admin.dm" +#include "code\modules\logging\categories\log_category_compats.dm" +#include "code\modules\logging\categories\log_category_debug.dm" +#include "code\modules\logging\categories\log_category_game.dm" +#include "code\modules\logging\categories\log_category_misc.dm" +#include "code\modules\logging\categories\log_category_pda.dm" #include "code\modules\logging\categories\log_category_silo.dm" #include "code\modules\logging\categories\log_category_target_zone_switch.dm" +#include "code\modules\logging\categories\log_category_uplink.dm" #include "code\modules\mafia\_defines.dm" #include "code\modules\mafia\controller.dm" #include "code\modules\mafia\map_pieces.dm"