|
| 1 | +# 📄 JSONLogger |
| 2 | + |
| 3 | +<picture><img alt="Compatible from Swift 5.1 to 6." src="https://img.shields.io/badge/Swift-6.0_%7C_5.10--5.1-blue"></picture> |
| 4 | +<picture><img alt="Compatible with macOS, iOS, visionOS, tvOS and watchOS." src="https://img.shields.io/badge/Platforms-macOS_%7C_iOS_%7C_visionOS_%7C_tvOS_%7C_watchOS-blue"></picture> |
| 5 | +<picture><img alt="Compatible with Linux, Windows, WASI and Android." src="https://img.shields.io/badge/Platforms-Linux_%7C_Windows_%7C_WASI_%7C_Android-blue"></picture> |
| 6 | +[](<https://github.com/xcode-actions/json-logger/releases>) |
| 7 | + |
| 8 | +A simple [swift-log](<https://github.com/apple/swift-log>)-compatible logger which logs in JSON, one entry per line. |
| 9 | + |
| 10 | +## Usage 🤓 |
| 11 | + |
| 12 | +Bootstrap the LoggingSystem with `JSONLogger` before creating any logger: |
| 13 | +```swift |
| 14 | +LoggingSystem.bootstrap(JSONLogger.init, metadataProvider: nil/* or whatever you want */) |
| 15 | +``` |
| 16 | + |
| 17 | +To stream JSONSeq (RFC 7464), you can use a convenience initializer: |
| 18 | +```swift |
| 19 | +LoggingSystem.bootstrap(JSONLogger.initForJSONSeq, metadataProvider: nil/* or whatever you want */) |
| 20 | +``` |
| 21 | + |
| 22 | +## Log Format 📖 |
| 23 | + |
| 24 | +The JSONs generated by `JSONLogger` have the following fields: |
| 25 | +- `level`: The level of the log. |
| 26 | +Value is a `Logger.Level` (`trace`, `debug`, `info`, `notice`, `warning`, `error` or `critical`). |
| 27 | +- `message`: The message of the log (`String` value). |
| 28 | +- `metadata`: The structured metadata of the logs (`JSON` object value). |
| 29 | +The structure of the metadata values are kept as much as possible. |
| 30 | +- `date` or `date-1970`: The date of the log. |
| 31 | +If the log can be encoded properly using a `JSONEncoder` the `date` field is present and the value comes from the encoder. |
| 32 | +If there is an error encoding the log, the `date-1970` fallback is used with the value being a `Double` representing the time interval since January 1, 1970 (UNIX time). |
| 33 | +- `label`: The label of the logger (`String` value). |
| 34 | +- `source`: The source of the log (`String` value). |
| 35 | +- `file`: The file from which the log has been generated (`String` value). |
| 36 | +- `function`: The function from which the log has been generated (`String` value). |
| 37 | +- `line`: The line from which the log has been generated (`UInt` value). |
| 38 | + |
| 39 | +The JSONs should not have any newlines, however there are no _actual_ guarantees from `JSONEncoder`. |
| 40 | +In particular you can configure the encoder to pretty print the JSONs… |
| 41 | + |
| 42 | +## Configuration 🛠️ |
| 43 | + |
| 44 | +### Output FileHandle |
| 45 | + |
| 46 | +By default `JSONLogger` logs on `stdout`. |
| 47 | +You can configure it to log on any file descriptor using the `fileHandle` parameter at init time. |
| 48 | + |
| 49 | +### Line Prefix, Suffix and Separator |
| 50 | + |
| 51 | +`JSONLogger` allows you to choose: |
| 52 | +- A suffix for the JSON payload (the “newline separator;” defaults to `[0x0a]`, aka. a single UNIX newline); |
| 53 | +- A prefix (defaults to `[]`); |
| 54 | +- An inter-message separator (defaults to `[]`). |
| 55 | + |
| 56 | +The inter-message separator is logged after a JSON message, but only when a new message arrives, |
| 57 | + as opposed to the suffix which is logged after _every_ JSON message. |
| 58 | + |
| 59 | +As an example, if you log two messages, you’ll get the following output: |
| 60 | +```text |
| 61 | +prefix JSON1 suffix separator prefix JSON2 suffix |
| 62 | +``` |
| 63 | + |
| 64 | +An interesting configuration is to set the prefix to `[0x1e]` and the suffix to `[0x0a]`, which generates a JSONSeq stream. |
| 65 | + |
| 66 | +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. |
| 67 | +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). |
| 68 | + |
| 69 | +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). |
| 70 | + |
| 71 | +### JSON Encoder |
| 72 | + |
| 73 | +In order to log messages, `JSONLogger` will create a `LogLine` struct for every message, then encode this struct using a `JSONEncoder`. |
| 74 | + |
| 75 | +This encoder is customizable. |
| 76 | + |
| 77 | +All JSON messages generated by `JSONLogger` should be decodable by a `JSONDecoder` matching the configuration of the encoder. |
| 78 | + |
| 79 | +#### When the Encoder Fails |
| 80 | + |
| 81 | +When there is a problem encoding a log line (e.g. a date formatter fails), `JSONLogger` will fallback to manually encoding the log message. |
| 82 | + |
| 83 | +The manually encoded JSON differs slightly from the normal output: |
| 84 | +- The metadata are stripped and replaced by: |
| 85 | +```json |
| 86 | +{ |
| 87 | + "JSONLogger.LogInfo": "Original metadata removed (see JSONLogger doc)", |
| 88 | + "JSONLogger.LogError": ERROR_MESSAGE |
| 89 | +} |
| 90 | +``` |
| 91 | +- The log message is prefixed by `MANGLED LOG MESSAGE (see JSONLogger doc) -- `; |
| 92 | +- Instead of a `date` field, there is a `date-1970` field whose value is the `timeIntervalSince1970` of the date of the message. |
| 93 | +This ensures the encoding of the message _cannot_ fail. |
| 94 | + |
| 95 | +The modified log message should still be parsable by a `JSONEncoder` as a `LogLine`. |
| 96 | + |
| 97 | +### JSON Coders for `StringConvertible`s |
| 98 | + |
| 99 | +The type of the value of a metadata entry can be either a `String`, an `Array`, a `Dictionary` or `any StringConvertible`. |
| 100 | + |
| 101 | +The `String`, `Array` and `Dictionary` types are straightforward to encode in a JSON message. |
| 102 | + |
| 103 | +For the string convertibles, `JSONLogger` will use a pair of `JSONEncoder`/`JSONDecoder` if present. |
| 104 | +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. |
| 105 | + |
| 106 | +If the encoder/decoder pair is set to `nil`, the String representation of the string convertible is used. |
0 commit comments