Files
CHOMPStation2/code/modules/logging/log_entry.dm
2025-09-14 20:05:26 +02:00

128 lines
4.0 KiB
Plaintext

// 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
/// Bitfield that describes how exactly to log stuff exactly
/// See code/__DEFINES/logging/dm
var/flags = NONE
/// 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, flags, list/data, list/semver_store)
..()
src.id = next_id++
src.timestamp = timestamp
src.category = category
src.flags = flags
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)
var/output = ""
if(format)
output += "\[[timestamp]\] [uppertext(category)]: [message]"
else
output += "[uppertext(category)]: [message]"
if(flags & ENTRY_USE_DATA_W_READABLE)
output += json_encode(data, JSON_PRETTY_PRINT)
return output
#define MANUAL_JSON_ENTRY(list, key, value) list.Add("\"[key]\":[(!isnull(value)) ? json_encode(value) : "null"]")
/// Converts the log entry to a JSON string.
/datum/log_entry/proc/to_json_text()
// I do not trust byond's json encoder, and need to ensure the order doesn't change.
var/list/json_entries = list()
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_TIMESTAMP, timestamp)
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_CATEGORY, category)
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_MESSAGE, message)
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_DATA, data)
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_WORLD_STATE, world.get_world_state_for_logging())
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_SEMVER_STORE, semver_store)
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_ID, id)
MANUAL_JSON_ENTRY(json_entries, LOG_ENTRY_KEY_SCHEMA_VERSION, schema_version)
return "{[json_entries.Join(",")]}"
#undef MANUAL_JSON_ENTRY
#define CHECK_AND_TRY_FILE_ERROR_RECOVERY(file) \
var/static/in_error_recovery = FALSE; \
if(!fexists(##file)) { \
if(in_error_recovery) { \
in_error_recovery = FALSE; \
CRASH("Failed to error recover log file: [file]"); \
}; \
in_error_recovery = TRUE; \
logger.Log(LOG_CATEGORY_INTERNAL_ERROR, "attempting to perform file error recovery: [file]"); \
logger.init_category_file(logger.log_categories[category]); \
call(src, __PROC__)(arglist(args)); \
return; \
}; \
in_error_recovery = FALSE;
/// Writes the log entry to a file.
/datum/log_entry/proc/write_entry_to_file(file)
CHECK_AND_TRY_FILE_ERROR_RECOVERY(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)
CHECK_AND_TRY_FILE_ERROR_RECOVERY(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)]")
#undef CHECK_AND_TRY_FILE_ERROR_RECOVERY