diff --git a/Package.swift b/Package.swift index 0ee5032..7bc3f8d 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -25,5 +25,6 @@ let package = Package( name: "LoggerTests", dependencies: ["Logger"] ), - ] + ], + swiftLanguageModes: [.v6] ) diff --git a/Sources/Logger/LogEntry/Level.swift b/Sources/Logger/LogEntry/Level.swift index 392223b..0d8b6ce 100644 --- a/Sources/Logger/LogEntry/Level.swift +++ b/Sources/Logger/LogEntry/Level.swift @@ -9,13 +9,13 @@ import Foundation import OSLog /// Enum representing different possible levels for log messages. Basically mapped object from the native OSLogEntryLog.Level -public enum Level: CaseIterable { +public enum Level: CaseIterable, Sendable { case debug // trace case info case `default` case warning // error case critical // fault - case custom(CustomStringConvertible) + case custom(String) public static var allCases: [Level] { [ diff --git a/Sources/Logger/LogEntry/LogEntry.swift b/Sources/Logger/LogEntry/LogEntry.swift index f2ae9c4..10ce352 100644 --- a/Sources/Logger/LogEntry/LogEntry.swift +++ b/Sources/Logger/LogEntry/LogEntry.swift @@ -7,14 +7,14 @@ import Foundation -public struct LogEntry { +public struct LogEntry: Sendable { public let header: LogHeader public let location: LogLocation - public let message: CustomStringConvertible + public let message: String public init(header: LogHeader, location: LogLocation, message: CustomStringConvertible) { self.header = header self.location = location - self.message = message + self.message = message.description } } diff --git a/Sources/Logger/LogEntry/LogEntryCoding/LogEntryDecoder.swift b/Sources/Logger/LogEntry/LogEntryCoding/LogEntryDecoder.swift index 360b9dc..bda9b2b 100644 --- a/Sources/Logger/LogEntry/LogEntryCoding/LogEntryDecoder.swift +++ b/Sources/Logger/LogEntry/LogEntryCoding/LogEntryDecoder.swift @@ -15,7 +15,7 @@ public struct LogEntryDecoder: LogEntryDecoding { private let logEntryConfig: LogEntryConfig public init( - logEntryConfig: LogEntryConfig = .init() + logEntryConfig: LogEntryConfig = LogEntryConfig() ) { self.logEntryConfig = logEntryConfig } @@ -57,12 +57,11 @@ public struct LogEntryDecoder: LogEntryDecoding { } return LogEntry( - header: .init( + header: LogHeader( date: date, - level: Level(rawValue: levelRawValue), - dateFormatter: logEntryConfig.dateFormatter + level: Level(rawValue: levelRawValue) ), - location: .init( + location: LogLocation( fileName: fileName, function: functionName, line: line diff --git a/Sources/Logger/LogEntry/LogEntryCoding/LogEntryEncoder.swift b/Sources/Logger/LogEntry/LogEntryCoding/LogEntryEncoder.swift index 1dd1a42..b26321a 100644 --- a/Sources/Logger/LogEntry/LogEntryCoding/LogEntryEncoder.swift +++ b/Sources/Logger/LogEntry/LogEntryCoding/LogEntryEncoder.swift @@ -11,7 +11,7 @@ public struct LogEntryEncoder: LogEntryEncoding { private let logEntryConfig: LogEntryConfig public init( - logEntryConfig: LogEntryConfig = .init() + logEntryConfig: LogEntryConfig = LogEntryConfig() ) { self.logEntryConfig = logEntryConfig } diff --git a/Sources/Logger/LogEntry/LogHeader.swift b/Sources/Logger/LogEntry/LogHeader.swift index f1d18c5..9ae0039 100644 --- a/Sources/Logger/LogEntry/LogHeader.swift +++ b/Sources/Logger/LogEntry/LogHeader.swift @@ -7,15 +7,13 @@ import Foundation -public struct LogHeader { +public struct LogHeader: Sendable { public let date: Date public let level: Level - public let dateFormatter: DateFormatter - public init(date: Date, level: Level, dateFormatter: DateFormatter) { + public init(date: Date, level: Level) { self.date = date self.level = level - self.dateFormatter = dateFormatter } } diff --git a/Sources/Logger/LogEntry/LogLocation.swift b/Sources/Logger/LogEntry/LogLocation.swift index 47ae234..dd975e0 100644 --- a/Sources/Logger/LogEntry/LogLocation.swift +++ b/Sources/Logger/LogEntry/LogLocation.swift @@ -7,7 +7,7 @@ import Foundation -public struct LogLocation { +public struct LogLocation: Sendable { public let fileName: String public let function: String public let line: Int diff --git a/Sources/Logger/LoggerManager.swift b/Sources/Logger/LoggerManager.swift index caca460..6445430 100644 --- a/Sources/Logger/LoggerManager.swift +++ b/Sources/Logger/LoggerManager.swift @@ -5,7 +5,6 @@ // Created by Martin Troup on 24.09.2021. // -import Combine import Foundation public typealias ApplicationCallbackBundle = (callbacks: [ApplicationCallbackType], level: Level) @@ -23,7 +22,7 @@ public class LoggerManager { private let metaInformationBundle: MetaInformationBundle? private var dateOfLastLog: Date? - private var subscriptions = Set() + private var applicationCallbackLogger: ApplicationCallbackLogger? /// `LoggerManager` initialization /// - Parameters: @@ -42,14 +41,12 @@ public class LoggerManager { if let applicationCallbackLoggerBundle = applicationCallbackLoggerBundle { let applicationCallbackLogger = ApplicationCallbackLogger( callbacks: applicationCallbackLoggerBundle.callbacks, - level: applicationCallbackLoggerBundle.level - ) - - applicationCallbackLogger.messagePublisher - .sink { [weak self] level, message in + level: applicationCallbackLoggerBundle.level, + logMessage: { [weak self] level, message in self?.log(message, onLevel: level) } - .store(in: &subscriptions) + ) + self.applicationCallbackLogger = applicationCallbackLogger } } @@ -67,7 +64,7 @@ public class LoggerManager { onLine line: Int = #line ) { let currentDate = Date() - let logHeader = LogHeader(date: currentDate, level: level, dateFormatter: DateFormatter.monthsDaysTimeFormatter) + let logHeader = LogHeader(date: currentDate, level: level) let logLocation = LogLocation(fileName: (file as NSString).lastPathComponent, function: function, line: line) let log = LogEntry(header: logHeader, location: logLocation, message: message) let availableLoggers = loggers.availableLoggers(forLevel: log.header.level) @@ -83,9 +80,9 @@ public class LoggerManager { let asynchronousLoggers = availableLoggers.filter(\.isAsynchronous) guard !asynchronousLoggers.isEmpty else { return } - serialQueue.async { + serialQueue.async(execute: DispatchWorkItem { asynchronousLoggers.forEach { $0.log(log) } - } + }) } public func logMetaInformation() { diff --git a/Sources/Logger/Loggers/ApplicationCallbackLogger.swift b/Sources/Logger/Loggers/ApplicationCallbackLogger.swift index a4d36bd..be3bafe 100644 --- a/Sources/Logger/Loggers/ApplicationCallbackLogger.swift +++ b/Sources/Logger/Loggers/ApplicationCallbackLogger.swift @@ -5,7 +5,6 @@ // Created by Martin Troup on 24.09.2021. // -import Combine import Foundation #if canImport(WatchKit) import WatchKit @@ -135,23 +134,22 @@ public enum ApplicationCallbackType: String, CaseIterable { } #endif -protocol ApplicationCallbackLoggerDelegate: AnyObject { - func logApplicationCallback(_ message: String, onLevel level: Level) -} - public class ApplicationCallbackLogger { - private let messageSubject = PassthroughSubject<(level: Level, message: String), Never>() - var messagePublisher: AnyPublisher<(level: Level, message: String), Never> { messageSubject.eraseToAnyPublisher() } - private let level: Level + private let logMessage: (Level, String) -> Void - init(callbacks: [ApplicationCallbackType] = ApplicationCallbackType.allCases, level: Level = .debug) { + init( + callbacks: [ApplicationCallbackType] = ApplicationCallbackType.allCases, + level: Level = .debug, + logMessage: @escaping (Level, String) -> Void + ) { self.level = level + self.logMessage = logMessage callbacks.forEach { callback in #if canImport(UIKit) || canImport(WatchKit) let selector = Selector(callback.rawValue) - #elseif os(OSX) + #elseif os(macOS) let selector = #selector(logNotification(_:)) #endif NotificationCenter.default.addObserver(self, selector: selector, name: callback.notificationName, object: nil) @@ -163,7 +161,7 @@ public class ApplicationCallbackLogger { extension ApplicationCallbackLogger { private func log(_ message: String, onLevel level: Level) { - messageSubject.send((level: level, message: message)) + logMessage(level, message) } #if canImport(UIKit) || canImport(WatchKit) @@ -247,7 +245,7 @@ extension ApplicationCallbackLogger { log("\(#function)", onLevel: level) } - #elseif os(OSX) + #elseif os(macOS) @objc fileprivate func logNotification(_ notification: NSNotification) { diff --git a/Sources/Logger/Loggers/FileLogger/FileAccessExecutor.swift b/Sources/Logger/Loggers/FileLogger/FileAccessExecutor.swift index df42cde..00d53a5 100644 --- a/Sources/Logger/Loggers/FileLogger/FileAccessExecutor.swift +++ b/Sources/Logger/Loggers/FileLogger/FileAccessExecutor.swift @@ -8,6 +8,6 @@ struct FileAccessExecutor { extension FileAccessExecutor { static func live(queue: DispatchQueue) -> Self { - .init(execute: { queue.async(execute: $0) }) + FileAccessExecutor(execute: { queue.async(execute: DispatchWorkItem(block: $0)) }) } } diff --git a/Sources/Logger/Loggers/FileLogger/FileLogger.swift b/Sources/Logger/Loggers/FileLogger/FileLogger.swift index 3753e1c..f116ed9 100644 --- a/Sources/Logger/Loggers/FileLogger/FileLogger.swift +++ b/Sources/Logger/Loggers/FileLogger/FileLogger.swift @@ -251,27 +251,21 @@ public class FileLogger: Logging { /// - Parameters: /// - log: `LogEntry` instance with header, location and log message public func log(_ logEntry: LogEntry) { - let unwrapped: (FileHandle?) throws -> FileHandle = { fileHandle in - guard let fileHandle = fileHandle else { throw FileLoggerError.missingWritableFileHandle } - - return fileHandle - } - - let utf8Data: (String) throws -> Data = { string in - guard let data = string.data(using: .utf8) else { throw FileLoggerError.stringToDataConversionFailure } - - return data - } - fileAccessExecutor { do { try self.refreshCurrentLogFileStatus() let contentToAppend = self.logEntryEncoder.encode(logEntry, verbose: true) + self.lineSeparator - let fileHandle = try unwrapped(self.currentWritableFileHandle) - + guard let fileHandle = self.currentWritableFileHandle else { + throw FileLoggerError.missingWritableFileHandle + } + + guard let data = contentToAppend.data(using: .utf8) else { + throw FileLoggerError.stringToDataConversionFailure + } + fileHandle.seekToEndOfFile() - fileHandle.write(try utf8Data(contentToAppend)) + fileHandle.write(data) } catch let error { self.externalLogger("Failed to write to a log file with error: \(error)!") } diff --git a/Sources/Logger/Loggers/NativeLogger/OSLogStore/OSLogStore+getEntries.swift b/Sources/Logger/Loggers/NativeLogger/OSLogStore/OSLogStore+getEntries.swift index fc51623..5f051b0 100644 --- a/Sources/Logger/Loggers/NativeLogger/OSLogStore/OSLogStore+getEntries.swift +++ b/Sources/Logger/Loggers/NativeLogger/OSLogStore/OSLogStore+getEntries.swift @@ -15,17 +15,14 @@ extension OSLogStore { } func getEntries(bundleIdentifier: String, position: OSLogPosition) async throws -> [OSEntryLog] { - try await withCheckedThrowingContinuation { continuation in - do { - let logs = try self - .getEntries(at: position) - .compactMap { $0 as? OSLogEntryLog } - .map(OSEntryLog.init) - .filter { $0.subsystem == bundleIdentifier } - continuation.resume(with: .success(logs)) - } catch { - continuation.resume(throwing: NativeLoggerError.gettingEntriesFailed(error)) - } + do { + return try self + .getEntries(at: position) + .compactMap { $0 as? OSLogEntryLog } + .map(OSEntryLog.init) + .filter { $0.subsystem == bundleIdentifier } + } catch { + throw NativeLoggerError.gettingEntriesFailed(error) } } } diff --git a/Tests/LoggerTests/Counter.swift b/Tests/LoggerTests/Counter.swift new file mode 100644 index 0000000..12e10f2 --- /dev/null +++ b/Tests/LoggerTests/Counter.swift @@ -0,0 +1,18 @@ +import Foundation + +final class Counter { + private let lock = NSLock() + private var count = 0 + + func increment() { + lock.lock() + defer { lock.unlock() } + count += 1 + } + + func value() -> Int { + lock.lock() + defer { lock.unlock() } + return count + } +} diff --git a/Tests/LoggerTests/LogEntry+mock.swift b/Tests/LoggerTests/LogEntry+mock.swift index 40af33c..3b43fe5 100644 --- a/Tests/LoggerTests/LogEntry+mock.swift +++ b/Tests/LoggerTests/LogEntry+mock.swift @@ -10,9 +10,9 @@ import Foundation extension LogEntry { static func mock(_ message: String) -> LogEntry { - .init( - header: .init(date: Date(), level: .info, dateFormatter: DateFormatter.monthsDaysTimeFormatter), - location: .init(fileName: "file", function: "function", line: 1), + LogEntry( + header: LogHeader(date: Date(), level: .info), + location: LogLocation(fileName: "file", function: "function", line: 1), message: message ) } diff --git a/Tests/LoggerTests/LoggerManagerTests.swift b/Tests/LoggerTests/LoggerManagerTests.swift index 47a363b..1391b32 100644 --- a/Tests/LoggerTests/LoggerManagerTests.swift +++ b/Tests/LoggerTests/LoggerManagerTests.swift @@ -7,7 +7,6 @@ import XCTest @testable import Logger -import Combine class LoggerManagerTests: XCTestCase { func test_log_delivers_to_synchronous_logger_immediately() { @@ -42,64 +41,45 @@ class LoggerManagerTests: XCTestCase { loggerManager.log("message", onLevel: .info) shouldFinishLogging.signal() - waitForExpectations(timeout: 0.5) + wait(for: [didLog], timeout: 0.5) } func test_loggerManager_multithreading_delete_and_log_simultaneously() throws { let loggerManager = LoggerManager( - loggers: .init(), + loggers: [], applicationCallbackLoggerBundle: nil, metaInformationLoggerBundle: nil ) - var cancellables = Set() - let expectation = self.expectation(description: "") - var logCount = 0 - var deleteCount = 0 - - //Simple mutex by using semaphore with value 1 - let semaphore = DispatchSemaphore(value: 1) - - (1...100).publisher - .flatMap { _ in - Just(()) - .subscribe(on: DispatchQueue.global()) - .handleEvents( - receiveOutput: { - loggerManager.log("1", onLevel: Level(rawValue: "1")) - semaphore.wait() - logCount += 1 - semaphore.signal() - } - ) - } - .collect(2) - .map { _ in } - .flatMap { - Just(()) - .subscribe(on: DispatchQueue.global()) - .handleEvents( - receiveOutput: { - loggerManager.deleteAllLogFiles() - semaphore.wait() - deleteCount += 1 - semaphore.signal() - } - ) - } - .sink( - receiveCompletion: { completion in - switch completion { - case .finished: - expectation.fulfill() - } - }, - receiveValue: { _ in } - ) - .store(in: &cancellables) - - waitForExpectations(timeout: 0.6) - XCTAssertEqual(logCount, 100) - XCTAssertEqual(deleteCount, 50) + + let logCounter = Counter() + let deleteCounter = Counter() + let dispatchGroup = DispatchGroup() + + for _ in 1...100 { + dispatchGroup.enter() + DispatchQueue.global().async(execute: DispatchWorkItem { + loggerManager.log("1", onLevel: Level(rawValue: "1")) + logCounter.increment() + dispatchGroup.leave() + }) + } + for _ in 1...50 { + dispatchGroup.enter() + DispatchQueue.global().async(execute: DispatchWorkItem { + loggerManager.deleteAllLogFiles() + deleteCounter.increment() + dispatchGroup.leave() + }) + } + + switch dispatchGroup.wait(timeout: .now() + 0.6) { + case .success: + XCTAssertEqual(logCounter.value(), 100) + XCTAssertEqual(deleteCounter.value(), 50) + + case .timedOut: + XCTFail("Timed out waiting for concurrent log and delete operations.") + } } } diff --git a/Tests/LoggerTests/Loggers/FileLoggerTests.swift b/Tests/LoggerTests/Loggers/FileLoggerTests.swift index 4db3b61..b5b63f8 100644 --- a/Tests/LoggerTests/Loggers/FileLoggerTests.swift +++ b/Tests/LoggerTests/Loggers/FileLoggerTests.swift @@ -7,7 +7,6 @@ @testable import Logger import XCTest -import Combine private extension FileAccessExecutor { static var syncMock: Self { @@ -96,7 +95,8 @@ class FileLoggerTests: XCTestCase { lineSeparator: "<-->", logEntryEncoder: LogEntryEncoder(), logEntryDecoder: LogEntryDecoder(), - externalLogger: { _ in } + externalLogger: { _ in }, + fileAccessQueue: .syncMock ) XCTAssertTrue(fileManager.directoryExists(at: logDirURL)) @@ -127,7 +127,8 @@ class FileLoggerTests: XCTestCase { lineSeparator: "<-->", logEntryEncoder: LogEntryEncoder(), logEntryDecoder: LogEntryDecoder(), - externalLogger: { _ in } + externalLogger: { _ in }, + fileAccessQueue: .syncMock ) XCTAssertEqual( @@ -291,17 +292,17 @@ class FileLoggerTests: XCTestCase { let date = Date(timeIntervalSince1970: 0) fileLogger.log( - .init( - header: .init(date: date, level: .info, dateFormatter: DateFormatter.dateTimeFormatter), - location: .init(fileName: "file", function: "function", line: 1), + LogEntry( + header: LogHeader(date: date, level: .info), + location: LogLocation(fileName: "file", function: "function", line: 1), message: "Error message" ) ) fileLogger.log( - .init( - header: .init(date: date, level: .info, dateFormatter: DateFormatter.dateTimeFormatter), - location: .init(fileName: "file2", function: "function2", line: 20), + LogEntry( + header: LogHeader(date: date, level: .info), + location: LogLocation(fileName: "file2", function: "function2", line: 20), message: "Warning message\nThis is test!" ) ) @@ -361,9 +362,9 @@ class FileLoggerTests: XCTestCase { let date = Date(timeIntervalSince1970: 0) fileLogger.log( - .init( - header: .init(date: date, level: .info, dateFormatter: DateFormatter.dateTimeFormatter), - location: .init(fileName: "File.swift", function: "Function", line: 1), + LogEntry( + header: LogHeader(date: date, level: .info), + location: LogLocation(fileName: "File.swift", function: "Function", line: 1), message: encodedCodableString ) ) @@ -416,23 +417,23 @@ class FileLoggerTests: XCTestCase { let date = Date(timeIntervalSince1970: 0) fileLogger.log( - .init( - header: .init(date: date, level: .info, dateFormatter: DateFormatter.dateTimeFormatter), - location: .init(fileName: "File.swift", function: "Function", line: 1), + LogEntry( + header: LogHeader(date: date, level: .info), + location: LogLocation(fileName: "File.swift", function: "Function", line: 1), message: encodedCodableString ) ) fileLogger.log( - .init( - header: .init(date: date, level: .info, dateFormatter: DateFormatter.dateTimeFormatter), - location: .init(fileName: "File2.swift", function: "Function2", line: 2), + LogEntry( + header: LogHeader(date: date, level: .info), + location: LogLocation(fileName: "File2.swift", function: "Function2", line: 2), message: "Special characters ::[]{}()//" ) ) fileLogger.log( - .init( - header: .init(date: date, level: .info, dateFormatter: DateFormatter.dateTimeFormatter), - location: .init(fileName: "File3.swift", function: "Function3", line: 3), + LogEntry( + header: LogHeader(date: date, level: .info), + location: LogLocation(fileName: "File3.swift", function: "Function3", line: 3), message: """ line 1 line 2 @@ -441,16 +442,16 @@ class FileLoggerTests: XCTestCase { ) ) fileLogger.log( - .init( - header: .init(date: date, level: .info, dateFormatter: DateFormatter.dateTimeFormatter), - location: .init(fileName: "File3.swift", function: "Function3", line: 3), + LogEntry( + header: LogHeader(date: date, level: .info), + location: LogLocation(fileName: "File3.swift", function: "Function3", line: 3), message: "[🚗] Some message" ) ) fileLogger.log( - .init( - header: .init(date: date, level: .info, dateFormatter: DateFormatter.dateTimeFormatter), - location: .init(fileName: "File4.swift", function: "Function4", line: 4), + LogEntry( + header: LogHeader(date: date, level: .info), + location: LogLocation(fileName: "File4.swift", function: "Function4", line: 4), message: encodedCodableString ) ) @@ -497,17 +498,17 @@ class FileLoggerTests: XCTestCase { let date = Date(timeIntervalSince1970: 0) fileLogger.log( - .init( - header: .init(date: date, level: .info, dateFormatter: DateFormatter.dateTimeFormatter), - location: .init(fileName: "file", function: "function", line: 1), + LogEntry( + header: LogHeader(date: date, level: .info), + location: LogLocation(fileName: "file", function: "function", line: 1), message: "Error message" ) ) fileLogger.log( - .init( - header: .init(date: date, level: .info, dateFormatter: DateFormatter.dateTimeFormatter), - location: .init(fileName: "file2", function: "function2", line: 20), + LogEntry( + header: LogHeader(date: date, level: .info), + location: LogLocation(fileName: "file2", function: "function2", line: 20), message: "Warning message\nThis is test!" ) ) @@ -519,9 +520,9 @@ class FileLoggerTests: XCTestCase { fileLogger.deleteAllLogFiles() fileLogger.log( - .init( - header: .init(date: date, level: .info, dateFormatter: DateFormatter.dateTimeFormatter), - location: .init(fileName: "file3", function: "function3", line: 30), + LogEntry( + header: LogHeader(date: date, level: .info), + location: LogLocation(fileName: "file3", function: "function3", line: 30), message: "Previous logs were deleted." ) ) @@ -533,6 +534,16 @@ class FileLoggerTests: XCTestCase { } func test_fileLogger_multithreading_delete_and_log_simultaneously() throws { + let fileAccessGroup = DispatchGroup() + let fileAccessQueue = DispatchQueue(label: "FileLoggerTests.fileAccessQueue") + let fileAccessExecutor = FileAccessExecutor { job in + fileAccessGroup.enter() + fileAccessQueue.async(execute: DispatchWorkItem { + defer { fileAccessGroup.leave() } + job() + }) + } + let fileLogger = try FileLogger( appName: nil, fileManager: fileManager, @@ -545,64 +556,48 @@ class FileLoggerTests: XCTestCase { lineSeparator: "<-->", logEntryEncoder: LogEntryEncoder(), logEntryDecoder: LogEntryDecoder(), - externalLogger: { _ in } + externalLogger: { _ in }, + fileAccessQueue: fileAccessExecutor ) - var cancellables = Set() - let expectation = self.expectation(description: "") - var logCount = 0 - var deleteCount = 0 - - //Simple mutex by using semaphore with value 1 - let semaphore = DispatchSemaphore(value: 1) - - (1...100).publisher - .flatMap { _ in - Just(()) - .subscribe(on: DispatchQueue.global()) - .handleEvents( - receiveOutput: { - fileLogger.log( - .init( - header: .init(date: Date(), level: .info, dateFormatter: DateFormatter.dateTimeFormatter), - location: .init(fileName: "File", function: "function", line: 1), - message: "Error message" - ) - ) - semaphore.wait() - logCount += 1 - semaphore.signal() - } - ) - } - .collect(2) - .map { _ in } - .flatMap { - Just(()) - .subscribe(on: DispatchQueue.global()) - .handleEvents( - receiveOutput: { - fileLogger.deleteAllLogFiles() - semaphore.wait() - deleteCount += 1 - semaphore.signal() - } + let logCounter = Counter() + let deleteCounter = Counter() + let dispatchGroup = DispatchGroup() + + for _ in 1...100 { + dispatchGroup.enter() + DispatchQueue.global().async(execute: DispatchWorkItem { + fileLogger.log( + LogEntry( + header: LogHeader(date: Date(), level: .info), + location: LogLocation(fileName: "File", function: "function", line: 1), + message: "Error message" ) - } - .sink( - receiveCompletion: { completion in - switch completion { - case .finished: - expectation.fulfill() - } - }, - receiveValue: { _ in } - ) - .store(in: &cancellables) - - waitForExpectations(timeout: 0.1) - XCTAssertEqual(logCount, 100) - XCTAssertEqual(deleteCount, 50) + ) + logCounter.increment() + dispatchGroup.leave() + }) + } + for _ in 1...50 { + dispatchGroup.enter() + DispatchQueue.global().async(execute: DispatchWorkItem { + fileLogger.deleteAllLogFiles() + deleteCounter.increment() + dispatchGroup.leave() + }) + } + + let result = dispatchGroup.wait(timeout: .now() + 1.0) + XCTAssertEqual(fileAccessGroup.wait(timeout: .now() + 1.0), .success) + + switch result { + case .success: + XCTAssertEqual(logCounter.value(), 100) + XCTAssertEqual(deleteCounter.value(), 50) + + case .timedOut: + XCTFail("Timed out waiting for concurrent log and delete operations.") + } } func test_deleting_log_files_from_outside() throws { @@ -625,17 +620,17 @@ class FileLoggerTests: XCTestCase { let date = Date(timeIntervalSince1970: 0) fileLogger.log( - .init( - header: .init(date: date, level: .info, dateFormatter: DateFormatter.dateTimeFormatter), - location: .init(fileName: "file", function: "function", line: 1), + LogEntry( + header: LogHeader(date: date, level: .info), + location: LogLocation(fileName: "file", function: "function", line: 1), message: "Error message" ) ) fileLogger.log( - .init( - header: .init(date: date, level: .info, dateFormatter: DateFormatter.dateTimeFormatter), - location: .init(fileName: "file2", function: "function2", line: 20), + LogEntry( + header: LogHeader(date: date, level: .info), + location: LogLocation(fileName: "file2", function: "function2", line: 20), message: "Warning message\nThis is test!" ) ) @@ -647,9 +642,9 @@ class FileLoggerTests: XCTestCase { try fileManager.deleteAllFiles(at: logDirURL, withPathExtension: "log") fileLogger.log( - .init( - header: .init(date: date, level: .info, dateFormatter: DateFormatter.dateTimeFormatter), - location: .init(fileName: "file3", function: "function3", line: 30), + LogEntry( + header: LogHeader(date: date, level: .info), + location: LogLocation(fileName: "file3", function: "function3", line: 30), message: "Previous logs were deleted." ) ) diff --git a/Tests/LoggerTests/Loggers/NativeLogger+OSLogStoreTests.swift b/Tests/LoggerTests/Loggers/NativeLogger+OSLogStoreTests.swift index 57d22bc..f7d4102 100644 --- a/Tests/LoggerTests/Loggers/NativeLogger+OSLogStoreTests.swift +++ b/Tests/LoggerTests/Loggers/NativeLogger+OSLogStoreTests.swift @@ -6,7 +6,7 @@ class NativeLogger_OSLogStoreTests: XCTestCase { struct LogEntryEncoderTest: LogEntryEncoding { func encode(_ logEntry: LogEntry, verbose: Bool) -> String { - logEntry.message as! String + logEntry.message } }