diff --git a/Sources/ReliaBLE/Logging/LogWriters.swift b/Sources/ReliaBLE/Logging/LogWriters.swift index dede711..cef548f 100644 --- a/Sources/ReliaBLE/Logging/LogWriters.swift +++ b/Sources/ReliaBLE/Logging/LogWriters.swift @@ -29,6 +29,9 @@ import os import Willow + +/// A LogSource represents the position in the source code where a message is logged. +public typealias LogSource = Willow.LogSource /// The LogModifier protocol defines a single method for modifying a log message after it has been constructed. /// This is very flexible allowing any object that conforms to modify messages in any way it wants. public typealias LogModifier = Willow.LogModifier diff --git a/Tests/ReliaBLETests/ReliaBLEManagerTests.swift b/Tests/ReliaBLETests/ReliaBLEManagerTests.swift index b495e9b..0d1853f 100644 --- a/Tests/ReliaBLETests/ReliaBLEManagerTests.swift +++ b/Tests/ReliaBLETests/ReliaBLEManagerTests.swift @@ -490,6 +490,32 @@ struct ReliaBLEManagerTests { #expect(writer.logType(forLogLevel: .event) == .default) } + @Test func capturingWriterRecordsForwardedMessagesAndLevels() { + let queue = DispatchQueue(label: "com.five3apps.relia-ble.tests.capturing") + let writer = CapturingLogWriter() + let service = LoggingService(levels: .all, writers: [writer], queue: queue) + service.enabled = true + + // Each entry point wraps its text in a `LogMessage`, so the structured overload fires for all four. + service.debug(tags: [.category(.scanning)], "debug message") + service.info(tags: [.peripheral("device-1")], "info message") + service.warn(tags: [.category(.connection)], "warn message") + service.error("error message") + + // Writes are dispatched asynchronously onto the serial `queue`; a sync barrier flushes them. + queue.sync {} + + let captured = writer.captured + #expect(captured.map(\.message) == ["debug message", "info message", "warn message", "error message"]) + #expect(captured.map(\.level) == [.debug, .info, .warn, .error]) + + // Disabling the service stops forwarding to the writer entirely. + service.enabled = false + service.error("dropped message") + queue.sync {} + #expect(writer.captured.count == 4) + } + // MARK: - Event Stream Broadcaster @Test func stateStreamReplaysToConcurrentSubscribers() async throws { @@ -530,6 +556,44 @@ struct ReliaBLEManagerTests { } } +// MARK: - Logging Test Support + +/// A ``LogWriter`` that records every forwarded message so tests can assert on exactly what the +/// ``LoggingService`` emitted — message text and level — at the writer boundary. +/// +/// Thread-safe: writes land on the service's logging queue while assertions read from the test +/// thread, so access to the backing store is guarded by a lock. +final class CapturingLogWriter: LogWriter, @unchecked Sendable { + struct Entry { + let message: String + let level: LogLevel + } + + private let lock = NSLock() + private var storage: [Entry] = [] + + /// A snapshot of everything captured so far, in the order it was written. + var captured: [Entry] { + lock.lock() + defer { lock.unlock() } + return storage + } + + func writeMessage(_ message: String, logLevel: LogLevel, logSource: LogSource) { + append(Entry(message: message, level: logLevel)) + } + + func writeMessage(_ message: any Willow.LogMessage, logLevel: LogLevel, logSource: LogSource) { + append(Entry(message: message.name, level: logLevel)) + } + + private func append(_ entry: Entry) { + lock.lock() + storage.append(entry) + lock.unlock() + } +} + // MARK: - Mock Harness /// Helpers for driving the Nordic `CBMCentralManagerMock` simulation under the constraints of the