@@ -43,6 +43,11 @@ public struct Logger {
43
43
/// An identifier of the creator of this `Logger`.
44
44
public let label : String
45
45
46
+ /// The metadata provider this logger was created with.
47
+ public var metadataProvider : Logger . MetadataProvider ? {
48
+ return self . handler. metadataProvider
49
+ }
50
+
46
51
internal init ( label: String , _ handler: LogHandler ) {
47
52
self . label = label
48
53
self . handler = handler
@@ -264,6 +269,7 @@ extension Logger {
264
269
}
265
270
266
271
#else
272
+ @inlinable
267
273
public func debug( _ message: @autoclosure ( ) -> Logger . Message ,
268
274
metadata: @autoclosure ( ) -> Logger . Metadata ? = nil ,
269
275
source: @autoclosure ( ) -> String ? = nil ,
@@ -435,6 +441,7 @@ extension Logger {
435
441
}
436
442
437
443
#else
444
+ @inlinable
438
445
public func notice( _ message: @autoclosure ( ) -> Logger . Message ,
439
446
metadata: @autoclosure ( ) -> Logger . Metadata ? = nil ,
440
447
file: String = #file, function: String = #function, line: UInt = #line) {
@@ -653,7 +660,8 @@ extension Logger {
653
660
/// configured. `LoggingSystem` is set up just once in a given program to set up the desired logging backend
654
661
/// implementation.
655
662
public enum LoggingSystem {
656
- private static let _factory = FactoryBox ( StreamLogHandler . standardOutput)
663
+ private static let _factory = FactoryBox { label, _ in StreamLogHandler . standardOutput ( label: label) }
664
+ private static let _metadataProviderFactory = MetadataProviderBox ( nil )
657
665
658
666
/// `bootstrap` is a one-time configuration function which globally selects the desired logging backend
659
667
/// implementation. `bootstrap` can be called at maximum once in any given program, calling it more than once will
@@ -662,36 +670,106 @@ public enum LoggingSystem {
662
670
/// - parameters:
663
671
/// - factory: A closure that given a `Logger` identifier, produces an instance of the `LogHandler`.
664
672
public static func bootstrap( _ factory: @escaping ( String ) -> LogHandler ) {
673
+ self . _factory. replaceFactory ( { label, _ in
674
+ factory ( label)
675
+ } , validate: true )
676
+ }
677
+
678
+ /// `bootstrap` is a one-time configuration function which globally selects the desired logging backend
679
+ /// implementation.
680
+ ///
681
+ /// - Warning:
682
+ /// `bootstrap` can be called at maximum once in any given program, calling it more than once will
683
+ /// lead to undefined behavior, most likely a crash.
684
+ ///
685
+ /// - parameters:
686
+ /// - metadataProvider: The `MetadataProvider` used to inject runtime-generated metadata from the execution context.
687
+ /// - factory: A closure that given a `Logger` identifier, produces an instance of the `LogHandler`.
688
+ public static func bootstrap( _ factory: @escaping ( String , Logger . MetadataProvider ? ) -> LogHandler ,
689
+ metadataProvider: Logger . MetadataProvider ? ) {
690
+ self . _metadataProviderFactory. replaceMetadataProvider ( metadataProvider, validate: true )
665
691
self . _factory. replaceFactory ( factory, validate: true )
666
692
}
667
693
668
- // for our testing we want to allow multiple bootstraping
694
+ // for our testing we want to allow multiple bootstrapping
669
695
internal static func bootstrapInternal( _ factory: @escaping ( String ) -> LogHandler ) {
696
+ self . _metadataProviderFactory. replaceMetadataProvider ( nil , validate: false )
697
+ self . _factory. replaceFactory ( { label, _ in
698
+ factory ( label)
699
+ } , validate: false )
700
+ }
701
+
702
+ // for our testing we want to allow multiple bootstrapping
703
+ internal static func bootstrapInternal( _ factory: @escaping ( String , Logger . MetadataProvider ? ) -> LogHandler ,
704
+ metadataProvider: Logger . MetadataProvider ? ) {
705
+ self . _metadataProviderFactory. replaceMetadataProvider ( metadataProvider, validate: false )
670
706
self . _factory. replaceFactory ( factory, validate: false )
671
707
}
672
708
673
- fileprivate static var factory : ( String ) -> LogHandler {
674
- return self . _factory. underlying
709
+ fileprivate static var factory : ( String , Logger . MetadataProvider ? ) -> LogHandler {
710
+ return { label, metadataProvider in
711
+ var handler = self . _factory. underlying ( label, metadataProvider)
712
+ handler. metadataProvider = metadataProvider
713
+ return handler
714
+ }
715
+ }
716
+
717
+ /// System wide ``Logger/MetadataProvider`` that was configured during the logging system's `bootstrap`.
718
+ ///
719
+ /// When creating a ``Logger`` using the plain ``Logger/init(label:)`` initializer, this metadata provider
720
+ /// will be provided to it.
721
+ ///
722
+ /// When using custom log handler factories, make sure to provide the bootstrapped metadata provider to them,
723
+ /// or the metadata will not be filled in automatically using the provider on log-sites. While using a custom
724
+ /// factory to avoid using the bootstrapped metadata provider may sometimes be useful, usually it will lead to
725
+ /// un-expected behavior, so make sure to always propagate it to your handlers.
726
+ public static var metadataProvider : Logger . MetadataProvider ? {
727
+ return self . _metadataProviderFactory. metadataProvider
675
728
}
676
729
677
730
private final class FactoryBox {
678
731
private let lock = ReadWriteLock ( )
679
- fileprivate var _underlying : ( String ) -> LogHandler
732
+ fileprivate var _underlying : ( _ label : String , _ provider : Logger . MetadataProvider ? ) -> LogHandler
680
733
private var initialized = false
681
734
682
- init ( _ underlying: @escaping ( String ) -> LogHandler ) {
735
+ init ( _ underlying: @escaping ( String , Logger . MetadataProvider ? ) -> LogHandler ) {
683
736
self . _underlying = underlying
684
737
}
685
738
686
- func replaceFactory( _ factory: @escaping ( String ) -> LogHandler , validate: Bool ) {
739
+ func replaceFactory( _ factory: @escaping ( String , Logger . MetadataProvider ? ) -> LogHandler , validate: Bool ) {
687
740
self . lock. withWriterLock {
688
741
precondition ( !validate || !self . initialized, " logging system can only be initialized once per process. " )
689
742
self . _underlying = factory
690
743
self . initialized = true
691
744
}
692
745
}
693
746
694
- var underlying : ( String ) -> LogHandler {
747
+ var underlying : ( String , Logger . MetadataProvider ? ) -> LogHandler {
748
+ return self . lock. withReaderLock {
749
+ return self . _underlying
750
+ }
751
+ }
752
+ }
753
+
754
+ private final class MetadataProviderBox {
755
+ private let lock = ReadWriteLock ( )
756
+
757
+ internal var _underlying : Logger . MetadataProvider ?
758
+ private var initialized = false
759
+
760
+ init ( _ underlying: Logger . MetadataProvider ? ) {
761
+ self . _underlying = underlying
762
+ }
763
+
764
+ func replaceMetadataProvider( _ metadataProvider: Logger . MetadataProvider ? , validate: Bool ) {
765
+ self . lock. withWriterLock {
766
+ precondition ( !validate || !self . initialized, " logging system can only be initialized once per process. " )
767
+ self . _underlying = metadataProvider
768
+ self . initialized = true
769
+ }
770
+ }
771
+
772
+ var metadataProvider : Logger . MetadataProvider ? {
695
773
return self . lock. withReaderLock {
696
774
return self . _underlying
697
775
}
@@ -786,7 +864,7 @@ extension Logger {
786
864
/// - parameters:
787
865
/// - label: An identifier for the creator of a `Logger`.
788
866
public init ( label: String ) {
789
- self . init ( label: label, LoggingSystem . factory ( label) )
867
+ self . init ( label: label, LoggingSystem . factory ( label, LoggingSystem . metadataProvider ) )
790
868
}
791
869
792
870
/// Construct a `Logger` given a `label` identifying the creator of the `Logger` or a non-standard `LogHandler`.
@@ -803,6 +881,39 @@ extension Logger {
803
881
public init ( label: String , factory: ( String ) -> LogHandler ) {
804
882
self = Logger ( label: label, factory ( label) )
805
883
}
884
+
885
+ /// Construct a `Logger` given a `label` identifying the creator of the `Logger` or a non-standard `LogHandler`.
886
+ ///
887
+ /// The `label` should identify the creator of the `Logger`. This can be an application, a sub-system, or even
888
+ /// a datatype.
889
+ ///
890
+ /// This initializer provides an escape hatch in case the global default logging backend implementation (set up
891
+ /// using `LoggingSystem.bootstrap` is not appropriate for this particular logger.
892
+ ///
893
+ /// - parameters:
894
+ /// - label: An identifier for the creator of a `Logger`.
895
+ /// - factory: A closure creating non-standard `LogHandler`s.
896
+ public init ( label: String , factory: ( String , Logger . MetadataProvider ? ) -> LogHandler ) {
897
+ self = Logger ( label: label, factory ( label, LoggingSystem . metadataProvider) )
898
+ }
899
+
900
+ /// Construct a `Logger` given a `label` identifying the creator of the `Logger` and a non-standard ``Logger/MetadataProvider``.
901
+ ///
902
+ /// The `label` should identify the creator of the `Logger`. This can be an application, a sub-system, or even
903
+ /// a datatype.
904
+ ///
905
+ /// This initializer provides an escape hatch in case the global default logging backend implementation (set up
906
+ /// using `LoggingSystem.bootstrap` is not appropriate for this particular logger.
907
+ ///
908
+ /// - parameters:
909
+ /// - label: An identifier for the creator of a `Logger`.
910
+ /// - metadataProvider: The custom metadata provider this logger should invoke,
911
+ /// instead of the system wide bootstrapped one, when a log statement is about to be emitted.
912
+ public init ( label: String , metadataProvider: MetadataProvider ) {
913
+ self = Logger ( label: label, factory: { label in
914
+ LoggingSystem . factory ( label, metadataProvider)
915
+ } )
916
+ }
806
917
}
807
918
808
919
extension Logger . Level {
@@ -1081,6 +1192,11 @@ let systemStdout = WASILibc.stdout!
1081
1192
1082
1193
/// `StreamLogHandler` is a simple implementation of `LogHandler` for directing
1083
1194
/// `Logger` output to either `stderr` or `stdout` via the factory methods.
1195
+ ///
1196
+ /// Metadata is merged in the following order:
1197
+ /// 1. Metadata set on the log handler itself is used as the base metadata.
1198
+ /// 2. The handler's ``metadataProvider`` is invoked, overriding any existing keys.
1199
+ /// 3. The per-log-statement metadata is merged, overriding any previously set keys.
1084
1200
public struct StreamLogHandler : LogHandler {
1085
1201
#if compiler(>=5.6)
1086
1202
internal typealias _SendableTextOutputStream = TextOutputStream & Sendable
@@ -1090,19 +1206,21 @@ public struct StreamLogHandler: LogHandler {
1090
1206
1091
1207
/// Factory that makes a `StreamLogHandler` to directs its output to `stdout`
1092
1208
public static func standardOutput( label: String ) -> StreamLogHandler {
1093
- return StreamLogHandler ( label: label, stream: StdioOutputStream . stdout)
1209
+ return StreamLogHandler ( label: label, stream: StdioOutputStream . stdout, metadataProvider : LoggingSystem . metadataProvider )
1094
1210
}
1095
1211
1096
1212
/// Factory that makes a `StreamLogHandler` to directs its output to `stderr`
1097
1213
public static func standardError( label: String ) -> StreamLogHandler {
1098
- return StreamLogHandler ( label: label, stream: StdioOutputStream . stderr)
1214
+ return StreamLogHandler ( label: label, stream: StdioOutputStream . stderr, metadataProvider : LoggingSystem . metadataProvider )
1099
1215
}
1100
1216
1101
1217
private let stream : _SendableTextOutputStream
1102
1218
private let label : String
1103
1219
1104
1220
public var logLevel : Logger . Level = . info
1105
1221
1222
+ public var metadataProvider : Logger . MetadataProvider ?
1223
+
1106
1224
private var prettyMetadata : String ?
1107
1225
public var metadata = Logger . Metadata ( ) {
1108
1226
didSet {
@@ -1121,38 +1239,72 @@ public struct StreamLogHandler: LogHandler {
1121
1239
1122
1240
// internal for testing only
1123
1241
internal init ( label: String , stream: _SendableTextOutputStream ) {
1242
+ self . init ( label: label, stream: stream, metadataProvider: LoggingSystem . metadataProvider)
1243
+ }
1244
+
1245
+ // internal for testing only
1246
+ internal init ( label: String , stream: _SendableTextOutputStream , metadataProvider: Logger . MetadataProvider ? ) {
1124
1247
self . label = label
1125
1248
self . stream = stream
1249
+ self . metadataProvider = metadataProvider
1126
1250
}
1127
1251
1128
1252
public func log( level: Logger . Level ,
1129
1253
message: Logger . Message ,
1130
- metadata: Logger . Metadata ? ,
1254
+ metadata explicitMetadata : Logger . Metadata ? ,
1131
1255
source: String ,
1132
1256
file: String ,
1133
1257
function: String ,
1134
1258
line: UInt ) {
1135
- let prettyMetadata = metadata? . isEmpty ?? true
1136
- ? self . prettyMetadata
1137
- : self . prettify ( self . metadata. merging ( metadata!, uniquingKeysWith: { _, new in new } ) )
1259
+ let effectiveMetadata = StreamLogHandler . prepareMetadata ( base: self . metadata, provider: self . metadataProvider, explicit: explicitMetadata)
1260
+
1261
+ let prettyMetadata : String ?
1262
+ if let effectiveMetadata = effectiveMetadata {
1263
+ prettyMetadata = self . prettify ( effectiveMetadata)
1264
+ } else {
1265
+ prettyMetadata = self . prettyMetadata
1266
+ }
1138
1267
1139
1268
var stream = self . stream
1140
1269
stream. write ( " \( self . timestamp ( ) ) \( level) \( self . label) : \( prettyMetadata. map { " \( $0) " } ?? " " ) [ \( source) ] \( message) \n " )
1141
1270
}
1142
1271
1272
+ internal static func prepareMetadata( base: Logger . Metadata , provider: Logger . MetadataProvider ? , explicit: Logger . Metadata ? ) -> Logger . Metadata ? {
1273
+ var metadata = base
1274
+
1275
+ let provided = provider? . get ( ) ?? [ : ]
1276
+
1277
+ guard !provided. isEmpty || !( ( explicit ?? [ : ] ) . isEmpty) else {
1278
+ // all per-log-statement values are empty
1279
+ return nil
1280
+ }
1281
+
1282
+ if !provided. isEmpty {
1283
+ metadata. merge ( provided, uniquingKeysWith: { _, provided in provided } )
1284
+ }
1285
+
1286
+ if let explicit = explicit, !explicit. isEmpty {
1287
+ metadata. merge ( explicit, uniquingKeysWith: { _, explicit in explicit } )
1288
+ }
1289
+
1290
+ return explicit
1291
+ }
1292
+
1143
1293
private func prettify( _ metadata: Logger . Metadata ) -> String ? {
1144
- return !metadata. isEmpty
1145
- ? metadata. lazy. sorted ( by: { $0. key < $1. key } ) . map { " \( $0) = \( $1) " } . joined ( separator: " " )
1146
- : nil
1294
+ if metadata. isEmpty {
1295
+ return nil
1296
+ } else {
1297
+ return metadata. lazy. sorted ( by: { $0. key < $1. key } ) . map { " \( $0) = \( $1) " } . joined ( separator: " " )
1298
+ }
1147
1299
}
1148
1300
1149
1301
private func timestamp( ) -> String {
1150
1302
var buffer = [ Int8] ( repeating: 0 , count: 255 )
1151
1303
#if os(Windows)
1152
- var timestamp : __time64_t = __time64_t ( )
1304
+ var timestamp = __time64_t ( )
1153
1305
_ = _time64 ( & timestamp)
1154
1306
1155
- var localTime : tm = tm ( )
1307
+ var localTime = tm ( )
1156
1308
_ = _localtime64_s ( & localTime, & timestamp)
1157
1309
1158
1310
_ = strftime ( & buffer, buffer. count, " %Y-%m-%dT%H:%M:%S%z " , & localTime)
0 commit comments