A simple swift-log-compatible logger which logs in JSON, one entry per line.
Bootstrap the LoggingSystem with JSONLogger before creating any logger:
LoggingSystem.bootstrap(JSONLogger.init, metadataProvider: nil/* or whatever you want */)To stream JSONSeq (RFC 7464), you can use a convenience initializer:
LoggingSystem.bootstrap(JSONLogger.initForJSONSeq, metadataProvider: nil/* or whatever you want */)The JSONs generated by JSONLogger have the following fields:
level: The level of the log. Value is aLogger.Level(trace,debug,info,notice,warning,errororcritical).message: The message of the log (Stringvalue).metadata: The structured metadata of the logs (JSONobject value). The structure of the metadata values are kept as much as possible.dateordate-1970: The date of the log. If the log can be encoded properly using aJSONEncoderthedatefield is present and the value comes from the encoder. If there is an error encoding the log, thedate-1970fallback is used with the value being aDoublerepresenting the time interval since January 1, 1970 (UNIX time).label: The label of the logger (Stringvalue).source: The source of the log (Stringvalue).file: The file from which the log has been generated (Stringvalue).function: The function from which the log has been generated (Stringvalue).line: The line from which the log has been generated (UIntvalue).
The JSONs should not have any newlines, however there are no actual guarantees from JSONEncoder.
In particular you can configure the encoder to pretty print the JSONs…
By default JSONLogger logs on stdout.
You can configure it to log on any file descriptor using the fileHandle parameter at init time.
JSONLogger allows you to choose:
- A suffix for the JSON payload (the “newline separator;” defaults to
[0x0a], aka. a single UNIX newline); - A prefix (defaults to
[]); - An inter-message separator (defaults to
[]).
The inter-message separator is logged after a JSON message, but only when a new message arrives, as opposed to the suffix which is logged after every JSON message.
As an example, if you log two messages, you’ll get the following output:
prefix JSON1 suffix separator prefix JSON2 suffix
An interesting configuration is to set the prefix to [0x1e] and the suffix to [0x0a], which generates a JSONSeq stream.
Another interesting configuration is to set the inter-JSON separator to [0xff] or [0xfe] (or both), and set the prefix and suffix to an empty array.
The 0xff and 0xfe bytes should never appear in valid UTF-8 strings and can be used to separate JSON payloads (JSON requires UTF-8 encoding).
I’m not sure why JSONSeq does not do that but there must be a good reason (probably because the resulting output would not be valid UTF-8 anymore).
In order to log messages, JSONLogger will create a LogLine struct for every message, then encode this struct using a JSONEncoder.
This encoder is customizable.
All JSON messages generated by JSONLogger should be decodable by a JSONDecoder matching the configuration of the encoder.
When there is a problem encoding a log line (e.g. a date formatter fails), JSONLogger will fallback to manually encoding the log message.
The manually encoded JSON differs slightly from the normal output:
- The metadata are stripped and replaced by:
{
"JSONLogger.LogInfo": "Original metadata removed (see JSONLogger doc)",
"JSONLogger.LogError": ERROR_MESSAGE
}- The log message is prefixed by
MANGLED LOG MESSAGE (see JSONLogger doc) --; - Instead of a
datefield, there is adate-1970field whose value is thetimeIntervalSince1970of the date of the message. This ensures the encoding of the message cannot fail.
The modified log message should still be parsable by a JSONEncoder as a LogLine.
The type of the value of a metadata entry can be either a String, an Array, a Dictionary or any StringConvertible.
The String, Array and Dictionary types are straightforward to encode in a JSON message.
For the string convertibles, JSONLogger will use a pair of JSONEncoder/JSONDecoder if present.
The encoder will be used to encode the given object, and the decoder will be used to decode the resulting JSON into a generic JSON object, that will then be put in the LogLine struct.
If the encoder/decoder pair is set to nil, the String representation of the string convertible is used.