Skip to content

Commit dbfe16d

Browse files
committed
Merge branch 'tech/frizlab/readme' into develop
2 parents c424e13 + 18ac9b1 commit dbfe16d

File tree

8 files changed

+153
-14
lines changed

8 files changed

+153
-14
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ let package = Package(
1515
.target(name: "JSONLogger", dependencies: [
1616
.product(name: "GenericJSON", package: "generic-json"),
1717
.product(name: "Logging", package: "swift-log"),
18-
], path: "Sources"),
18+
], path: "Sources", exclude: ["JSONLogger+WithSendable.swift"]),
1919
.testTarget(name: "JSONLoggerTests", dependencies: ["JSONLogger"]),
2020
]
2121
)

[email protected]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ let package = Package(
2424
.target(name: "JSONLogger", dependencies: [
2525
.product(name: "GenericJSON", package: "generic-json"),
2626
.product(name: "Logging", package: "swift-log"),
27-
], path: "Sources", swiftSettings: swiftSettings),
27+
], path: "Sources", exclude: ["JSONLogger+NoSendable.swift"], swiftSettings: swiftSettings),
2828
.testTarget(name: "JSONLoggerTests", dependencies: ["JSONLogger"], swiftSettings: swiftSettings),
2929
]
3030
)

Readme.adoc

Lines changed: 0 additions & 10 deletions
This file was deleted.

Readme.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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://img.shields.io/github/v/release/xcode-actions/json-logger>)](<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.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Foundation
2+
3+
import Logging
4+
5+
6+
7+
extension JSONLogger {
8+
9+
public init(label: String, metadataProvider: Logger.MetadataProvider? = LoggingSystem.metadataProvider) {
10+
/* The fileHandle argument should be present to avoid infinite recursion
11+
* but its value should be the same as the default value of the initializer we’re calling (for API consistency). */
12+
self.init(label: label, fileHandle: .standardOutput, metadataProvider: metadataProvider)
13+
}
14+
15+
public static func initForJSONSeq(label: String, metadataProvider: Logger.MetadataProvider? = LoggingSystem.metadataProvider) -> JSONLogger {
16+
Self.forJSONSeq(label: label, metadataProvider: metadataProvider)
17+
}
18+
19+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Foundation
2+
3+
import Logging
4+
5+
6+
7+
/* The @Sendable attribute is only available starting at Swift 5.5.
8+
* We make these methods only available starting at Swift 5.8 for our convenience (avoids creating another Package@swift-... file)
9+
* and because for Swift <5.8 the non-@Sendable variants of the methods are available. */
10+
extension JSONLogger {
11+
12+
@Sendable
13+
public init(label: String, metadataProvider: Logger.MetadataProvider? = LoggingSystem.metadataProvider) {
14+
/* The fileHandle argument should be present to avoid infinite recursion
15+
* but its value should be the same as the default value of the initializer we’re calling (for API consistency). */
16+
self.init(label: label, fileHandle: .standardOutput, metadataProvider: metadataProvider)
17+
}
18+
19+
@Sendable
20+
public static func initForJSONSeq(label: String, metadataProvider: Logger.MetadataProvider? = LoggingSystem.metadataProvider) -> JSONLogger {
21+
Self.forJSONSeq(label: label, metadataProvider: metadataProvider)
22+
}
23+
24+
}

Sources/JSONLogger.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Logging
66

77

88
/**
9-
A logger that logs it’s messages to stdout in the JSON format, one log per line.
9+
A logger that logs its messages to stdout in the JSON format, one log per line.
1010

1111
The end of line separator is actually customizable, and can be any sequence of bytes.
1212
By default it’s “`\n`”.

Tests/JSONLoggerTests/JSONLoggerTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ final class JSONLoggerTests : XCTestCase {
2929
}()
3030

3131
override class func setUp() {
32-
LoggingSystem.bootstrap{ JSONLogger(label: $0) }
32+
LoggingSystem.bootstrap(JSONLogger.init, metadataProvider: nil)
3333
}
3434

3535
/* From <https://apple.github.io/swift-log/docs/current/Logging/Protocols/LogHandler.html#treat-log-level-amp-metadata-as-values>. */

0 commit comments

Comments
 (0)