| 
 | 1 | +#if LoggingSupport && ReloadingSupport  | 
 | 2 | + | 
 | 3 | +public import Logging  | 
 | 4 | +public import ServiceLifecycle  | 
 | 5 | +import Synchronization  | 
 | 6 | + | 
 | 7 | +/// A log handler whose log level can be controlled the hot-reloaded configuration value `logLevel`.  | 
 | 8 | +///  | 
 | 9 | +/// ## Usage  | 
 | 10 | +///  | 
 | 11 | +/// ```swift  | 
 | 12 | +/// // An existing log handler, for example `StreamLogHandler.standardError(...)`.  | 
 | 13 | +/// let logHandler = ...  | 
 | 14 | +/// // A config reader with at least one reloading provider, containing a value for key `logLevel`.  | 
 | 15 | +/// let configReader = ...  | 
 | 16 | +/// let configurableLogHandler = LiveConfigurableLogHandler(  | 
 | 17 | +///     upstream: logHandler,  | 
 | 18 | +///     config: configReader,  | 
 | 19 | +///     diagnosticLogger: Logger(label: "LiveConfigurableLogHandler", factory: { _ in logHandler })  | 
 | 20 | +/// )  | 
 | 21 | +///  | 
 | 22 | +/// // 1. Add `configurableLogHandler` to a ServiceGroup.  | 
 | 23 | +/// // 2. Bootstrap `configurableLogHandler` as the Swift Log backend.  | 
 | 24 | +/// ```  | 
 | 25 | +public struct LiveConfigurableLogHandler<Upstream: LogHandler> {  | 
 | 26 | +    var upstream: Upstream  | 
 | 27 | +    var service: LiveConfigurableLogHandlerService  | 
 | 28 | + | 
 | 29 | +    init(  | 
 | 30 | +        upstream: Upstream,  | 
 | 31 | +        service: LiveConfigurableLogHandlerService  | 
 | 32 | +    ) {  | 
 | 33 | +        self.upstream = upstream  | 
 | 34 | +        self.service = service  | 
 | 35 | +    }  | 
 | 36 | + | 
 | 37 | +    public init(upstream: Upstream, config: ConfigReader, diagnosticLogger: Logger) {  | 
 | 38 | +        self.init(  | 
 | 39 | +            upstream: upstream,  | 
 | 40 | +            service: .init(  | 
 | 41 | +                config: config,  | 
 | 42 | +                diagnosticLogger: diagnosticLogger  | 
 | 43 | +            )  | 
 | 44 | +        )  | 
 | 45 | +    }  | 
 | 46 | +}  | 
 | 47 | + | 
 | 48 | +extension LiveConfigurableLogHandler: Service {  | 
 | 49 | +    public func run() async throws {  | 
 | 50 | +        try await service.run()  | 
 | 51 | +    }  | 
 | 52 | +}  | 
 | 53 | + | 
 | 54 | +extension LiveConfigurableLogHandler: LogHandler {  | 
 | 55 | +    public var logLevel: Logger.Level {  | 
 | 56 | +        get {  | 
 | 57 | +            service.currentLogLevel ?? upstream.logLevel  | 
 | 58 | +        }  | 
 | 59 | +        set {  | 
 | 60 | +            upstream.logLevel = newValue  | 
 | 61 | +        }  | 
 | 62 | +    }  | 
 | 63 | + | 
 | 64 | +    public var metadata: Logger.Metadata {  | 
 | 65 | +        get { upstream.metadata }  | 
 | 66 | +        set { upstream.metadata = newValue }  | 
 | 67 | +    }  | 
 | 68 | + | 
 | 69 | +    public subscript(metadataKey key: String) -> Logging.Logger.Metadata.Value? {  | 
 | 70 | +        get {  | 
 | 71 | +            upstream[metadataKey: key]  | 
 | 72 | +        }  | 
 | 73 | +        set(newValue) {  | 
 | 74 | +            upstream[metadataKey: key] = newValue  | 
 | 75 | +        }  | 
 | 76 | +    }  | 
 | 77 | + | 
 | 78 | +    public func log(  | 
 | 79 | +        level: Logger.Level,  | 
 | 80 | +        message: Logger.Message,  | 
 | 81 | +        metadata: Logger.Metadata?,  | 
 | 82 | +        source: String,  | 
 | 83 | +        file: String,  | 
 | 84 | +        function: String,  | 
 | 85 | +        line: UInt  | 
 | 86 | +    ) {  | 
 | 87 | +        guard level >= logLevel else {  | 
 | 88 | +            return  | 
 | 89 | +        }  | 
 | 90 | +        upstream.log(  | 
 | 91 | +            level: level,  | 
 | 92 | +            message: message,  | 
 | 93 | +            metadata: metadata,  | 
 | 94 | +            source: source,  | 
 | 95 | +            file: file,  | 
 | 96 | +            function: function,  | 
 | 97 | +            line: line  | 
 | 98 | +        )  | 
 | 99 | +    }  | 
 | 100 | +}  | 
 | 101 | + | 
 | 102 | +extension ConfigKey {  | 
 | 103 | +    fileprivate static let logLevel: Self = ["logLevel"]  | 
 | 104 | +}  | 
 | 105 | + | 
 | 106 | +final class LiveConfigurableLogHandlerService: Sendable {  | 
 | 107 | + | 
 | 108 | +    private let config: ConfigReader  | 
 | 109 | +    private let diagnosticLogger: Logger  | 
 | 110 | + | 
 | 111 | +    private struct Storage {  | 
 | 112 | +        var logLevel: Logger.Level?  | 
 | 113 | +    }  | 
 | 114 | +    private let storage: Mutex<Storage>  | 
 | 115 | + | 
 | 116 | +    var currentLogLevel: Logger.Level? {  | 
 | 117 | +        storage.withLock { $0.logLevel }  | 
 | 118 | +    }  | 
 | 119 | + | 
 | 120 | +    init(  | 
 | 121 | +        config: ConfigReader,  | 
 | 122 | +        diagnosticLogger: Logger  | 
 | 123 | +    ) {  | 
 | 124 | +        self.config = config  | 
 | 125 | +        self.diagnosticLogger = diagnosticLogger  | 
 | 126 | +        self.storage = .init(.init(logLevel: config.string(forKey: .logLevel)))  | 
 | 127 | +    }  | 
 | 128 | +}  | 
 | 129 | + | 
 | 130 | +extension LiveConfigurableLogHandlerService {  | 
 | 131 | +    func run() async throws {  | 
 | 132 | +        diagnosticLogger.debug("Starting")  | 
 | 133 | +        defer {  | 
 | 134 | +            diagnosticLogger.debug("Stopping")  | 
 | 135 | +        }  | 
 | 136 | +        try await config.watchString(forKey: .logLevel, as: Logger.Level.self) { updates in  | 
 | 137 | +            for await logLevel in updates {  | 
 | 138 | +                let oldLogLevel = storage.withLock { storage in  | 
 | 139 | +                    let oldLogLevel = storage.logLevel  | 
 | 140 | +                    storage.logLevel = logLevel  | 
 | 141 | +                    return oldLogLevel  | 
 | 142 | +                }  | 
 | 143 | +                diagnosticLogger.debug(  | 
 | 144 | +                    "Updated log level",  | 
 | 145 | +                    metadata: [  | 
 | 146 | +                        "newLogLevelOverride": "\(logLevel?.rawValue ?? "<nil>")",  | 
 | 147 | +                        "oldLogLevelOverride": "\(oldLogLevel?.rawValue ?? "<nil>")",  | 
 | 148 | +                    ]  | 
 | 149 | +                )  | 
 | 150 | +            }  | 
 | 151 | +        }  | 
 | 152 | +    }  | 
 | 153 | +}  | 
 | 154 | + | 
 | 155 | +#endif  | 
0 commit comments