@@ -27,7 +27,7 @@ import Logging
2727
2828 This configuration is interesting mostly to generate `json-seq` stream.
2929 To do this, set the inter-JSON separator to `[]`, the prefix to `[0x1e]` and the suffix to `[0x0a]`,
30- or use the convenience ``JSONLogger/ forJSONSeq()``.
30+ or use the convenience ``forJSONSeq(on:label:metadataProvider: )``.
3131
3232 Finally, another interesting configuration is to set the separator to `[0xff]` or `[0xfe]`.
3333 These bytes should not appear in valid UTF-8 strings and should be able to be used to separate JSON payloads.
@@ -37,6 +37,37 @@ import Logging
3737 The output file descriptor is also customizable and is `stdout` by default. */
3838public struct JSONLogger : LogHandler {
3939
40+ public static let defaultJSONEncoder : JSONEncoder = {
41+ let res = JSONEncoder ( )
42+ res. outputFormatting = [ . withoutEscapingSlashes]
43+ res. keyEncodingStrategy = . useDefaultKeys
44+ res. dateEncodingStrategy = . iso8601
45+ res. dataEncodingStrategy = . base64
46+ res. nonConformingFloatEncodingStrategy = . convertToString( positiveInfinity: " +inf " , negativeInfinity: " -inf " , nan: " nan " )
47+ return res
48+ } ( )
49+
50+ public static let defaultJSONCodersForStringConvertibles : ( JSONEncoder , JSONDecoder ) = {
51+ let encoder = JSONEncoder ( )
52+ encoder. outputFormatting = [ . withoutEscapingSlashes]
53+ encoder. keyEncodingStrategy = . useDefaultKeys
54+ encoder. dateEncodingStrategy = . iso8601
55+ encoder. dataEncodingStrategy = . base64
56+ encoder. nonConformingFloatEncodingStrategy = . throw
57+ let decoder = JSONDecoder ( )
58+ if #available( macOS 12 . 0 , tvOS 15 . 0 , iOS 15 . 0 , watchOS 8 . 0 , * ) {
59+ decoder. allowsJSON5 = false
60+ }
61+ decoder. keyDecodingStrategy = . useDefaultKeys
62+ decoder. dateDecodingStrategy = . iso8601
63+ decoder. dataDecodingStrategy = . base64
64+ if #available( macOS 12 . 0 , tvOS 15 . 0 , iOS 15 . 0 , watchOS 8 . 0 , * ) {
65+ decoder. assumesTopLevelDictionary = false
66+ }
67+ decoder. nonConformingFloatDecodingStrategy = . throw
68+ return ( encoder, decoder)
69+ } ( )
70+
4071 public var logLevel : Logger . Level = . info
4172
4273 public var metadata : Logger . Metadata = [ : ] {
@@ -52,21 +83,28 @@ public struct JSONLogger : LogHandler {
5283 public let suffix : Data
5384
5485 /**
55- If `true `, the `Encodable` properties in the metadata will be encoded and kept structured in the resulting log line .
56- If the encoding fails or this property is set to `false ` the String value will be used. */
57- public var tryEncodingStringConvertibles : Bool
86+ If non-`nil `, the `Encodable` stringConvertible properties in the metadata will be encoded as `JSON` using the `JSONEncoder` and `JSONDecoder` .
87+ If the encoding fails or this property is set to `nil ` the String value will be used. */
88+ public var jsonCodersForStringConvertibles : ( JSONEncoder , JSONDecoder ) ?
5889
5990 public static func forJSONSeq( on fd: FileDescriptor = . standardError, label: String , metadataProvider: Logger . MetadataProvider ? = LoggingSystem . metadataProvider) -> Self {
6091 return Self ( label: label, fd: fd, lineSeparator: Data ( ) , prefix: Data ( [ 0x1e ] ) , suffix: Data ( [ 0x0a ] ) , metadataProvider: metadataProvider)
6192 }
6293
63- public init ( label: String , fd: FileDescriptor = . standardError, lineSeparator: Data = Data ( " \n " . utf8) , prefix: Data = Data ( ) , suffix: Data = Data ( ) , tryEncodingStringConvertibles: Bool = true , metadataProvider: Logger . MetadataProvider ? = LoggingSystem . metadataProvider) {
94+ public init (
95+ label: String ,
96+ fd: FileDescriptor = . standardError,
97+ lineSeparator: Data = Data ( " \n " . utf8) , prefix: Data = Data ( ) , suffix: Data = Data ( ) ,
98+ jsonEncoder: JSONEncoder = Self . defaultJSONEncoder,
99+ jsonCodersForStringConvertibles: ( JSONEncoder , JSONDecoder ) = Self . defaultJSONCodersForStringConvertibles,
100+ metadataProvider: Logger . MetadataProvider ? = LoggingSystem . metadataProvider
101+ ) {
64102 self . label = label
65103 self . outputFileDescriptor = fd
66104 self . lineSeparator = lineSeparator
67105 self . prefix = prefix
68106 self . suffix = suffix
69- self . tryEncodingStringConvertibles = tryEncodingStringConvertibles
107+ self . jsonCodersForStringConvertibles = jsonCodersForStringConvertibles
70108
71109 self . metadataProvider = metadataProvider
72110 }
@@ -164,8 +202,12 @@ extension JSONLogger {
164202 case let . array( array) : . array ( array . map ( jsonMetadataValue ( _: ) ) )
165203 case let . dictionary( dictionary) : . object( dictionary. mapValues ( jsonMetadataValue ( _: ) ) )
166204 case let . stringConvertible( s) :
167- if tryEncodingStringConvertibles, let c = s as? any Encodable , let encoded = try ? JSON ( encodable: c) {
168- encoded
205+ if let ( encoder, decoder) = jsonCodersForStringConvertibles,
206+ let c = s as? any Encodable ,
207+ let data = try ? encoder. encode ( c) ,
208+ let json = try ? decoder. decode ( JSON . self, from: data)
209+ {
210+ json
169211 } else {
170212 . string( s. description)
171213 }
0 commit comments