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
🆑
server: All logs are now formatted in json, excluding perf and
investigations
/🆑

---------

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: tattle <66640614+dragomagol@users.noreply.github.com>
Co-authored-by: Kyle Spier-Swenson <kyleshome@gmail.com>
Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com>
This commit is contained in:
Zephyr
2023-05-22 14:51:00 +12:00
committed by GitHub
co-authored by tattle Kyle Spier-Swenson GoldenAlpharex
parent 3df5d3b42b
commit fbec9c14e9
49 changed files with 1055 additions and 353 deletions
+7 -1
View File
@@ -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;\
}
+84 -13
View File
@@ -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
+1 -1
View File
@@ -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)
+26 -1
View File
@@ -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)
+20 -18
View File
@@ -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)
+12 -18
View File
@@ -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)
+2 -3
View File
@@ -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
+23 -29
View File
@@ -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)
+2 -2
View File
@@ -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)
+2 -3
View File
@@ -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)
+16 -24
View File
@@ -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)
+3 -2
View File
@@ -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
))
+2 -3
View File
@@ -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)
+4 -5
View File
@@ -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
+2 -2
View File
@@ -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)
+6 -11
View File
@@ -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)
+2 -3
View File
@@ -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)
+8 -12
View File
@@ -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)
+1 -2
View File
@@ -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)
+3 -3
View File
@@ -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)
+2 -3
View File
@@ -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()
+12
View File
@@ -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]),
)
+4 -33
View File
@@ -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
@@ -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
+11 -5
View File
@@ -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.
+14 -8
View File
@@ -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.
+3 -1
View File
@@ -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
+10
View File
@@ -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
+16
View File
@@ -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.
*/
+19
View File
@@ -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)
+12 -1
View File
@@ -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
+5 -3
View File
@@ -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))
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
+39 -15
View File
@@ -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))
+114
View File
@@ -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)]")
+197 -25
View File
@@ -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
+1 -1
View File
@@ -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,
+11
View File
@@ -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:
+1 -1
View File
@@ -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,
+14 -4
View File
@@ -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)
+1 -97
View File
@@ -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
+103
View File
@@ -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
+8
View File
@@ -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"