From dc2586ba95f1a05f84de6c1565d934b04f32c4a6 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Mon, 7 Apr 2025 18:23:44 +0200 Subject: [PATCH 01/12] add logging --- CHANGELOG.md | 21 +++ Sources/PowerSync/Kotlin/DatabaseLogger.swift | 135 ++++++++++++++++++ .../Kotlin/KotlinPowerSyncDatabaseImpl.swift | 10 +- Sources/PowerSync/Logger.swift | 52 +++++++ Sources/PowerSync/LoggerProtocol.swift | 82 +++++++++++ Sources/PowerSync/PowerSyncDatabase.swift | 7 +- .../PowerSync/PowerSyncDatabaseProtocol.swift | 6 + .../KotlinPowerSyncDatabaseImplTests.swift | 45 +++++- Tests/PowerSyncTests/Kotlin/TestLogger.swift | 23 +++ 9 files changed, 376 insertions(+), 5 deletions(-) create mode 100644 Sources/PowerSync/Kotlin/DatabaseLogger.swift create mode 100644 Sources/PowerSync/Logger.swift create mode 100644 Sources/PowerSync/LoggerProtocol.swift create mode 100644 Tests/PowerSyncTests/Kotlin/TestLogger.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 4523c23..b075199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +# 1.0.0-Beta.10 (unreleased) + +* Added the ability to specify a custom logging implementation +```swift + let db = PowerSyncDatabase( + schema: Schema( + tables: [ + Table( + name: "users", + columns: [ + .text("name"), + .text("email") + ] + ) + ] + ), + logger: DefaultLogger(minSeverity: .debug) +) +``` +* added `.close()` method on `PowerSyncDatabaseProtocol` + ## 1.0.0-Beta.9 * Update PowerSync SQLite core extension to 0.3.12. diff --git a/Sources/PowerSync/Kotlin/DatabaseLogger.swift b/Sources/PowerSync/Kotlin/DatabaseLogger.swift new file mode 100644 index 0000000..fe4e3ab --- /dev/null +++ b/Sources/PowerSync/Kotlin/DatabaseLogger.swift @@ -0,0 +1,135 @@ +import PowerSyncKotlin + +/// Maps a Kermit `Severity` level to a local `LogSeverity`. +/// +/// - Parameter severity: The Kermit log severity value from Kotlin. +/// - Returns: The corresponding `LogSeverity` used in Swift. +private func mapKermitSeverity(_ severity: PowerSyncKotlin.Kermit_coreSeverity) -> LogSeverity { + switch severity { + case PowerSyncKotlin.Kermit_coreSeverity.verbose: + return LogSeverity.debug + case PowerSyncKotlin.Kermit_coreSeverity.debug: + return LogSeverity.debug + case PowerSyncKotlin.Kermit_coreSeverity.info: + return LogSeverity.info + case PowerSyncKotlin.Kermit_coreSeverity.warn: + return LogSeverity.warning + case PowerSyncKotlin.Kermit_coreSeverity.error: + return LogSeverity.error + case PowerSyncKotlin.Kermit_coreSeverity.assert: + return LogSeverity.fault + } +} + +/// Maps a local `LogSeverity` to a Kermit-compatible `Kermit_coreSeverity`. +/// +/// - Parameter severity: The Swift-side `LogSeverity`. +/// - Returns: The equivalent Kermit log severity. +private func mapSeverity(_ severity: LogSeverity) -> PowerSyncKotlin.Kermit_coreSeverity { + switch severity { + case .debug: + return PowerSyncKotlin.Kermit_coreSeverity.debug + case .info: + return PowerSyncKotlin.Kermit_coreSeverity.info + case .warning: + return PowerSyncKotlin.Kermit_coreSeverity.warn + case .error: + return PowerSyncKotlin.Kermit_coreSeverity.error + case .fault: + return PowerSyncKotlin.Kermit_coreSeverity.assert + } +} + +/// Adapts a Swift `LogWritterProtocol` to Kermit's `LogWriter` interface. +/// +/// This allows Kotlin logging (via Kermit) to call into the Swift logging implementation. +private class KermitLogWriterAdapter: Kermit_coreLogWriter { + /// The underlying Swift log writer to forward log messages to. + var adapter: LogWriterProtocol + + /// Initializes a new adapter. + /// + /// - Parameter adapter: A Swift log writer that will handle log output. + init(adapter: LogWriterProtocol) { + self.adapter = adapter + super.init() + } + + /// Called by Kermit to log a message. + /// + /// - Parameters: + /// - severity: The severity level of the log. + /// - message: The content of the log message. + /// - tag: An optional string categorizing the log. + /// - throwable: An optional Kotlin exception (ignored here). + override func log(severity: Kermit_coreSeverity, message: String, tag: String, throwable: KotlinThrowable?) { + adapter.log( + severity: mapKermitSeverity(severity), + message: message, + tag: tag + ) + } +} + +/// A logger implementation that integrates with PowerSync's Kotlin backend using Kermit. +/// +/// This class bridges Swift log writers with the Kotlin logging system and supports +/// runtime configuration of severity levels and writer lists. +public class DatabaseLogger: LoggerProtocol { + /// The underlying Kermit logger instance provided by the PowerSyncKotlin SDK. + internal let kLogger = PowerSyncKotlin.generateLogger(logger: nil) + + /// Initializes a new logger with an optional list of writers. + /// + /// - Parameter writers: An array of Swift log writers. Defaults to an empty array. + init(writers: [any LogWriterProtocol] = []) { + setWriters(writers) + } + + /// Sets the minimum severity level that will be logged. + /// + /// Messages below this level will be ignored. + /// + /// - Parameter severity: The minimum `LogSeverity` to allow through. + public func setMinSeverity(_ severity: LogSeverity) { + kLogger.mutableConfig.setMinSeverity( + mapSeverity(severity) + ) + } + + /// Sets the list of log writers that will receive log messages. + /// + /// This updates both the internal writer list and the Kermit logger's configuration. + /// + /// - Parameter writers: An array of Swift `LogWritterProtocol` implementations. + public func setWriters(_ writers: [any LogWriterProtocol]) { + kLogger.mutableConfig.setLogWriterList( + writers.map { item in KermitLogWriterAdapter(adapter: item) } + ) + } + + /// Logs a debug-level message. + public func debug(_ message: String, tag: String) { + kLogger.d(messageString: message, throwable: nil, tag: tag) + } + + /// Logs an info-level message. + public func info(_ message: String, tag: String) { + kLogger.i(messageString: message, throwable: nil, tag: tag) + } + + /// Logs a warning-level message. + public func warning(_ message: String, tag: String) { + kLogger.w(messageString: message, throwable: nil, tag: tag) + } + + /// Logs an error-level message. + public func error(_ message: String, tag: String) { + kLogger.e(messageString: message, throwable: nil, tag: tag) + } + + /// Logs a fault (assert-level) message, typically used for critical issues. + public func fault(_ message: String, tag: String) { + kLogger.a(messageString: message, throwable: nil, tag: tag) + } +} diff --git a/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift b/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift index efd3b5e..ea78b55 100644 --- a/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift +++ b/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift @@ -8,13 +8,15 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol { init( schema: Schema, - dbFilename: String + dbFilename: String, + logger: DatabaseLogger? = nil ) { let factory = PowerSyncKotlin.DatabaseDriverFactory() kotlinDatabase = PowerSyncDatabase( factory: factory, schema: KotlinAdapter.Schema.toKotlin(schema), - dbFilename: dbFilename + dbFilename: dbFilename, + logger: logger?.kLogger ) } @@ -232,4 +234,8 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol { func readTransaction(callback: @escaping (any PowerSyncTransaction) throws -> R) async throws -> R { return try safeCast(await kotlinDatabase.readTransaction(callback: TransactionCallback(callback: callback)), to: R.self) } + + func close() async throws{ + try await kotlinDatabase.close() + } } diff --git a/Sources/PowerSync/Logger.swift b/Sources/PowerSync/Logger.swift new file mode 100644 index 0000000..8c6351e --- /dev/null +++ b/Sources/PowerSync/Logger.swift @@ -0,0 +1,52 @@ +import OSLog + +/// A log writer that bridges custom `LogSeverity` levels to Apple's unified `Logger` framework. +/// +/// This writer uses `os.Logger` on iOS 14+ and falls back to `print` for earlier versions. +/// Tags are optionally prefixed to messages in square brackets. +public class SwiftLogWriter: LogWriterProtocol { + + /// Logs a message with a given severity and optional tag. + /// + /// - Parameters: + /// - severity: The severity level of the message. + /// - message: The content of the log message. + /// - tag: An optional tag used to categorize the message. If empty, no brackets are shown. + public func log(severity: LogSeverity, message: String, tag: String) { + let tagPrefix = tag.isEmpty ? "" : "[\(tag)] " + let message = "\(tagPrefix) \(message)" + if #available(iOS 14.0, *) { + let l = Logger() + + switch severity { + case .info: + l.info("\(message)") + case .error: + l.error("\(message)") + case .debug: + l.debug("\(message)") + case .warning: + l.warning("\(message)") + case .fault: + l.fault("\(message)") + } + } else { + print("\(severity.rawValue): \(message)") + } + } +} + +/// A default logger configuration that uses `SwiftLogWritter` and filters messages by minimum severity. +/// +/// This logger integrates with your custom logging system and uses `os.Logger` under the hood. +public class DefaultLogger: DatabaseLogger { + + /// Initializes the default logger with an optional minimum severity level. + /// + /// - Parameter minSeverity: The minimum severity level to log. Defaults to `.debug`. + public init(minSeverity: LogSeverity = .debug) { + super.init() + setMinSeverity(minSeverity) + setWriters([SwiftLogWriter()]) + } +} diff --git a/Sources/PowerSync/LoggerProtocol.swift b/Sources/PowerSync/LoggerProtocol.swift new file mode 100644 index 0000000..620b3c1 --- /dev/null +++ b/Sources/PowerSync/LoggerProtocol.swift @@ -0,0 +1,82 @@ +/// Represents the severity level of a log message. +public enum LogSeverity: String, CaseIterable { + /// Informational messages that highlight the progress of the application. + case info = "INFO" + + /// Error events that might still allow the application to continue running. + case error = "ERROR" + + /// Detailed information typically used for debugging. + case debug = "DEBUG" + + /// Potentially harmful situations that are not necessarily errors. + case warning = "WARNING" + + /// Serious errors indicating critical failures, often unrecoverable. + case fault = "FAULT" +} + +/// A protocol for writing log messages to a specific backend or output. +/// +/// Conformers handle the actual writing or forwarding of log messages. +public protocol LogWriterProtocol { + /// Logs a message with the given severity and optional tag. + /// + /// - Parameters: + /// - severity: The severity level of the log message. + /// - message: The content of the log message. + /// - tag: An optional tag to categorize or group the log message. + func log(severity: LogSeverity, message: String, tag: String) +} + +/// A protocol defining the interface for a logger that supports severity filtering and multiple writers. +/// +/// Conformers provide logging APIs and manage attached log writers. +public protocol LoggerProtocol { + /// Sets the minimum severity level to be logged. + /// + /// Log messages below this severity will be ignored. + /// + /// - Parameter severity: The minimum severity level to log. + func setMinSeverity(_ severity: LogSeverity) + + /// Sets the list of log writers that will handle log output. + /// + /// - Parameter writters: An array of `LogWritterProtocol` conformers. + func setWriters(_ writters: [LogWriterProtocol]) + + /// Logs an informational message. + /// + /// - Parameters: + /// - message: The content of the log message. + /// - tag: An optional tag to categorize the message. + func info(_ message: String, tag: String) + + /// Logs an error message. + /// + /// - Parameters: + /// - message: The content of the log message. + /// - tag: An optional tag to categorize the message. + func error(_ message: String, tag: String) + + /// Logs a debug message. + /// + /// - Parameters: + /// - message: The content of the log message. + /// - tag: An optional tag to categorize the message. + func debug(_ message: String, tag: String) + + /// Logs a warning message. + /// + /// - Parameters: + /// - message: The content of the log message. + /// - tag: An optional tag to categorize the message. + func warning(_ message: String, tag: String) + + /// Logs a fault message, typically used for critical system-level failures. + /// + /// - Parameters: + /// - message: The content of the log message. + /// - tag: An optional tag to categorize the message. + func fault(_ message: String, tag: String) +} diff --git a/Sources/PowerSync/PowerSyncDatabase.swift b/Sources/PowerSync/PowerSyncDatabase.swift index 454e7e3..e50ce87 100644 --- a/Sources/PowerSync/PowerSyncDatabase.swift +++ b/Sources/PowerSync/PowerSyncDatabase.swift @@ -7,14 +7,17 @@ public let DEFAULT_DB_FILENAME = "powersync.db" /// - Parameters: /// - schema: The database schema /// - dbFilename: The database filename. Defaults to "powersync.db" +/// - logger: Optional logging interface /// - Returns: A configured PowerSyncDatabase instance public func PowerSyncDatabase( schema: Schema, - dbFilename: String = DEFAULT_DB_FILENAME + dbFilename: String = DEFAULT_DB_FILENAME, + logger: DatabaseLogger = DefaultLogger() ) -> PowerSyncDatabaseProtocol { return KotlinPowerSyncDatabaseImpl( schema: schema, - dbFilename: dbFilename + dbFilename: dbFilename, + logger: logger ) } diff --git a/Sources/PowerSync/PowerSyncDatabaseProtocol.swift b/Sources/PowerSync/PowerSyncDatabaseProtocol.swift index ea5418c..9de5f00 100644 --- a/Sources/PowerSync/PowerSyncDatabaseProtocol.swift +++ b/Sources/PowerSync/PowerSyncDatabaseProtocol.swift @@ -100,6 +100,12 @@ public protocol PowerSyncDatabaseProtocol: Queries { /// /// - Parameter clearLocal: Set to false to preserve data in local-only tables. func disconnectAndClear(clearLocal: Bool) async throws + + /// Close the database, releasing resources. + /// Also disconnects any active connection. + /// + /// Once close is called, this database cannot be used again - a new one must be constructed. + func close() async throws } public extension PowerSyncDatabaseProtocol { diff --git a/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift b/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift index 3c645c5..1bc0767 100644 --- a/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift +++ b/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift @@ -16,13 +16,15 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase { database = KotlinPowerSyncDatabaseImpl( schema: schema, - dbFilename: ":memory:" + dbFilename: ":memory:", + logger: DefaultLogger() ) try await database.disconnectAndClear() } override func tearDown() async throws { try await database.disconnectAndClear() + try await database.close() database = nil try await super.tearDown() } @@ -468,4 +470,45 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase { XCTAssertEqual(peopleCount, 1) } + + func testCustomLogger() async throws { + let logger = TestLogger() + + let db2 = KotlinPowerSyncDatabaseImpl( + schema: schema, + dbFilename: ":memory:", + logger: logger + ) + + try await db2.close() + + let warningIndex = logger.logs.firstIndex( + where: { value in + value.contains("warning: Multiple PowerSync instances for the same database have been detected") + } + ) + + XCTAssert(warningIndex! >= 0) + } + + func testMinimumSeverity() async throws { + let logger = TestLogger(minSeverity: .error) + + let db2 = KotlinPowerSyncDatabaseImpl( + schema: schema, + dbFilename: ":memory:", + logger: logger + ) + + try await db2.close() + + let warningIndex = logger.logs.firstIndex( + where: { value in + value.contains("warning: Multiple PowerSync instances for the same database have been detected") + } + ) + + // The warning should not be present due to the min severity + XCTAssert(warningIndex == nil) + } } diff --git a/Tests/PowerSyncTests/Kotlin/TestLogger.swift b/Tests/PowerSyncTests/Kotlin/TestLogger.swift new file mode 100644 index 0000000..ecc8305 --- /dev/null +++ b/Tests/PowerSyncTests/Kotlin/TestLogger.swift @@ -0,0 +1,23 @@ +@testable import PowerSync + + +class TestLogWriterAdapter: LogWriterProtocol { + var logs = [String]() + + func log(severity: LogSeverity, message: String, tag: String) { + logs.append("\(severity): \(message) (\(tag))") + } +} + +class TestLogger: DefaultLogger { + let writer = TestLogWriterAdapter() + + public var logs: [String] { + return writer.logs + } + + override init(minSeverity: LogSeverity = .debug) { + super.init(minSeverity: minSeverity) + setWriters([writer]) + } +} From 15807622a7c5bee4fc5987a47449268df91e7863 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 8 Apr 2025 09:24:12 +0200 Subject: [PATCH 02/12] Improve loggers --- Sources/PowerSync/Kotlin/DatabaseLogger.swift | 99 +++++++------------ Sources/PowerSync/Logger.swift | 55 +++++++++-- Sources/PowerSync/LoggerProtocol.swift | 41 +++++--- Sources/PowerSync/PowerSyncDatabase.swift | 4 +- .../KotlinPowerSyncDatabaseImplTests.swift | 16 +-- Tests/PowerSyncTests/Kotlin/TestLogger.swift | 12 --- 6 files changed, 121 insertions(+), 106 deletions(-) diff --git a/Sources/PowerSync/Kotlin/DatabaseLogger.swift b/Sources/PowerSync/Kotlin/DatabaseLogger.swift index fe4e3ab..435ec97 100644 --- a/Sources/PowerSync/Kotlin/DatabaseLogger.swift +++ b/Sources/PowerSync/Kotlin/DatabaseLogger.swift @@ -1,57 +1,17 @@ import PowerSyncKotlin -/// Maps a Kermit `Severity` level to a local `LogSeverity`. -/// -/// - Parameter severity: The Kermit log severity value from Kotlin. -/// - Returns: The corresponding `LogSeverity` used in Swift. -private func mapKermitSeverity(_ severity: PowerSyncKotlin.Kermit_coreSeverity) -> LogSeverity { - switch severity { - case PowerSyncKotlin.Kermit_coreSeverity.verbose: - return LogSeverity.debug - case PowerSyncKotlin.Kermit_coreSeverity.debug: - return LogSeverity.debug - case PowerSyncKotlin.Kermit_coreSeverity.info: - return LogSeverity.info - case PowerSyncKotlin.Kermit_coreSeverity.warn: - return LogSeverity.warning - case PowerSyncKotlin.Kermit_coreSeverity.error: - return LogSeverity.error - case PowerSyncKotlin.Kermit_coreSeverity.assert: - return LogSeverity.fault - } -} - -/// Maps a local `LogSeverity` to a Kermit-compatible `Kermit_coreSeverity`. -/// -/// - Parameter severity: The Swift-side `LogSeverity`. -/// - Returns: The equivalent Kermit log severity. -private func mapSeverity(_ severity: LogSeverity) -> PowerSyncKotlin.Kermit_coreSeverity { - switch severity { - case .debug: - return PowerSyncKotlin.Kermit_coreSeverity.debug - case .info: - return PowerSyncKotlin.Kermit_coreSeverity.info - case .warning: - return PowerSyncKotlin.Kermit_coreSeverity.warn - case .error: - return PowerSyncKotlin.Kermit_coreSeverity.error - case .fault: - return PowerSyncKotlin.Kermit_coreSeverity.assert - } -} - -/// Adapts a Swift `LogWritterProtocol` to Kermit's `LogWriter` interface. +/// Adapts a Swift `LoggerProtocol` to Kermit's `LogWriter` interface. /// /// This allows Kotlin logging (via Kermit) to call into the Swift logging implementation. private class KermitLogWriterAdapter: Kermit_coreLogWriter { /// The underlying Swift log writer to forward log messages to. - var adapter: LogWriterProtocol + let logger: any LoggerProtocol /// Initializes a new adapter. /// /// - Parameter adapter: A Swift log writer that will handle log output. - init(adapter: LogWriterProtocol) { - self.adapter = adapter + init(logger: any LoggerProtocol) { + self.logger = logger super.init() } @@ -63,11 +23,20 @@ private class KermitLogWriterAdapter: Kermit_coreLogWriter { /// - tag: An optional string categorizing the log. /// - throwable: An optional Kotlin exception (ignored here). override func log(severity: Kermit_coreSeverity, message: String, tag: String, throwable: KotlinThrowable?) { - adapter.log( - severity: mapKermitSeverity(severity), - message: message, - tag: tag - ) + switch severity { + case PowerSyncKotlin.Kermit_coreSeverity.verbose: + return logger.debug(message, tag: tag) + case PowerSyncKotlin.Kermit_coreSeverity.debug: + return logger.debug(message, tag: tag) + case PowerSyncKotlin.Kermit_coreSeverity.info: + return logger.info(message, tag: tag) + case PowerSyncKotlin.Kermit_coreSeverity.warn: + return logger.warning(message, tag: tag) + case PowerSyncKotlin.Kermit_coreSeverity.error: + return logger.error(message, tag: tag) + case PowerSyncKotlin.Kermit_coreSeverity.assert: + return logger.fault(message, tag: tag) + } } } @@ -75,15 +44,21 @@ private class KermitLogWriterAdapter: Kermit_coreLogWriter { /// /// This class bridges Swift log writers with the Kotlin logging system and supports /// runtime configuration of severity levels and writer lists. -public class DatabaseLogger: LoggerProtocol { +internal class DatabaseLogger: LoggerProtocol { /// The underlying Kermit logger instance provided by the PowerSyncKotlin SDK. - internal let kLogger = PowerSyncKotlin.generateLogger(logger: nil) + public let kLogger = PowerSyncKotlin.generateLogger(logger: nil) + public let logger: any LoggerProtocol /// Initializes a new logger with an optional list of writers. /// /// - Parameter writers: An array of Swift log writers. Defaults to an empty array. - init(writers: [any LogWriterProtocol] = []) { - setWriters(writers) + init(_ logger: any LoggerProtocol) { + self.logger = logger + // Set to the lowest severity. The adapter downstream logger should filter by severity + kLogger.mutableConfig.setMinSeverity(Kermit_coreSeverity.verbose) + kLogger.mutableConfig.setLogWriterList( + [KermitLogWriterAdapter(logger: logger)] + ) } /// Sets the minimum severity level that will be logged. @@ -92,9 +67,7 @@ public class DatabaseLogger: LoggerProtocol { /// /// - Parameter severity: The minimum `LogSeverity` to allow through. public func setMinSeverity(_ severity: LogSeverity) { - kLogger.mutableConfig.setMinSeverity( - mapSeverity(severity) - ) + logger.setMinSeverity(severity) } /// Sets the list of log writers that will receive log messages. @@ -103,33 +76,31 @@ public class DatabaseLogger: LoggerProtocol { /// /// - Parameter writers: An array of Swift `LogWritterProtocol` implementations. public func setWriters(_ writers: [any LogWriterProtocol]) { - kLogger.mutableConfig.setLogWriterList( - writers.map { item in KermitLogWriterAdapter(adapter: item) } - ) + logger.setWriters(writers) } /// Logs a debug-level message. public func debug(_ message: String, tag: String) { - kLogger.d(messageString: message, throwable: nil, tag: tag) + logger.debug(message, tag: tag) } /// Logs an info-level message. public func info(_ message: String, tag: String) { - kLogger.i(messageString: message, throwable: nil, tag: tag) + logger.info(message, tag: tag) } /// Logs a warning-level message. public func warning(_ message: String, tag: String) { - kLogger.w(messageString: message, throwable: nil, tag: tag) + logger.warning(message, tag: tag) } /// Logs an error-level message. public func error(_ message: String, tag: String) { - kLogger.e(messageString: message, throwable: nil, tag: tag) + logger.error(message, tag: tag) } /// Logs a fault (assert-level) message, typically used for critical issues. public func fault(_ message: String, tag: String) { - kLogger.a(messageString: message, throwable: nil, tag: tag) + logger.fault(message, tag: tag) } } diff --git a/Sources/PowerSync/Logger.swift b/Sources/PowerSync/Logger.swift index 8c6351e..5e44710 100644 --- a/Sources/PowerSync/Logger.swift +++ b/Sources/PowerSync/Logger.swift @@ -1,10 +1,9 @@ import OSLog -/// A log writer that bridges custom `LogSeverity` levels to Apple's unified `Logger` framework. +/// A log writer which prints to the standard output /// /// This writer uses `os.Logger` on iOS 14+ and falls back to `print` for earlier versions. -/// Tags are optionally prefixed to messages in square brackets. -public class SwiftLogWriter: LogWriterProtocol { +public class PrintLogWriter: LogWriterProtocol { /// Logs a message with a given severity and optional tag. /// @@ -39,14 +38,54 @@ public class SwiftLogWriter: LogWriterProtocol { /// A default logger configuration that uses `SwiftLogWritter` and filters messages by minimum severity. /// /// This logger integrates with your custom logging system and uses `os.Logger` under the hood. -public class DefaultLogger: DatabaseLogger { +public class DefaultLogger: LoggerProtocol { + public var minSeverirty: LogSeverity + public var writers: [any LogWriterProtocol] /// Initializes the default logger with an optional minimum severity level. /// /// - Parameter minSeverity: The minimum severity level to log. Defaults to `.debug`. - public init(minSeverity: LogSeverity = .debug) { - super.init() - setMinSeverity(minSeverity) - setWriters([SwiftLogWriter()]) + public init(minSeverity: LogSeverity = .debug, writers: [any LogWriterProtocol]? = nil ) { + self.writers = writers ?? [ PrintLogWriter() ] + self.minSeverirty = minSeverity + } + + public func setWriters(_ writters: [any LogWriterProtocol]) { + self.writers = writters + } + + public func setMinSeverity(_ severity: LogSeverity) { + self.minSeverirty = severity + } + + + public func debug(_ message: String, tag: String) { + self.writeLog(message, tag: tag, severity: LogSeverity.debug) + } + + public func error(_ message: String, tag: String) { + self.writeLog(message, tag: tag, severity: LogSeverity.error) + } + + public func info(_ message: String, tag: String) { + self.writeLog(message, tag: tag, severity: LogSeverity.info) + } + + public func warning(_ message: String, tag: String) { + self.writeLog(message, tag: tag, severity: LogSeverity.warning) + } + + public func fault(_ message: String, tag: String) { + self.writeLog(message, tag: tag, severity: LogSeverity.fault) + } + + private func writeLog(_ message: String, tag: String, severity: LogSeverity) { + if (severity.rawValue < self.minSeverirty.rawValue) { + return + } + + for writer in self.writers { + writer.log(severity: severity, message: message, tag: tag) + } } } diff --git a/Sources/PowerSync/LoggerProtocol.swift b/Sources/PowerSync/LoggerProtocol.swift index 620b3c1..a99f4ce 100644 --- a/Sources/PowerSync/LoggerProtocol.swift +++ b/Sources/PowerSync/LoggerProtocol.swift @@ -1,19 +1,34 @@ -/// Represents the severity level of a log message. -public enum LogSeverity: String, CaseIterable { - /// Informational messages that highlight the progress of the application. - case info = "INFO" - - /// Error events that might still allow the application to continue running. - case error = "ERROR" - +public enum LogSeverity: Int, CaseIterable { /// Detailed information typically used for debugging. - case debug = "DEBUG" - + case debug = 0 + + /// Informational messages that highlight the progress of the application. + case info = 1 + /// Potentially harmful situations that are not necessarily errors. - case warning = "WARNING" - + case warning = 2 + + /// Error events that might still allow the application to continue running. + case error = 3 + /// Serious errors indicating critical failures, often unrecoverable. - case fault = "FAULT" + case fault = 4 + + /// Map severity to its string representation + public var stringValue: String { + switch self { + case .debug: return "DEBUG" + case .info: return "INFO" + case .warning: return "WARNING" + case .error: return "ERROR" + case .fault: return "FAULT" + } + } + + /// Convert Int to String representation + public static func string(from intValue: Int) -> String? { + return LogSeverity(rawValue: intValue)?.stringValue + } } /// A protocol for writing log messages to a specific backend or output. diff --git a/Sources/PowerSync/PowerSyncDatabase.swift b/Sources/PowerSync/PowerSyncDatabase.swift index e50ce87..41c7d71 100644 --- a/Sources/PowerSync/PowerSyncDatabase.swift +++ b/Sources/PowerSync/PowerSyncDatabase.swift @@ -12,12 +12,12 @@ public let DEFAULT_DB_FILENAME = "powersync.db" public func PowerSyncDatabase( schema: Schema, dbFilename: String = DEFAULT_DB_FILENAME, - logger: DatabaseLogger = DefaultLogger() + logger: (any LoggerProtocol)? = nil ) -> PowerSyncDatabaseProtocol { return KotlinPowerSyncDatabaseImpl( schema: schema, dbFilename: dbFilename, - logger: logger + logger: logger != nil ? DatabaseLogger(logger!) : nil ) } diff --git a/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift b/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift index 1bc0767..191d0fe 100644 --- a/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift +++ b/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift @@ -17,7 +17,7 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase { database = KotlinPowerSyncDatabaseImpl( schema: schema, dbFilename: ":memory:", - logger: DefaultLogger() + logger: DatabaseLogger(DefaultLogger()) ) try await database.disconnectAndClear() } @@ -472,17 +472,18 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase { } func testCustomLogger() async throws { - let logger = TestLogger() + let testWriter = TestLogWriterAdapter() + let logger = DefaultLogger(minSeverity: LogSeverity.debug, writers: [testWriter]) let db2 = KotlinPowerSyncDatabaseImpl( schema: schema, dbFilename: ":memory:", - logger: logger + logger: DatabaseLogger(logger) ) try await db2.close() - let warningIndex = logger.logs.firstIndex( + let warningIndex = testWriter.logs.firstIndex( where: { value in value.contains("warning: Multiple PowerSync instances for the same database have been detected") } @@ -492,17 +493,18 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase { } func testMinimumSeverity() async throws { - let logger = TestLogger(minSeverity: .error) + let testWriter = TestLogWriterAdapter() + let logger = DefaultLogger(minSeverity: LogSeverity.error, writers: [testWriter]) let db2 = KotlinPowerSyncDatabaseImpl( schema: schema, dbFilename: ":memory:", - logger: logger + logger: DatabaseLogger(logger) ) try await db2.close() - let warningIndex = logger.logs.firstIndex( + let warningIndex = testWriter.logs.firstIndex( where: { value in value.contains("warning: Multiple PowerSync instances for the same database have been detected") } diff --git a/Tests/PowerSyncTests/Kotlin/TestLogger.swift b/Tests/PowerSyncTests/Kotlin/TestLogger.swift index ecc8305..fa26789 100644 --- a/Tests/PowerSyncTests/Kotlin/TestLogger.swift +++ b/Tests/PowerSyncTests/Kotlin/TestLogger.swift @@ -9,15 +9,3 @@ class TestLogWriterAdapter: LogWriterProtocol { } } -class TestLogger: DefaultLogger { - let writer = TestLogWriterAdapter() - - public var logs: [String] { - return writer.logs - } - - override init(minSeverity: LogSeverity = .debug) { - super.init(minSeverity: minSeverity) - setWriters([writer]) - } -} From 7e9325ba4d2c5b4711183f68b9a868cb66aecdf2 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 8 Apr 2025 09:44:14 +0200 Subject: [PATCH 03/12] optional tag usage --- Sources/PowerSync/Kotlin/DatabaseLogger.swift | 20 ++++----- Sources/PowerSync/Logger.swift | 42 +++++++++++-------- Sources/PowerSync/LoggerProtocol.swift | 12 +++--- Tests/PowerSyncTests/Kotlin/TestLogger.swift | 4 +- 4 files changed, 42 insertions(+), 36 deletions(-) diff --git a/Sources/PowerSync/Kotlin/DatabaseLogger.swift b/Sources/PowerSync/Kotlin/DatabaseLogger.swift index 435ec97..eb0617e 100644 --- a/Sources/PowerSync/Kotlin/DatabaseLogger.swift +++ b/Sources/PowerSync/Kotlin/DatabaseLogger.swift @@ -9,7 +9,7 @@ private class KermitLogWriterAdapter: Kermit_coreLogWriter { /// Initializes a new adapter. /// - /// - Parameter adapter: A Swift log writer that will handle log output. + /// - Parameter logger: A Swift log writer that will handle log output. init(logger: any LoggerProtocol) { self.logger = logger super.init() @@ -20,7 +20,7 @@ private class KermitLogWriterAdapter: Kermit_coreLogWriter { /// - Parameters: /// - severity: The severity level of the log. /// - message: The content of the log message. - /// - tag: An optional string categorizing the log. + /// - tag: A string categorizing the log. /// - throwable: An optional Kotlin exception (ignored here). override func log(severity: Kermit_coreSeverity, message: String, tag: String, throwable: KotlinThrowable?) { switch severity { @@ -40,7 +40,7 @@ private class KermitLogWriterAdapter: Kermit_coreLogWriter { } } -/// A logger implementation that integrates with PowerSync's Kotlin backend using Kermit. +/// A logger implementation that integrates with PowerSync's Kotlin core using Kermit. /// /// This class bridges Swift log writers with the Kotlin logging system and supports /// runtime configuration of severity levels and writer lists. @@ -51,10 +51,10 @@ internal class DatabaseLogger: LoggerProtocol { /// Initializes a new logger with an optional list of writers. /// - /// - Parameter writers: An array of Swift log writers. Defaults to an empty array. + /// - Parameter logger: A logger which will be called for each internal log operation init(_ logger: any LoggerProtocol) { self.logger = logger - // Set to the lowest severity. The adapter downstream logger should filter by severity + // Set to the lowest severity. The provided logger should filter by severity kLogger.mutableConfig.setMinSeverity(Kermit_coreSeverity.verbose) kLogger.mutableConfig.setLogWriterList( [KermitLogWriterAdapter(logger: logger)] @@ -80,27 +80,27 @@ internal class DatabaseLogger: LoggerProtocol { } /// Logs a debug-level message. - public func debug(_ message: String, tag: String) { + public func debug(_ message: String, tag: String?) { logger.debug(message, tag: tag) } /// Logs an info-level message. - public func info(_ message: String, tag: String) { + public func info(_ message: String, tag: String?) { logger.info(message, tag: tag) } /// Logs a warning-level message. - public func warning(_ message: String, tag: String) { + public func warning(_ message: String, tag: String?) { logger.warning(message, tag: tag) } /// Logs an error-level message. - public func error(_ message: String, tag: String) { + public func error(_ message: String, tag: String?) { logger.error(message, tag: tag) } /// Logs a fault (assert-level) message, typically used for critical issues. - public func fault(_ message: String, tag: String) { + public func fault(_ message: String, tag: String?) { logger.fault(message, tag: tag) } } diff --git a/Sources/PowerSync/Logger.swift b/Sources/PowerSync/Logger.swift index 5e44710..4ec845f 100644 --- a/Sources/PowerSync/Logger.swift +++ b/Sources/PowerSync/Logger.swift @@ -11,8 +11,14 @@ public class PrintLogWriter: LogWriterProtocol { /// - severity: The severity level of the message. /// - message: The content of the log message. /// - tag: An optional tag used to categorize the message. If empty, no brackets are shown. - public func log(severity: LogSeverity, message: String, tag: String) { - let tagPrefix = tag.isEmpty ? "" : "[\(tag)] " + public func log(severity: LogSeverity, message: String, tag: String?) { + let tagPrefix: String + if let tag, !tag.isEmpty { + tagPrefix = "[\(tag)] " + } else { + tagPrefix = "" + } + let message = "\(tagPrefix) \(message)" if #available(iOS 14.0, *) { let l = Logger() @@ -30,21 +36,21 @@ public class PrintLogWriter: LogWriterProtocol { l.fault("\(message)") } } else { - print("\(severity.rawValue): \(message)") + print("\(severity.stringValue): \(message)") } } } -/// A default logger configuration that uses `SwiftLogWritter` and filters messages by minimum severity. -/// -/// This logger integrates with your custom logging system and uses `os.Logger` under the hood. +/// A default logger configuration that uses `PrintLogWritter` and filters messages by minimum severity. public class DefaultLogger: LoggerProtocol { public var minSeverirty: LogSeverity public var writers: [any LogWriterProtocol] /// Initializes the default logger with an optional minimum severity level. /// - /// - Parameter minSeverity: The minimum severity level to log. Defaults to `.debug`. + /// - Parameters + /// - minSeverity: The minimum severity level to log. Defaults to `.debug`. + /// - writers: Optional writers which logs should be written to. Defaults to a `PrintLogWriter`. public init(minSeverity: LogSeverity = .debug, writers: [any LogWriterProtocol]? = nil ) { self.writers = writers ?? [ PrintLogWriter() ] self.minSeverirty = minSeverity @@ -59,27 +65,27 @@ public class DefaultLogger: LoggerProtocol { } - public func debug(_ message: String, tag: String) { - self.writeLog(message, tag: tag, severity: LogSeverity.debug) + public func debug(_ message: String, tag: String? = nil) { + self.writeLog(message, severity: LogSeverity.debug, tag: tag) } - public func error(_ message: String, tag: String) { - self.writeLog(message, tag: tag, severity: LogSeverity.error) + public func error(_ message: String, tag: String? = nil) { + self.writeLog(message, severity: LogSeverity.error, tag: tag) } - public func info(_ message: String, tag: String) { - self.writeLog(message, tag: tag, severity: LogSeverity.info) + public func info(_ message: String, tag: String? = nil) { + self.writeLog(message, severity: LogSeverity.info, tag: tag) } - public func warning(_ message: String, tag: String) { - self.writeLog(message, tag: tag, severity: LogSeverity.warning) + public func warning(_ message: String, tag: String? = nil) { + self.writeLog(message, severity: LogSeverity.warning, tag: tag) } - public func fault(_ message: String, tag: String) { - self.writeLog(message, tag: tag, severity: LogSeverity.fault) + public func fault(_ message: String, tag: String? = nil) { + self.writeLog(message, severity: LogSeverity.fault, tag: tag) } - private func writeLog(_ message: String, tag: String, severity: LogSeverity) { + private func writeLog(_ message: String, severity: LogSeverity, tag: String?) { if (severity.rawValue < self.minSeverirty.rawValue) { return } diff --git a/Sources/PowerSync/LoggerProtocol.swift b/Sources/PowerSync/LoggerProtocol.swift index a99f4ce..4177d32 100644 --- a/Sources/PowerSync/LoggerProtocol.swift +++ b/Sources/PowerSync/LoggerProtocol.swift @@ -41,7 +41,7 @@ public protocol LogWriterProtocol { /// - severity: The severity level of the log message. /// - message: The content of the log message. /// - tag: An optional tag to categorize or group the log message. - func log(severity: LogSeverity, message: String, tag: String) + func log(severity: LogSeverity, message: String, tag: String?) } /// A protocol defining the interface for a logger that supports severity filtering and multiple writers. @@ -65,33 +65,33 @@ public protocol LoggerProtocol { /// - Parameters: /// - message: The content of the log message. /// - tag: An optional tag to categorize the message. - func info(_ message: String, tag: String) + func info(_ message: String, tag: String?) /// Logs an error message. /// /// - Parameters: /// - message: The content of the log message. /// - tag: An optional tag to categorize the message. - func error(_ message: String, tag: String) + func error(_ message: String, tag: String?) /// Logs a debug message. /// /// - Parameters: /// - message: The content of the log message. /// - tag: An optional tag to categorize the message. - func debug(_ message: String, tag: String) + func debug(_ message: String, tag: String?) /// Logs a warning message. /// /// - Parameters: /// - message: The content of the log message. /// - tag: An optional tag to categorize the message. - func warning(_ message: String, tag: String) + func warning(_ message: String, tag: String?) /// Logs a fault message, typically used for critical system-level failures. /// /// - Parameters: /// - message: The content of the log message. /// - tag: An optional tag to categorize the message. - func fault(_ message: String, tag: String) + func fault(_ message: String, tag: String?) } diff --git a/Tests/PowerSyncTests/Kotlin/TestLogger.swift b/Tests/PowerSyncTests/Kotlin/TestLogger.swift index fa26789..bff9ad1 100644 --- a/Tests/PowerSyncTests/Kotlin/TestLogger.swift +++ b/Tests/PowerSyncTests/Kotlin/TestLogger.swift @@ -4,8 +4,8 @@ class TestLogWriterAdapter: LogWriterProtocol { var logs = [String]() - func log(severity: LogSeverity, message: String, tag: String) { - logs.append("\(severity): \(message) (\(tag))") + func log(severity: LogSeverity, message: String, tag: String?) { + logs.append("\(severity): \(message) \(tag != nil ? "\(tag!)" : "")") } } From f3e921096a1c347a861ff518c452096a3278c0af Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 8 Apr 2025 09:51:51 +0200 Subject: [PATCH 04/12] Added logger to readme --- README.md | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 40a4849..682258c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

-*[PowerSync](https://www.powersync.com) is a sync engine for building local-first apps with instantly-responsive UI/UX and simplified state transfer. Syncs between SQLite on the client-side and Postgres, MongoDB or MySQL on the server-side.* +_[PowerSync](https://www.powersync.com) is a sync engine for building local-first apps with instantly-responsive UI/UX and simplified state transfer. Syncs between SQLite on the client-side and Postgres, MongoDB or MySQL on the server-side._ # PowerSync Swift @@ -16,7 +16,7 @@ This SDK is currently in a beta release it is suitable for production use, given - [Sources](./Sources/) - - This is the Swift SDK implementation. + - This is the Swift SDK implementation. ## Demo Apps / Example Projects @@ -51,11 +51,35 @@ to your `Package.swift` file and pin the dependency to a specific version. The v to your `Package.swift` file and pin the dependency to a specific version. This is required because the package is in beta. +## Usage + +Create a PowerSync client + +```swift +import PowerSync + +let powersync = PowerSyncDatabase( + schema: Schema( + tables: [ + Table( + name: "users", + columns: [ + .text("count"), + .integer("is_active"), + .real("weight"), + .text("description") + ] + ) + ] + ), + logger: DefaultLogger(minSeverity: .debug) +) +``` + ## Underlying Kotlin Dependency The PowerSync Swift SDK currently makes use of the [PowerSync Kotlin Multiplatform SDK](https://github.com/powersync-ja/powersync-kotlin) with the API tool [SKIE](https://skie.touchlab.co/) and KMMBridge under the hood to help generate and publish a native Swift package. We will move to an entirely Swift native API in v1 and do not expect there to be any breaking changes. For more details, see the [Swift SDK reference](https://docs.powersync.com/client-sdk-references/swift). - ## Migration from Alpha to Beta See these [developer notes](https://docs.powersync.com/client-sdk-references/swift#migrating-from-the-alpha-to-the-beta-sdk) if you are migrating from the alpha to the beta version of the Swift SDK. From 28cff0409a471b99086210ae9bbc872820c43fb9 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 8 Apr 2025 10:08:08 +0200 Subject: [PATCH 05/12] update test --- .../Kotlin/KotlinPowerSyncDatabaseImplTests.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift b/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift index 191d0fe..fb747d0 100644 --- a/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift +++ b/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift @@ -24,7 +24,12 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase { override func tearDown() async throws { try await database.disconnectAndClear() - try await database.close() + // Tests currently fail if this is called. + // The watched query tests try and read from the DB while it's closing. + // This causes a PowerSyncException to be thrown in the Kotlin flow. + // Custom exceptions are not supported by SKIEE. This causes a crash. + // FIXME: Reapply once watched query errors are handled better. + // try await database.close() database = nil try await super.tearDown() } From 075ff0cca7eed5abcb0672979fa7032f4c2162b4 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 8 Apr 2025 10:16:01 +0200 Subject: [PATCH 06/12] cleanup --- Sources/PowerSync/PowerSyncDatabase.swift | 4 ++-- .../Kotlin/KotlinPowerSyncDatabaseImplTests.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/PowerSync/PowerSyncDatabase.swift b/Sources/PowerSync/PowerSyncDatabase.swift index 41c7d71..dbabf92 100644 --- a/Sources/PowerSync/PowerSyncDatabase.swift +++ b/Sources/PowerSync/PowerSyncDatabase.swift @@ -12,12 +12,12 @@ public let DEFAULT_DB_FILENAME = "powersync.db" public func PowerSyncDatabase( schema: Schema, dbFilename: String = DEFAULT_DB_FILENAME, - logger: (any LoggerProtocol)? = nil + logger: (any LoggerProtocol) = DefaultLogger() ) -> PowerSyncDatabaseProtocol { return KotlinPowerSyncDatabaseImpl( schema: schema, dbFilename: dbFilename, - logger: logger != nil ? DatabaseLogger(logger!) : nil + logger: DatabaseLogger(logger) ) } diff --git a/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift b/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift index fb747d0..a97ff9f 100644 --- a/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift +++ b/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift @@ -27,7 +27,7 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase { // Tests currently fail if this is called. // The watched query tests try and read from the DB while it's closing. // This causes a PowerSyncException to be thrown in the Kotlin flow. - // Custom exceptions are not supported by SKIEE. This causes a crash. + // Custom exceptions in flows are not supported by SKIEE. This causes a crash. // FIXME: Reapply once watched query errors are handled better. // try await database.close() database = nil From 8f0911f32e74c9fe10d2119debab51cf946e5156 Mon Sep 17 00:00:00 2001 From: stevensJourney <51082125+stevensJourney@users.noreply.github.com> Date: Tue, 8 Apr 2025 10:24:16 +0200 Subject: [PATCH 07/12] Update Sources/PowerSync/Kotlin/DatabaseLogger.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Sources/PowerSync/Kotlin/DatabaseLogger.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PowerSync/Kotlin/DatabaseLogger.swift b/Sources/PowerSync/Kotlin/DatabaseLogger.swift index eb0617e..9b0289e 100644 --- a/Sources/PowerSync/Kotlin/DatabaseLogger.swift +++ b/Sources/PowerSync/Kotlin/DatabaseLogger.swift @@ -74,7 +74,7 @@ internal class DatabaseLogger: LoggerProtocol { /// /// This updates both the internal writer list and the Kermit logger's configuration. /// - /// - Parameter writers: An array of Swift `LogWritterProtocol` implementations. + /// - Parameter writers: An array of Swift `LogWriterProtocol` implementations. public func setWriters(_ writers: [any LogWriterProtocol]) { logger.setWriters(writers) } From b2efe62fb5deee24f2f4047c35bf5f5c9213c702 Mon Sep 17 00:00:00 2001 From: stevensJourney <51082125+stevensJourney@users.noreply.github.com> Date: Tue, 8 Apr 2025 10:24:32 +0200 Subject: [PATCH 08/12] Update Sources/PowerSync/LoggerProtocol.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Sources/PowerSync/LoggerProtocol.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/PowerSync/LoggerProtocol.swift b/Sources/PowerSync/LoggerProtocol.swift index 4177d32..105f0a1 100644 --- a/Sources/PowerSync/LoggerProtocol.swift +++ b/Sources/PowerSync/LoggerProtocol.swift @@ -57,9 +57,8 @@ public protocol LoggerProtocol { /// Sets the list of log writers that will handle log output. /// - /// - Parameter writters: An array of `LogWritterProtocol` conformers. - func setWriters(_ writters: [LogWriterProtocol]) - + /// - Parameter writers: An array of `LogWriterProtocol` conformers. + func setWriters(_ writers: [LogWriterProtocol]) /// Logs an informational message. /// /// - Parameters: From 12250ffd23140a778c270eee2e1e5cb59b2c2cc2 Mon Sep 17 00:00:00 2001 From: stevensJourney <51082125+stevensJourney@users.noreply.github.com> Date: Tue, 8 Apr 2025 10:24:41 +0200 Subject: [PATCH 09/12] Update Sources/PowerSync/Logger.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Sources/PowerSync/Logger.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PowerSync/Logger.swift b/Sources/PowerSync/Logger.swift index 4ec845f..7113a64 100644 --- a/Sources/PowerSync/Logger.swift +++ b/Sources/PowerSync/Logger.swift @@ -43,7 +43,7 @@ public class PrintLogWriter: LogWriterProtocol { /// A default logger configuration that uses `PrintLogWritter` and filters messages by minimum severity. public class DefaultLogger: LoggerProtocol { - public var minSeverirty: LogSeverity + public var minSeverity: LogSeverity public var writers: [any LogWriterProtocol] /// Initializes the default logger with an optional minimum severity level. From 608cb4614c5ee7ffed2b44e6e7e01b39293e23aa Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 8 Apr 2025 10:25:30 +0200 Subject: [PATCH 10/12] fix typo --- Sources/PowerSync/Logger.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/PowerSync/Logger.swift b/Sources/PowerSync/Logger.swift index 7113a64..648cd6e 100644 --- a/Sources/PowerSync/Logger.swift +++ b/Sources/PowerSync/Logger.swift @@ -53,7 +53,7 @@ public class DefaultLogger: LoggerProtocol { /// - writers: Optional writers which logs should be written to. Defaults to a `PrintLogWriter`. public init(minSeverity: LogSeverity = .debug, writers: [any LogWriterProtocol]? = nil ) { self.writers = writers ?? [ PrintLogWriter() ] - self.minSeverirty = minSeverity + self.minSeverity = minSeverity } public func setWriters(_ writters: [any LogWriterProtocol]) { @@ -61,7 +61,7 @@ public class DefaultLogger: LoggerProtocol { } public func setMinSeverity(_ severity: LogSeverity) { - self.minSeverirty = severity + self.minSeverity = severity } @@ -86,7 +86,7 @@ public class DefaultLogger: LoggerProtocol { } private func writeLog(_ message: String, severity: LogSeverity, tag: String?) { - if (severity.rawValue < self.minSeverirty.rawValue) { + if (severity.rawValue < self.minSeverity.rawValue) { return } From 23beb9adaa05f44beb99f9418a66008655c3ce39 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 8 Apr 2025 10:31:49 +0200 Subject: [PATCH 11/12] cleanup print logger --- Sources/PowerSync/Logger.swift | 67 +++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/Sources/PowerSync/Logger.swift b/Sources/PowerSync/Logger.swift index 648cd6e..988d013 100644 --- a/Sources/PowerSync/Logger.swift +++ b/Sources/PowerSync/Logger.swift @@ -2,45 +2,60 @@ import OSLog /// A log writer which prints to the standard output /// -/// This writer uses `os.Logger` on iOS 14+ and falls back to `print` for earlier versions. +/// This writer uses `os.Logger` on iOS/macOS/tvOS/watchOS 14+ and falls back to `print` for earlier versions. public class PrintLogWriter: LogWriterProtocol { + private let subsystem: String + private let category: String + private lazy var logger: Any? = { + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { + return Logger(subsystem: subsystem, category: category) + } + return nil + }() + + /// Creates a new PrintLogWriter + /// - Parameters: + /// - subsystem: The subsystem identifier (typically reverse DNS notation of your app) + /// - category: The category within your subsystem + public init(subsystem: String = Bundle.main.bundleIdentifier ?? "com.powersync.logger", + category: String = "default") { + self.subsystem = subsystem + self.category = category + } + /// Logs a message with a given severity and optional tag. - /// - /// - Parameters: - /// - severity: The severity level of the message. - /// - message: The content of the log message. - /// - tag: An optional tag used to categorize the message. If empty, no brackets are shown. + /// - Parameters: + /// - severity: The severity level of the message. + /// - message: The content of the log message. + /// - tag: An optional tag used to categorize the message. If empty, no brackets are shown. public func log(severity: LogSeverity, message: String, tag: String?) { - let tagPrefix: String - if let tag, !tag.isEmpty { - tagPrefix = "[\(tag)] " - } else { - tagPrefix = "" - } + let tagPrefix = tag.map { !$0.isEmpty ? "[\($0)] " : "" } ?? "" + let formattedMessage = "\(tagPrefix)\(message)" - let message = "\(tagPrefix) \(message)" - if #available(iOS 14.0, *) { - let l = Logger() + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { + guard let logger = logger as? Logger else { return } switch severity { - case .info: - l.info("\(message)") - case .error: - l.error("\(message)") - case .debug: - l.debug("\(message)") - case .warning: - l.warning("\(message)") - case .fault: - l.fault("\(message)") + case .info: + logger.info("\(formattedMessage, privacy: .public)") + case .error: + logger.error("\(formattedMessage, privacy: .public)") + case .debug: + logger.debug("\(formattedMessage, privacy: .public)") + case .warning: + logger.warning("\(formattedMessage, privacy: .public)") + case .fault: + logger.fault("\(formattedMessage, privacy: .public)") } } else { - print("\(severity.stringValue): \(message)") + print("\(severity.stringValue): \(formattedMessage)") } } } + + /// A default logger configuration that uses `PrintLogWritter` and filters messages by minimum severity. public class DefaultLogger: LoggerProtocol { public var minSeverity: LogSeverity From 0285979a15e73ac3797740b3cef16aa09cc727c3 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Mon, 14 Apr 2025 09:37:37 +0200 Subject: [PATCH 12/12] Cleanup protocol --- Sources/PowerSync/Kotlin/DatabaseLogger.swift | 18 ------------------ Sources/PowerSync/LoggerProtocol.swift | 11 ----------- 2 files changed, 29 deletions(-) diff --git a/Sources/PowerSync/Kotlin/DatabaseLogger.swift b/Sources/PowerSync/Kotlin/DatabaseLogger.swift index 9b0289e..21229d1 100644 --- a/Sources/PowerSync/Kotlin/DatabaseLogger.swift +++ b/Sources/PowerSync/Kotlin/DatabaseLogger.swift @@ -61,24 +61,6 @@ internal class DatabaseLogger: LoggerProtocol { ) } - /// Sets the minimum severity level that will be logged. - /// - /// Messages below this level will be ignored. - /// - /// - Parameter severity: The minimum `LogSeverity` to allow through. - public func setMinSeverity(_ severity: LogSeverity) { - logger.setMinSeverity(severity) - } - - /// Sets the list of log writers that will receive log messages. - /// - /// This updates both the internal writer list and the Kermit logger's configuration. - /// - /// - Parameter writers: An array of Swift `LogWriterProtocol` implementations. - public func setWriters(_ writers: [any LogWriterProtocol]) { - logger.setWriters(writers) - } - /// Logs a debug-level message. public func debug(_ message: String, tag: String?) { logger.debug(message, tag: tag) diff --git a/Sources/PowerSync/LoggerProtocol.swift b/Sources/PowerSync/LoggerProtocol.swift index 105f0a1..f2c3396 100644 --- a/Sources/PowerSync/LoggerProtocol.swift +++ b/Sources/PowerSync/LoggerProtocol.swift @@ -48,17 +48,6 @@ public protocol LogWriterProtocol { /// /// Conformers provide logging APIs and manage attached log writers. public protocol LoggerProtocol { - /// Sets the minimum severity level to be logged. - /// - /// Log messages below this severity will be ignored. - /// - /// - Parameter severity: The minimum severity level to log. - func setMinSeverity(_ severity: LogSeverity) - - /// Sets the list of log writers that will handle log output. - /// - /// - Parameter writers: An array of `LogWriterProtocol` conformers. - func setWriters(_ writers: [LogWriterProtocol]) /// Logs an informational message. /// /// - Parameters: