From c775bb964c84618ca2d37512da6acd4ed15a7c3b Mon Sep 17 00:00:00 2001 From: "Alkenso (Vladimir Vashurkin)" Date: Tue, 10 Dec 2024 05:09:47 +0200 Subject: [PATCH] Improve Log --- .../Common/SpellbookLog.swift | 84 ++++++++++++++++++- Sources/SpellbookTestUtils/TestError.swift | 2 +- Tests/SpellbookTests/Common/SBLogTests.swift | 20 +++++ 3 files changed, 104 insertions(+), 2 deletions(-) diff --git a/Sources/SpellbookFoundation/Common/SpellbookLog.swift b/Sources/SpellbookFoundation/Common/SpellbookLog.swift index e22736a..f5787c9 100644 --- a/Sources/SpellbookFoundation/Common/SpellbookLog.swift +++ b/Sources/SpellbookFoundation/Common/SpellbookLog.swift @@ -21,6 +21,7 @@ // SOFTWARE. import Foundation +import os public protocol SpellbookLog { func _custom( @@ -116,6 +117,59 @@ extension SpellbookLog { context: context ) } + + @discardableResult + public func `try`( + level: SpellbookLogLevel = .error, + _ message: @autoclosure () -> Any, + assert: Bool = false, + file: StaticString = #file, + function: StaticString = #function, + line: Int = #line, + context: Any? = nil, + body: () throws -> R + ) -> R? { + do { + return try body() + } catch { + custom( + level: level, + message: "\(message()). Error: \(error)", + assert: assert, + file: file, + function: function, + line: line, + context: context + ) + return nil + } + } + + @discardableResult + public func `try`( + level: SpellbookLogLevel = .error, + assert: Bool = false, + file: StaticString = #file, + function: StaticString = #function, + line: Int = #line, + context: Any? = nil, + body: () throws -> R + ) -> R? { + do { + return try body() + } catch { + custom( + level: level, + message: "\(error)", + assert: assert, + file: file, + function: function, + line: line, + context: context + ) + return nil + } + } } public enum SpellbookLogLevel: Int, Hashable { @@ -164,6 +218,7 @@ public struct SpellbookLogRecord { public var file: StaticString public var function: StaticString public var line: Int + public var date: Date public var context: Any? public init( @@ -173,6 +228,7 @@ public struct SpellbookLogRecord { file: StaticString, function: StaticString, line: Int, + date: Date, context: Any? ) { self.source = source @@ -181,10 +237,26 @@ public struct SpellbookLogRecord { self.file = file self.function = function self.line = line + self.date = date self.context = context } } +extension SpellbookLogRecord { + public var fullDescription: String { + let date = Self.dateFormatter.string(from: date) + let file = String("\(file)").lastPathComponent.deletingPathExtension + return "\(date) \(file).\(function):\(line) [\(source)] \(level.description.uppercased()): \(message)" + } + + private static let dateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSSSS ZZZZZ" + return formatter + }() +} + public struct SpellbookLogSource { /// Subsystem is usually a big component or a feature of the product. /// For example, `SpellbookFoundation`. @@ -224,6 +296,16 @@ public struct SpellbookLogDestination { } } +extension SpellbookLogDestination { + public static func print(minLevel: SpellbookLogLevel = .info) -> Self { + .init(minLevel: minLevel) { Swift.print($0.fullDescription) } + } + + public static func nslog(minLevel: SpellbookLogLevel = .info) -> Self { + .init(minLevel: minLevel) { NSLog($0.fullDescription) } + } +} + public final class SpellbookLogger { public init(name: String, useQueue: Bool = true) { queue = useQueue ? DispatchQueue(label: "SpellbookLog.\(name).queue") : nil @@ -318,7 +400,7 @@ extension SpellbookLogger: SpellbookLog { let record = SpellbookLogRecord( source: source, level: level, message: "\(message())", - file: file, function: function, line: line, context: context + file: file, function: function, line: line, date: Date(), context: context ) queue.async { destinations.forEach { $0.log(record) } diff --git a/Sources/SpellbookTestUtils/TestError.swift b/Sources/SpellbookTestUtils/TestError.swift index 466da09..18ea8f5 100644 --- a/Sources/SpellbookTestUtils/TestError.swift +++ b/Sources/SpellbookTestUtils/TestError.swift @@ -22,7 +22,7 @@ import Foundation -public struct TestError: Error { +public struct TestError: Error, CustomStringConvertible { public let description: String public let underlyingError: Error? diff --git a/Tests/SpellbookTests/Common/SBLogTests.swift b/Tests/SpellbookTests/Common/SBLogTests.swift index b0bd6d9..140e14e 100644 --- a/Tests/SpellbookTests/Common/SBLogTests.swift +++ b/Tests/SpellbookTests/Common/SBLogTests.swift @@ -139,4 +139,24 @@ final class SBLogTests: XCTestCase { waitForExpectations(timeout: 0.1) } + + func test_try() { + let log = SpellbookLogger(name: "test") + let exp = expectation(description: "logged") + exp.expectedFulfillmentCount = 2 + var records: [SpellbookLogRecord] = [] + log.destinations.append(.init { + records.append($0) + exp.fulfill() + }) + + log.try(level: .error, "foo failed") { throw TestError("foo error") } + XCTAssertEqual(log.try(level: .warning, "bar not failed") { 10 }, 10) + log.try { throw TestError("baz error") } + + waitForExpectations() + + XCTAssertTrue(records.contains { $0.message == "foo failed. Error: foo error" }) + XCTAssertTrue(records.contains { $0.message == "baz error" }) + } }