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
,error
orcritical
).message
: The message of the log (String
value).metadata
: The structured metadata of the logs (JSON
object value). The structure of the metadata values are kept as much as possible.date
ordate-1970
: The date of the log. If the log can be encoded properly using aJSONEncoder
thedate
field is present and the value comes from the encoder. If there is an error encoding the log, thedate-1970
fallback is used with the value being aDouble
representing the time interval since January 1, 1970 (UNIX time).label
: The label of the logger (String
value).source
: The source of the log (String
value).file
: The file from which the log has been generated (String
value).function
: The function from which the log has been generated (String
value).line
: The line from which the log has been generated (UInt
value).
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
date
field, there is adate-1970
field whose value is thetimeIntervalSince1970
of 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.