@@ -17,7 +17,11 @@ import Darwin
17
17
#elseif os(Windows)
18
18
import CRT
19
19
#elseif canImport(Glibc)
20
+ #if compiler(>=6.0)
21
+ @preconcurrency import Glibc
22
+ #else
20
23
import Glibc
24
+ #endif
21
25
#elseif canImport(Musl)
22
26
import Musl
23
27
#elseif canImport(WASILibc)
@@ -498,11 +502,12 @@ extension Logger {
498
502
/// configured. `LoggingSystem` is set up just once in a given program to set up the desired logging backend
499
503
/// implementation.
500
504
public enum LoggingSystem {
501
- private static let _factory = FactoryBox { label, _ in StreamLogHandler . standardError ( label: label) }
502
- private static let _metadataProviderFactory = MetadataProviderBox ( nil )
505
+ private static let _factory = FactoryBox ( { label, _ in StreamLogHandler . standardError ( label: label) } ,
506
+ violationErrorMesage: " logging system can only be initialized once per process. " )
507
+ private static let _metadataProviderFactory = MetadataProviderBox ( nil , violationErrorMesage: " logging system can only be initialized once per process. " )
503
508
504
509
#if DEBUG
505
- private static var _warnOnceBox : WarnOnceBox = WarnOnceBox ( )
510
+ private static let _warnOnceBox : WarnOnceBox = WarnOnceBox ( )
506
511
#endif
507
512
508
513
/// `bootstrap` is a one-time configuration function which globally selects the desired logging backend
@@ -511,8 +516,9 @@ public enum LoggingSystem {
511
516
///
512
517
/// - parameters:
513
518
/// - factory: A closure that given a `Logger` identifier, produces an instance of the `LogHandler`.
514
- public static func bootstrap( _ factory: @escaping ( String ) -> any LogHandler ) {
515
- self . _factory. replaceFactory ( { label, _ in
519
+ @preconcurrency
520
+ public static func bootstrap( _ factory: @escaping @Sendable ( String ) -> any LogHandler ) {
521
+ self . _factory. replace ( { label, _ in
516
522
factory ( label)
517
523
} , validate: true )
518
524
}
@@ -527,25 +533,26 @@ public enum LoggingSystem {
527
533
/// - parameters:
528
534
/// - metadataProvider: The `MetadataProvider` used to inject runtime-generated metadata from the execution context.
529
535
/// - factory: A closure that given a `Logger` identifier, produces an instance of the `LogHandler`.
530
- public static func bootstrap( _ factory: @escaping ( String , Logger . MetadataProvider ? ) -> any LogHandler ,
536
+ @preconcurrency
537
+ public static func bootstrap( _ factory: @escaping @Sendable ( String , Logger . MetadataProvider ? ) -> any LogHandler ,
531
538
metadataProvider: Logger . MetadataProvider ? ) {
532
- self . _metadataProviderFactory. replaceMetadataProvider ( metadataProvider, validate: true )
533
- self . _factory. replaceFactory ( factory, validate: true )
539
+ self . _metadataProviderFactory. replace ( metadataProvider, validate: true )
540
+ self . _factory. replace ( factory, validate: true )
534
541
}
535
542
536
543
// for our testing we want to allow multiple bootstrapping
537
- internal static func bootstrapInternal( _ factory: @escaping ( String ) -> any LogHandler ) {
538
- self . _metadataProviderFactory. replaceMetadataProvider ( nil , validate: false )
539
- self . _factory. replaceFactory ( { label, _ in
544
+ internal static func bootstrapInternal( _ factory: @escaping @ Sendable ( String ) -> any LogHandler ) {
545
+ self . _metadataProviderFactory. replace ( nil , validate: false )
546
+ self . _factory. replace ( { label, _ in
540
547
factory ( label)
541
548
} , validate: false )
542
549
}
543
550
544
551
// for our testing we want to allow multiple bootstrapping
545
- internal static func bootstrapInternal( _ factory: @escaping ( String , Logger . MetadataProvider ? ) -> any LogHandler ,
552
+ internal static func bootstrapInternal( _ factory: @escaping @ Sendable ( String , Logger . MetadataProvider ? ) -> any LogHandler ,
546
553
metadataProvider: Logger . MetadataProvider ? ) {
547
- self . _metadataProviderFactory. replaceMetadataProvider ( metadataProvider, validate: false )
548
- self . _factory. replaceFactory ( factory, validate: false )
554
+ self . _metadataProviderFactory. replace ( metadataProvider, validate: false )
555
+ self . _factory. replace ( factory, validate: false )
549
556
}
550
557
551
558
fileprivate static var factory : ( String , Logger . MetadataProvider ? ) -> any LogHandler {
@@ -564,7 +571,7 @@ public enum LoggingSystem {
564
571
/// factory to avoid using the bootstrapped metadata provider may sometimes be useful, usually it will lead to
565
572
/// un-expected behavior, so make sure to always propagate it to your handlers.
566
573
public static var metadataProvider : Logger . MetadataProvider ? {
567
- return self . _metadataProviderFactory. metadataProvider
574
+ return self . _metadataProviderFactory. underlying
568
575
}
569
576
570
577
#if DEBUG
@@ -576,54 +583,71 @@ public enum LoggingSystem {
576
583
}
577
584
#endif
578
585
579
- private final class FactoryBox {
586
+ /// Protects an object such that it can only be accessed through a Reader-Writer lock.
587
+ final class RWLockedValueBox < Value: Sendable > : @unchecked Sendable {
580
588
private let lock = ReadWriteLock ( )
581
- fileprivate var _underlying : ( _ label: String , _ provider: Logger . MetadataProvider ? ) -> any LogHandler
582
- private var initialized = false
589
+ private var storage : Value
583
590
584
- init ( _ underlying : @escaping ( String , Logger . MetadataProvider ? ) -> any LogHandler ) {
585
- self . _underlying = underlying
591
+ init ( initialValue : Value ) {
592
+ self . storage = initialValue
586
593
}
587
594
588
- func replaceFactory( _ factory: @escaping ( String , Logger . MetadataProvider ? ) -> any LogHandler , validate: Bool ) {
589
- self . lock. withWriterLock {
590
- precondition ( !validate || !self . initialized, " logging system can only be initialized once per process. " )
591
- self . _underlying = factory
592
- self . initialized = true
595
+ func withReadLock< Result> ( _ operation: ( Value ) -> Result ) -> Result {
596
+ self . lock. withReaderLock {
597
+ operation ( self . storage)
593
598
}
594
599
}
595
600
596
- var underlying : ( String , Logger . MetadataProvider ? ) -> any LogHandler {
597
- return self . lock. withReaderLock {
598
- return self . _underlying
601
+ func withWriteLock < Result > ( _ operation : ( inout Value ) -> Result ) -> Result {
602
+ self . lock. withWriterLock {
603
+ operation ( & self . storage )
599
604
}
600
605
}
601
606
}
602
607
603
- private final class MetadataProviderBox {
604
- private let lock = ReadWriteLock ( )
605
-
606
- internal var _underlying : Logger . MetadataProvider ?
607
- private var initialized = false
608
-
609
- init ( _ underlying: Logger . MetadataProvider ? ) {
610
- self . _underlying = underlying
611
- }
608
+ /// Protects an object applying the constraints that it can only be accessed through a Reader-Writer lock
609
+ /// and can ony bre updated once from the initial value given.
610
+ private struct ReplaceOnceBox < BoxedType: Sendable > {
611
+ private struct ReplaceOnce : Sendable {
612
+ private var initialized = false
613
+ private var _underlying : BoxedType
614
+ private let violationErrorMessage : String
612
615
613
- func replaceMetadataProvider( _ metadataProvider: Logger . MetadataProvider ? , validate: Bool ) {
614
- self . lock. withWriterLock {
615
- precondition ( !validate || !self . initialized, " logging system can only be initialized once per process. " )
616
- self . _underlying = metadataProvider
616
+ mutating func replaceUnderlying( _ underlying: BoxedType , validate: Bool ) {
617
+ precondition ( !validate || !self . initialized, self . violationErrorMessage)
618
+ self . _underlying = underlying
617
619
self . initialized = true
618
620
}
619
- }
620
621
621
- var metadataProvider : Logger . MetadataProvider ? {
622
- return self . lock. withReaderLock {
622
+ var underlying : BoxedType {
623
623
return self . _underlying
624
624
}
625
+
626
+ init ( underlying: BoxedType , violationErrorMessage: String ) {
627
+ self . _underlying = underlying
628
+ self . violationErrorMessage = violationErrorMessage
629
+ }
630
+ }
631
+
632
+ private let storage : RWLockedValueBox < ReplaceOnce >
633
+
634
+ init ( _ underlying: BoxedType , violationErrorMesage: String ) {
635
+ self . storage = . init( initialValue: ReplaceOnce ( underlying: underlying,
636
+ violationErrorMessage: violationErrorMesage) )
637
+ }
638
+
639
+ func replace( _ newUnderlying: BoxedType , validate: Bool ) {
640
+ self . storage. withWriteLock { $0. replaceUnderlying ( newUnderlying, validate: validate) }
641
+ }
642
+
643
+ var underlying : BoxedType {
644
+ self . storage. withReadLock { $0. underlying }
625
645
}
626
646
}
647
+
648
+ private typealias FactoryBox = ReplaceOnceBox < @Sendable ( _ label: String , _ provider: Logger . MetadataProvider ? ) -> any LogHandler >
649
+
650
+ private typealias MetadataProviderBox = ReplaceOnceBox < Logger . MetadataProvider ? >
627
651
}
628
652
629
653
extension Logger {
@@ -1021,7 +1045,7 @@ internal typealias CFilePointer = UnsafeMutablePointer<FILE>
1021
1045
/// A wrapper to facilitate `print`-ing to stderr and stdio that
1022
1046
/// ensures access to the underlying `FILE` is locked to prevent
1023
1047
/// cross-thread interleaving of output.
1024
- internal struct StdioOutputStream : TextOutputStream {
1048
+ internal struct StdioOutputStream : TextOutputStream , @ unchecked Sendable {
1025
1049
internal let file : CFilePointer
1026
1050
internal let flushMode : FlushMode
1027
1051
@@ -1062,8 +1086,41 @@ internal struct StdioOutputStream: TextOutputStream {
1062
1086
return contiguousString. utf8
1063
1087
}
1064
1088
1065
- internal static let stderr = StdioOutputStream ( file: systemStderr, flushMode: . always)
1066
- internal static let stdout = StdioOutputStream ( file: systemStdout, flushMode: . always)
1089
+ internal static let stderr = {
1090
+ // Prevent name clashes
1091
+ #if canImport(Darwin)
1092
+ let systemStderr = Darwin . stderr
1093
+ #elseif os(Windows)
1094
+ let systemStderr = CRT . stderr
1095
+ #elseif canImport(Glibc)
1096
+ let systemStderr = Glibc . stderr!
1097
+ #elseif canImport(Musl)
1098
+ let systemStderr = Musl . stderr!
1099
+ #elseif canImport(WASILibc)
1100
+ let systemStderr = WASILibc . stderr!
1101
+ #else
1102
+ #error("Unsupported runtime")
1103
+ #endif
1104
+ return StdioOutputStream ( file: systemStderr, flushMode: . always)
1105
+ } ( )
1106
+
1107
+ internal static let stdout = {
1108
+ // Prevent name clashes
1109
+ #if canImport(Darwin)
1110
+ let systemStdout = Darwin . stdout
1111
+ #elseif os(Windows)
1112
+ let systemStdout = CRT . stdout
1113
+ #elseif canImport(Glibc)
1114
+ let systemStdout = Glibc . stdout!
1115
+ #elseif canImport(Musl)
1116
+ let systemStdout = Musl . stdout!
1117
+ #elseif canImport(WASILibc)
1118
+ let systemStdout = WASILibc . stdout!
1119
+ #else
1120
+ #error("Unsupported runtime")
1121
+ #endif
1122
+ return StdioOutputStream ( file: systemStdout, flushMode: . always)
1123
+ } ( )
1067
1124
1068
1125
/// Defines the flushing strategy for the underlying stream.
1069
1126
internal enum FlushMode {
@@ -1072,26 +1129,6 @@ internal struct StdioOutputStream: TextOutputStream {
1072
1129
}
1073
1130
}
1074
1131
1075
- // Prevent name clashes
1076
- #if canImport(Darwin)
1077
- let systemStderr = Darwin . stderr
1078
- let systemStdout = Darwin . stdout
1079
- #elseif os(Windows)
1080
- let systemStderr = CRT . stderr
1081
- let systemStdout = CRT . stdout
1082
- #elseif canImport(Glibc)
1083
- let systemStderr = Glibc . stderr!
1084
- let systemStdout = Glibc . stdout!
1085
- #elseif canImport(Musl)
1086
- let systemStderr = Musl . stderr!
1087
- let systemStdout = Musl . stdout!
1088
- #elseif canImport(WASILibc)
1089
- let systemStderr = WASILibc . stderr!
1090
- let systemStdout = WASILibc . stdout!
1091
- #else
1092
- #error("Unsupported runtime")
1093
- #endif
1094
-
1095
1132
/// `StreamLogHandler` is a simple implementation of `LogHandler` for directing
1096
1133
/// `Logger` output to either `stderr` or `stdout` via the factory methods.
1097
1134
///
@@ -1341,7 +1378,7 @@ extension Logger.MetadataValue: ExpressibleByArrayLiteral {
1341
1378
1342
1379
#if DEBUG
1343
1380
/// Contains state to manage all kinds of "warn only once" warnings which the logging system may want to issue.
1344
- private final class WarnOnceBox {
1381
+ private final class WarnOnceBox : @ unchecked Sendable {
1345
1382
private let lock : Lock = Lock ( )
1346
1383
private var warnOnceLogHandlerNotSupportedMetadataProviderPerType = Set < ObjectIdentifier > ( )
1347
1384
0 commit comments