# Logging overhaul ## Expected outcome Allow machine consumable output of log entries, while retaining the human readable (to an extent) logs that already exist. Formalise the content of any specific log entry, this comes at a cost to developer time, but means logs are more consistent, easier to use for those not adding new logs and can be tagged with more relevant metadata. Allow custom output mechanisms for server owners that might want to output logs to say, a database, where it can be consumed or used in webservices or other needs. Individual log entries contain enough information to be parsed completely context free, without needing to know what round this log file is for, or what date at which log entry structures changed ## The plan 1. Create a set of logging_entry datum subtypes 2. The base logging_entry datum would have preprovided fields that are automatically added to every logging entry. 3. every logging_entry datum must implement a to_text method, that writes a human readable form of the log entry, SSlog by default will always write to a relevant logging file this textual form, with a config flag available to turn this off if you only wish to present logs to end users via a webservice or other consuming party. 4. Developer create an instance of this logging entry datum, configure it and then tell it to be logged 5. The log subsystem stores the list of datums and periodically flushes them through a middleware output, of which there may be one, or several. 6. For example, there may be a json output middleware that writes the log entry as a json object. Or a database output middleware that writes them to a database table ## illustrative example Here's what it would look like to create an instance with the default defacto fields, it's expected that this would auto decorate with the category of log, user and location data, (based on the objects passed in). ``` var/entry = new /datum/log_entry/communication(source, target, location) //Configuring additional metadata entry.additional_tags("administrative", "radio") // Communication specific data entry.source_radio_channel(1337) entry.add_message_text(somesaymessage) //commit the entry to be written out at a later date by the SSlogging subsystem entry.log() ``` Note: it's highly recommended to not store a reference to any of the objects passed in, as this could cause all sorts of GC pain because the log entry is not guaranteed to be written out immediately, when constructing, convert all relevant data to text within the constructor. ### output forms The expected text output would be something like `COMMS: Flargle sent a communication message at (1, 232, 255) on channel 1337, "somesaymessage"` It's not expected that text output contains all fields, as it's meant to be a reference for administrative users, who have preexisting state in their heads about a given round. And as json ``` { timestamp: 1632793849, version: "2.3.1", roundid: "69420". servername: "sugondese" category: "communication", private: 0 location: [1, 232, 255], tags: ["administrative", "communications", "radio"] source: "Medbay Stutter Nurse 100", source_ckey: "A developer", target: "" target_ckey:"" "extended_fields": { "radiomessage" : "somesaymessage", "channel" : "1337", } } ``` # Field structure while developers are free to add as many fields as they find reasonable, there are a required set of fields that are part of the default log_entry datum. ## Required #### timestamp a unix timestamp of the real server time this log was created #### version a semantic versioning string that indicates the structure of this log entry - https://semver.org/ The form is roughly - MAJOR version when you make incompatible API changes, - MINOR version when you add functionality in a backwards compatible manner, and - PATCH version when you make backwards compatible bug fixes. It's extremely unlikely the PATCH field will be used (perhaps we should mandate incrementing it any time the log datum is touched?), but when you add new fields to a log datum, the MINOR version should be incremented in the datum, when you take away a field, the MAJOR version should be incremented, then parsers can identify if an entry is parseable (albeit potentially not including new fields in your service), or not parseable (fields will not be present). #### roundid The round id of the round that this log entry was created in, a numeric id #### server unique name The unique name of the server this log entry was recorded on, a text string #### category The type of the log entry, this is an upercase text string, example categories will be things like, SAY, OOC, GAME, ATTACK, BAN etc #### private An integer flag, of 1 or 0, if set to 1 the field is considered private and should be ommitted from any public facing logs, examples would include things like admin say messages, or other admin related information. #### Location The x,y,z map coordinate of where the log entry was emitted from, will be (0,0,0) if the event comes from an abstract system #### tags a list of extra tags that apply to this message, it's expected datums that extend the parent log_entry datum will provide a predefined set of relevant tags, but developers will also be able to provide more tags in the constructor of the datum, and should be encouraged to do so. For example, you may wish to tag anything with interest to admins as `administrative` so log consumers can quickly find and target these entries, or if a log entry is associated with something people do to grief, you may wish to tag it as `badbehaviour`. I envision the log_entry datum having helper methods such as `tag_behavioural()` which can be quickly called to add the relevant tags. ## Optional Fields in this section may not be included by developers in which case they should be written as a null representation in the output systems format. #### source The source of this message, this should be the objects textual name as it would be recognised by administrators in game #### source_ckey If a player is associated with this action, this should be their ckey #### target The target of this message, this should be the targets textual name as it would be recognised by administrators in game #### target_ckey If a player is targeted by the action described in this log entry, this should be their ckey ## Extending fields Fields outside the default required, or optional set should be stored as an associative array of key -> data, when representing these logging output subsystems should be aiming to clearly delinate that they are not present on all entries, for example in the json form it may be ``` "extended_fields": { "radiomessage" : "You are a big dum dum I hate you admin", "channel" : "ooc", } ``` Removing an extended field from the list of fields in logs is a MAJOR version change extended fields are non optional and should always be present if the datum specifies some. # Default log outputs Two default log outputs are imagined by default, the first being a simple textual log created by calling to_text on every queued datum and dispatching the result to the relevant CHANNEL.log file The second will be a json output, where the log output is single file json array, containing objects, each object being the json representation of the logging datum, with default fields and optional fields. It's strongly recommended to write all fields out to the json objects, basically, make it as easy as possible to parse logs statelessly, by having the version, roundid, server and timestamp attached to every single log entry, so it doesn't have to be constructed as parser state.