diff --git a/code/__HELPERS/_time.dm b/code/__HELPERS/_time.dm index b929ae8636b3..51f4abd04ebb 100644 --- a/code/__HELPERS/_time.dm +++ b/code/__HELPERS/_time.dm @@ -38,6 +38,10 @@ var/rollovercheck_last_timeofday = 0 wtime = world.time return time2text(wtime - GLOB.timezoneOffset, format) +/// Show the current date and time in ISO 8601 format +/proc/iso_time_stamp() + return "[time2text(world.realtime, "YYYY-MM-DD")]T[time2text(world.realtime, "hh:mm:ss")]Z" + /proc/time_stamp() // Shows current GMT time return time2text(world.timeofday, "hh:mm:ss") diff --git a/code/__HELPERS/logging.dm b/code/__HELPERS/logging.dm index 5ecbff108725..c835806e2672 100644 --- a/code/__HELPERS/logging.dm +++ b/code/__HELPERS/logging.dm @@ -314,3 +314,44 @@ GLOBAL_PROTECT(config_error_log) /proc/shutdown_logging() rustg_log_close_all() GLOB.logger.shutdown_logging() + +/// Primitive json logging handler for proper log intake to OpenSearch until we figure out how exactly we want to structure this +/// example usage: log_json_event(list(damage = 4), logtype = "ATTACK", module = "combat", eventtype = "hit", actor = attacking_mob, interactee = attacked_mob, location = get_turf(attacked_mob), "[attacking_mob] hit [attacked_mob] in [get_area(attacked_mob)]") +/// This should only be used for high profile logs currently because of likely performance overhead +/proc/log_json_event(message, logtype = "ADMIN", module = "unspecified", eventtype = "unspecified", actor, interactee, atom/location, list/data) + var/list/event + if(data) + event = data.Copy() + else + event = list() + event["logtype"] = logtype + event["module"] = module + event["eventtype"] = eventtype + if(actor) + if(istype(actor, /client)) + var/client/client_actor = actor + event["actor_key"] = client_actor.ckey + event["actor_mob"] = client_actor.mob?.name + else if(istype(actor, /mob)) + var/mob/mob_actor = actor + event["actor_key"] = mob_actor.logging_ckey || "*null*" + event["actor_mob"] = mob_actor.name + if(interactee) + if(istype(interactee, /client)) + var/client/client_interactee = interactee + event["interactee_key"] = client_interactee.ckey + event["interactee_mob"] = client_interactee.mob?.name + else if(istype(interactee, /mob)) + var/mob/mob_interactee = interactee + event["interactee_key"] = mob_interactee.logging_ckey || "*null*" + event["interactee_mob"] = mob_interactee.name + if(isatom(location)) + var/turf/turf = get_turf(location) + if(turf) + event["x"] = turf.x + event["y"] = turf.y + event["z"] = turf.z + if(message) + event["message"] = message + event["timestamp"] = iso_time_stamp() + WRITE_LOG_NO_FORMAT(GLOB.json_event_log, "[json_encode(event)]\n") diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index e467631c915e..c503a7b7d22a 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -132,6 +132,7 @@ var/global/cas_tracking_id_increment = 0 //this var used to assign unique tracki if(round_statistics) round_statistics.track_round_end() log_game("Round end result: [round_finished]") + log_json_event("Round completed: [round_finished]", logtype = "STATS", module = "game_mode", eventtype = "round_end", data = list("duration" = ROUND_TIME, "result" = round_finished)) to_chat_spaced(world, margin_top = 2, type = MESSAGE_TYPE_SYSTEM, html = SPAN_ROUNDHEADER("|Round Complete|")) to_chat_spaced(world, type = MESSAGE_TYPE_SYSTEM, html = SPAN_ROUNDBODY("Thus ends the story of the brave men and women of the [MAIN_SHIP_NAME] and their struggle on [SSmapping.configs[GROUND_MAP].map_name].\nThe game-mode was: [master_mode]!\n[CONFIG_GET(string/endofroundblurb)]")) diff --git a/code/game/jobs/role_authority.dm b/code/game/jobs/role_authority.dm index dc9865f8d6e6..051dbb67d018 100644 --- a/code/game/jobs/role_authority.dm +++ b/code/game/jobs/role_authority.dm @@ -218,6 +218,8 @@ I hope it's easier to tell what the heck this proc is even doing, unlike previou unassigned_players = shuffle(unassigned_players, 1) //Shuffle the players. + var/ready_players = length(unassigned_players) + var/list/json_data = list("ready_players" = ready_players) // How many positions do we open based on total pop for(var/i in roles_by_name) @@ -274,6 +276,7 @@ I hope it's easier to tell what the heck this proc is even doing, unlike previou var/list/roles_left = assign_roles(temp_roles_for_mode, unassigned_players) var/alternate_option_assigned = 0; + var/lobbied = 0 for(var/mob/new_player/M in unassigned_players) switch(M.client.prefs.alternate_option) if(GET_RANDOM_JOB) @@ -287,8 +290,11 @@ I hope it's easier to tell what the heck this proc is even doing, unlike previou assign_role(M, temp_roles_for_mode[JOB_XENOMORPH]) if(RETURN_TO_LOBBY) M.ready = 0 + lobbied++ unassigned_players -= M + json_data["lobbied"] = lobbied + if(length(unassigned_players)) to_world(SPAN_DEBUG("Error setting up jobs, unassigned_players still has players left. Length of: [length(unassigned_players)].")) log_debug("Error setting up jobs, unassigned_players still has players left. Length of: [length(unassigned_players)].") @@ -300,6 +306,10 @@ I hope it's easier to tell what the heck this proc is even doing, unlike previou if(istype(hive) && istype(XJ)) hive.stored_larva += max(0, (XJ.total_positions - XJ.current_positions) \ + (XJ.calculate_extra_spawn_positions(alternate_option_assigned))) + json_data["xenos"] = XJ.current_positions + json_data["larvas"] = hive.stored_larva + + log_json_event("Assigning roundstart jobs with [ready_players] players readied up.", logtype = "STATS", module = "roles", eventtype = "job_assignments", data = json_data) /*===============================================================*/ diff --git a/code/game/world.dm b/code/game/world.dm index f5388ed6fd52..9558101dad30 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -138,6 +138,7 @@ var/world_topic_spam_protect_time = world.timeofday GLOB.round_stats = "[GLOB.log_directory]/round_stats.log" GLOB.scheduler_stats = "[GLOB.log_directory]/round_scheduler_stats.log" GLOB.mutator_logs = "[GLOB.log_directory]/mutator_logs.log" + GLOB.json_event_log = "[GLOB.log_directory]/json_event_log.log" start_log(GLOB.tgui_log) start_log(GLOB.world_href_log) @@ -147,6 +148,7 @@ var/world_topic_spam_protect_time = world.timeofday start_log(GLOB.round_stats) start_log(GLOB.scheduler_stats) start_log(GLOB.mutator_logs) + log_json_event(logtype = "WORLD", module = "logging", eventtype = "logstart", data = list("round_id" = GLOB.round_id)) if(fexists(GLOB.config_error_log)) fcopy(GLOB.config_error_log, "[GLOB.log_directory]/config_error.log") diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index 5f10b8d24d22..50172eb56bdb 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -302,6 +302,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) var/list/activemins = adm["present"] var/admin_number_present = activemins.len + log_json_event("New ahelp ticket", logtype = "ADMIN", module = "adminhelp", eventtype = "new_ticket", actor = initiator, data = list("ticket_id" = id, "ahelp_message" = message, "admins_present" = admin_number_present)) log_admin_private("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.") if(admin_number_present <= 0) to_chat(initiator, SPAN_NOTICE("No active admins are online, your adminhelp was sent to admins who are available through IRC or Discord."), confidential = TRUE) diff --git a/code/modules/logging/global_logs.dm b/code/modules/logging/global_logs.dm index ff384fe537c0..b5c4b237514d 100644 --- a/code/modules/logging/global_logs.dm +++ b/code/modules/logging/global_logs.dm @@ -28,6 +28,9 @@ GLOBAL_PROTECT(scheduler_stats) GLOBAL_VAR(mutator_logs) GLOBAL_PROTECT(mutator_logs) +GLOBAL_VAR(json_event_log) +GLOBAL_PROTECT(json_event_log) + GLOBAL_VAR(round_stats) GLOBAL_PROTECT(round_stats)