From f8d94242fc4b949c8454898c60b25735e963eecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Pacheco=20Neves?= Date: Fri, 9 Apr 2021 22:32:10 +0100 Subject: [PATCH] =?UTF-8?q?Implement=20scoped=20loggers=20from=20`ModuleLo?= =?UTF-8?q?gger`s=20=F0=9F=8D=B0=20(#230)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On certain occasions, one needs to pass in a simple `Logger` instance but want log events forwarded to a larger logging infrastructure. If we're dealing with a `ModuleLogger` with multiple registered modules, one may want to derive a simpler `Logger` that sends all events under a single module to the upstream logger. To achieve that, `ModuleLogger` can now create scoped loggers via a new `scopedLogger(for:)` helper. ## Changes - Create `ModuleLogger.scopedLogger(for:)` helper. - Create new `Log.ForwardingLogger` private helper class to forward log events to an upstream (Module) logger. --- Sources/Logging/Loggers/ModuleLogger.swift | 38 +++++++++ .../Loggers/ModuleLoggerTestCase.swift | 82 +++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/Sources/Logging/Loggers/ModuleLogger.swift b/Sources/Logging/Loggers/ModuleLogger.swift index 6fcf181..3246fc6 100644 --- a/Sources/Logging/Loggers/ModuleLogger.swift +++ b/Sources/Logging/Loggers/ModuleLogger.swift @@ -194,3 +194,41 @@ public extension ModuleLogger where Self: LogDestination { } } } + +public extension ModuleLogger { + + /// Scopes the logger to one that sends all log events from a single `module` via `self`. + /// + /// This can be useful when we need to pass in a simple `Logger` instance but want log events forwarded to a + /// larger logging infrastructure (e.g. with multiple modules), under a single module. + /// + /// - Parameter module: The module to scope log events with. + /// - Returns: A new logger that sends all events with the chosen `module` to `self`. + func scopedLogger(for module: Module) -> Logger { Log.ForwardingLogger(scoping: module, from: self) } +} + +private extension Log { + + final class ForwardingLogger: Logger { + + let upstreamLog: (Log.Level, () -> String, StaticString, UInt, StaticString) -> Void + + init(scoping module: L.Module, from logger: L) { + + self.upstreamLog = { level, message, file, line, function in + logger.log(module: module, level: level, message: message(), file: file, line: line, function: function) + } + } + + func log( + level: Log.Level, + message: @autoclosure () -> String, + file: StaticString, + line: UInt, + function: StaticString + ) { + + upstreamLog(level, message, file, line, function) + } + } +} diff --git a/Tests/AlicerceTests/Logging/Loggers/ModuleLoggerTestCase.swift b/Tests/AlicerceTests/Logging/Loggers/ModuleLoggerTestCase.swift index 49c742e..22a48ff 100644 --- a/Tests/AlicerceTests/Logging/Loggers/ModuleLoggerTestCase.swift +++ b/Tests/AlicerceTests/Logging/Loggers/ModuleLoggerTestCase.swift @@ -175,6 +175,88 @@ class ModuleLoggerTestCase: XCTestCase { log.error("message", file: "filename.ext", line: 1337, function: "function") } + + // scopedLogger(for:) + + func testScopedLogger_WithVerboseLog_ShouldInvokeUpstreamLogWithCorrectModuleAndLogLevel() { + + let scopedLogger = log.scopedLogger(for: .🤖) + + log.moduleLogInvokedClosure = { module, level, message, file, line, function in + XCTAssertEqual(module, MockModule.🤖) + XCTAssertEqual(level, .verbose) + XCTAssertEqual(message, "message") + XCTAssertEqual(file.description, "filename.ext") + XCTAssertEqual(line, 1337) + XCTAssertEqual(function.description, "function") + } + + scopedLogger.verbose("message", file: "filename.ext", line: 1337, function: "function") + } + + func testScopedLogger_WithDebugLog_ShouldInvokeUpstreamLogWithCorrectModuleAndLogLevel() { + + let scopedLogger = log.scopedLogger(for: .🤖) + + log.moduleLogInvokedClosure = { module, level, message, file, line, function in + XCTAssertEqual(module, MockModule.🤖) + XCTAssertEqual(level, .debug) + XCTAssertEqual(message, "message") + XCTAssertEqual(file.description, "filename.ext") + XCTAssertEqual(line, 1337) + XCTAssertEqual(function.description, "function") + } + + scopedLogger.debug("message", file: "filename.ext", line: 1337, function: "function") + } + + func testScopedLogger_WithInfoLog_ShouldInvokeUpstreamLogWithCorrectModuleAndLogLevel() { + + let scopedLogger = log.scopedLogger(for: .🤖) + + log.moduleLogInvokedClosure = { module, level, message, file, line, function in + XCTAssertEqual(module, MockModule.🤖) + XCTAssertEqual(level, .info) + XCTAssertEqual(message, "message") + XCTAssertEqual(file.description, "filename.ext") + XCTAssertEqual(line, 1337) + XCTAssertEqual(function.description, "function") + } + + scopedLogger.info("message", file: "filename.ext", line: 1337, function: "function") + } + + func testScopedLogger_WithWarningLog_ShouldInvokeUpstreamLogWithCorrectModuleAndLogLevel() { + + let scopedLogger = log.scopedLogger(for: .🤖) + + log.moduleLogInvokedClosure = { module, level, message, file, line, function in + XCTAssertEqual(module, MockModule.🤖) + XCTAssertEqual(level, .warning) + XCTAssertEqual(message, "message") + XCTAssertEqual(file.description, "filename.ext") + XCTAssertEqual(line, 1337) + XCTAssertEqual(function.description, "function") + } + + scopedLogger.warning("message", file: "filename.ext", line: 1337, function: "function") + } + + func testScopedLogger_WithErrorLog_ShouldInvokeUpstreamLogWithCorrectModuleAndLogLevel() { + + let scopedLogger = log.scopedLogger(for: .🤖) + + log.moduleLogInvokedClosure = { module, level, message, file, line, function in + XCTAssertEqual(module, MockModule.🤖) + XCTAssertEqual(level, .error) + XCTAssertEqual(message, "message") + XCTAssertEqual(file.description, "filename.ext") + XCTAssertEqual(line, 1337) + XCTAssertEqual(function.description, "function") + } + + scopedLogger.error("message", file: "filename.ext", line: 1337, function: "function") + } } private enum MockModule: String, LogModule {