From 6052f3cb031f430ef6db75baf085a87a2a63e285 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Thu, 31 Jul 2025 17:42:41 +0200 Subject: [PATCH 1/9] Add network logging support --- CHANGELOG.md | 23 +++++++ .../PowerSync/SystemManager.swift | 18 +++++- .../Kotlin/KotlinNetworkLogger.swift | 29 +++++++++ .../Kotlin/KotlinPowerSyncDatabaseImpl.swift | 8 +-- .../PowerSync/Protocol/NetworkLogger.swift | 54 +++++++++++++++++ .../Protocol/PowerSyncDatabaseProtocol.swift | 60 +++++++++++++------ 6 files changed, 166 insertions(+), 26 deletions(-) create mode 100644 Sources/PowerSync/Kotlin/KotlinNetworkLogger.swift create mode 100644 Sources/PowerSync/Protocol/NetworkLogger.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index ac6fe39..e0d67f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 1.4.0 + +* Added the ability to log PowerSync network requests. +```swift +struct InlineLogger: NetworkLogger { + func log(_ message: String) { + print("Network: \(message)") + } +} + +try await db.connect( + connector: connector, + options: ConnectOptions( + clientConfiguration: SyncClientConfiguration( + networkLogger: NetworkLoggerConfig( + logLevel: .headers, + logger: InlineLogger() + ) + ) + ) + ) +``` + ## 1.3.0 * Use version `0.4.2` of the PowerSync core extension, which improves the reliability diff --git a/Demo/PowerSyncExample/PowerSync/SystemManager.swift b/Demo/PowerSyncExample/PowerSync/SystemManager.swift index 0246f7f..13b468b 100644 --- a/Demo/PowerSyncExample/PowerSync/SystemManager.swift +++ b/Demo/PowerSyncExample/PowerSync/SystemManager.swift @@ -13,6 +13,12 @@ func getAttachmentsDirectoryPath() throws -> String { let logTag = "SystemManager" +struct InlineLogger: NetworkLogger { + func log(_ message: String) { + print("Network: \(message)") + } +} + @Observable class SystemManager { let connector = SupabaseConnector() @@ -70,7 +76,17 @@ class SystemManager { func connect() async { do { - try await db.connect(connector: connector) + try await db.connect( + connector: connector, + options: ConnectOptions( + clientConfiguration: SyncClientConfiguration( + networkLogger: NetworkLoggerConfig( + logLevel: .headers, + logger: InlineLogger() + ) + ) + ) + ) try await attachments?.startSync() } catch { print("Unexpected error: \(error.localizedDescription)") // Catches any other error diff --git a/Sources/PowerSync/Kotlin/KotlinNetworkLogger.swift b/Sources/PowerSync/Kotlin/KotlinNetworkLogger.swift new file mode 100644 index 0000000..4997dcc --- /dev/null +++ b/Sources/PowerSync/Kotlin/KotlinNetworkLogger.swift @@ -0,0 +1,29 @@ +import PowerSyncKotlin + +extension NetworkLogLevel { + func toKotlin() -> SwiftNetworkLogLevel { + switch self { + case .all: + return SwiftNetworkLogLevel.all + case .headers: + return SwiftNetworkLogLevel.headers + case .body: + return SwiftNetworkLogLevel.body + case .info: + return SwiftNetworkLogLevel.info + case .none: + return SwiftNetworkLogLevel.none + } + } +} + +extension NetworkLoggerConfig { + func toKotlinConfig() -> SwiftNetworkLoggerConfig { + return SwiftNetworkLoggerConfig( + logLevel: self.logLevel.toKotlin(), + log: { [logger] message in + logger.log(message) + } + ) + } +} diff --git a/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift b/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift index ccfcd3a..45e1553 100644 --- a/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift +++ b/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift @@ -52,10 +52,6 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol { ) let resolvedOptions = options ?? ConnectOptions() - let useWebsockets = switch (resolvedOptions.connectionMethod) { - case .http: false - case .webSocket: true - } try await kotlinDatabase.connect( connector: connectorAdapter, @@ -64,8 +60,8 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol { params: resolvedOptions.params.mapValues { $0.toKotlinMap() }, options: createSyncOptions( newClient: resolvedOptions.newClientImplementation, - webSocket: useWebsockets, - userAgent: "PowerSync Swift SDK" + userAgent: "PowerSync Swift SDK", + loggingConfig: resolvedOptions.clientConfiguration?.networkLogger?.toKotlinConfig() ) ) } diff --git a/Sources/PowerSync/Protocol/NetworkLogger.swift b/Sources/PowerSync/Protocol/NetworkLogger.swift new file mode 100644 index 0000000..f91dc74 --- /dev/null +++ b/Sources/PowerSync/Protocol/NetworkLogger.swift @@ -0,0 +1,54 @@ +/// A logger which handles PowerSync network request logs. +/// +/// Implement this protocol to receive network logging messages at the level +/// specified in `NetworkLoggerConfig`. The `log(_:)` method will be called +/// for each network event that meets the configured logging criteria. +public protocol NetworkLogger { + /// Logs a network-related message. + /// - Parameter message: The formatted log message to record + func log(_ message: String) +} + +/// Level of logs to expose to a `NetworkLogger` handler. +/// +/// Controls the verbosity of network logging for PowerSync HTTP requests. +/// The log level is configured once during initialization and determines +/// which network events will be logged throughout the session. +public enum NetworkLogLevel { + /// Log all network activity including headers, body, and info + case all + /// Log only request/response headers + case headers + /// Log only request/response body content + case body + /// Log basic informational messages about requests + case info + /// Disable all network logging + case none +} + +/// Configuration for PowerSync HTTP request logging. +/// +/// This configuration is set once during initialization and used throughout +/// the PowerSync session. The `logLevel` determines which network events +/// are logged, while the `logger` handles the actual log output. +/// +/// - Note: The log level cannot be changed after initialization. A new call to `PowerSyncDatabase.connect` is required to change the level. +public struct NetworkLoggerConfig { + /// The logging level that determines which network events are logged. + /// Set once during initialization and used throughout the session. + public let logLevel: NetworkLogLevel + + /// The logger instance that receives network log messages. + /// Must conform to `NetworkLogger` protocol. + public let logger: NetworkLogger + + /// Creates a new network logger configuration. + /// - Parameters: + /// - logLevel: The `NetworkLogLevel` to use for filtering log messages + /// - logger: A `NetworkLogger` instance to handle log output + public init(logLevel: NetworkLogLevel, logger: NetworkLogger) { + self.logLevel = logLevel + self.logger = logger + } +} diff --git a/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift b/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift index c8b63c2..a0dd2b4 100644 --- a/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift +++ b/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift @@ -1,5 +1,28 @@ import Foundation +/// Configuration for the sync client used to connect to the PowerSync service. +/// +/// Provides options to customize network behavior and logging for PowerSync +/// HTTP requests and responses. +public struct SyncClientConfiguration { + /// Optional configuration for logging PowerSync HTTP requests. + /// + /// When provided, network requests will be logged according to the + /// specified `NetworkLogLevel`. The `logLevel` is set during initialization + /// and remains constant throughout the PowerSync session. + /// + /// Set to `nil` to disable network logging entirely. + /// + /// - SeeAlso: `NetworkLoggerConfig` for configuration options + public let networkLogger: NetworkLoggerConfig? + + /// Creates a new sync client configuration. + /// - Parameter networkLogger: Optional network logger configuration + public init(networkLogger: NetworkLoggerConfig? = nil) { + self.networkLogger = networkLogger + } +} + /// Options for configuring a PowerSync connection. /// /// Provides optional parameters to customize sync behavior such as throttling and retry policies. @@ -41,14 +64,18 @@ public struct ConnectOptions { /// We encourage interested users to try the new client. @_spi(PowerSyncExperimental) public var newClientImplementation: Bool - - /// The connection method used to connect to the Powersync service. + + /// Configuration for the sync client used for PowerSync requests. /// - /// The default method is ``ConnectionMethod/http``. Using ``ConnectionMethod/webSocket(_:)`` can - /// improve performance as a more efficient binary protocol is used. However, using the websocket connection method - /// requires enabling ``ConnectOptions/newClientImplementation``. - @_spi(PowerSyncExperimental) - public var connectionMethod: ConnectionMethod + /// Provides options to customize network behavior including logging of HTTP + /// requests and responses. When `nil`, default HTTP client settings are used + /// with no network logging. + /// + /// Set this to configure network logging or other HTTP client behaviors + /// specific to PowerSync operations. + /// + /// - SeeAlso: `SyncClientConfiguration` for available configuration options + public var clientConfiguration: SyncClientConfiguration? /// Initializes a `ConnectOptions` instance with optional values. /// @@ -56,16 +83,18 @@ public struct ConnectOptions { /// - crudThrottle: TimeInterval between CRUD operations in milliseconds. Defaults to `1` second. /// - retryDelay: Delay TimeInterval between retry attempts in milliseconds. Defaults to `5` seconds. /// - params: Custom sync parameters to send to the server. Defaults to an empty dictionary. + /// - clientConfiguration: Configuration for the HTTP client used to connect to PowerSync. public init( crudThrottle: TimeInterval = 1, retryDelay: TimeInterval = 5, - params: JsonParam = [:] + params: JsonParam = [:], + clientConfiguration: SyncClientConfiguration? = nil ) { self.crudThrottle = crudThrottle self.retryDelay = retryDelay self.params = params self.newClientImplementation = false - self.connectionMethod = .http + self.clientConfiguration = clientConfiguration } /// Initializes a ``ConnectOptions`` instance with optional values, including experimental options. @@ -75,22 +104,16 @@ public struct ConnectOptions { retryDelay: TimeInterval = 5, params: JsonParam = [:], newClientImplementation: Bool = false, - connectionMethod: ConnectionMethod = .http + clientConfiguration: SyncClientConfiguration? = nil ) { self.crudThrottle = crudThrottle self.retryDelay = retryDelay self.params = params self.newClientImplementation = newClientImplementation - self.connectionMethod = connectionMethod + self.clientConfiguration = clientConfiguration } } -@_spi(PowerSyncExperimental) -public enum ConnectionMethod { - case http - case webSocket -} - /// A PowerSync managed database. /// /// Use one instance per database file. @@ -108,7 +131,6 @@ public protocol PowerSyncDatabaseProtocol: Queries { /// Wait for the first sync to occur func waitForFirstSync() async throws - /// Replace the schema with a new version. This is for advanced use cases - typically the schema /// should just be specified once in the constructor. /// @@ -247,7 +269,7 @@ public extension PowerSyncDatabaseProtocol { } func disconnectAndClear(clearLocal: Bool = true) async throws { - try await self.disconnectAndClear(clearLocal: clearLocal) + try await disconnectAndClear(clearLocal: clearLocal) } func getCrudBatch(limit: Int32 = 100) async throws -> CrudBatch? { From dbedebade1f0ad8993d084a2714b712db367469a Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 6 Aug 2025 09:26:46 +0200 Subject: [PATCH 2/9] add unit test for logs --- Tests/PowerSyncTests/ConnectTests.swift | 53 +++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/Tests/PowerSyncTests/ConnectTests.swift b/Tests/PowerSyncTests/ConnectTests.swift index 3df8894..bd18627 100644 --- a/Tests/PowerSyncTests/ConnectTests.swift +++ b/Tests/PowerSyncTests/ConnectTests.swift @@ -88,4 +88,57 @@ final class ConnectTests: XCTestCase { await fulfillment(of: [expectation], timeout: 5) watchTask.cancel() } + + func testSyncHTTPLogs() async throws { + let expectation = XCTestExpectation( + description: "Should log a request to the PowerSync endpoint" + ) + + let fakeUrl = "https://fakepowersyncinstance.fakepowersync.local" + + struct InlineLogger: NetworkLogger { + let logger: (_: String) -> Void + + func log(_ message: String) { + logger(message) + } + } + + let testLogger = InlineLogger { message in + // We want to see a request to the specified instance + if message.contains(fakeUrl) { + expectation.fulfill() + } + } + + class TestConnector: PowerSyncBackendConnector { + let url: String + + init(url: String) { + self.url = url + } + + override func fetchCredentials() async throws -> PowerSyncCredentials? { + PowerSyncCredentials( + endpoint: url, + token: "123" + ) + } + } + + try await database.connect( + connector: TestConnector(url: fakeUrl), + options: ConnectOptions( + clientConfiguration: SyncClientConfiguration( + networkLogger: NetworkLoggerConfig( + logLevel: + .all, + logger: testLogger + ) + ) + ) + ) + + await fulfillment(of: [expectation], timeout: 5) + } } From b906368278f20f4f9fcb6dd15e578adc6f813bc4 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 6 Aug 2025 09:47:13 +0200 Subject: [PATCH 3/9] update naming in protocols --- CHANGELOG.md | 7 ++--- .../Kotlin/KotlinNetworkLogger.swift | 20 +++++++------- .../Kotlin/KotlinPowerSyncDatabaseImpl.swift | 2 +- .../Protocol/PowerSyncDatabaseProtocol.swift | 14 +++++----- ...rkLogger.swift => SyncRequestLogger.swift} | 26 +++++++++---------- Tests/PowerSyncTests/ConnectTests.swift | 4 +-- 6 files changed, 37 insertions(+), 36 deletions(-) rename Sources/PowerSync/Protocol/{NetworkLogger.swift => SyncRequestLogger.swift} (65%) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0d67f5..63fdf5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,10 @@ ## 1.4.0 -* Added the ability to log PowerSync network requests. +* Added the ability to log PowerSync sync network requests. + ```swift -struct InlineLogger: NetworkLogger { +struct InlineLogger: SyncRequestLogger { func log(_ message: String) { print("Network: \(message)") } @@ -14,7 +15,7 @@ try await db.connect( connector: connector, options: ConnectOptions( clientConfiguration: SyncClientConfiguration( - networkLogger: NetworkLoggerConfig( + requestLogger: SyncRequestLoggerConfiguration( logLevel: .headers, logger: InlineLogger() ) diff --git a/Sources/PowerSync/Kotlin/KotlinNetworkLogger.swift b/Sources/PowerSync/Kotlin/KotlinNetworkLogger.swift index 4997dcc..735c51f 100644 --- a/Sources/PowerSync/Kotlin/KotlinNetworkLogger.swift +++ b/Sources/PowerSync/Kotlin/KotlinNetworkLogger.swift @@ -1,25 +1,25 @@ import PowerSyncKotlin -extension NetworkLogLevel { - func toKotlin() -> SwiftNetworkLogLevel { +extension SyncRequestLogLevel { + func toKotlin() -> SwiftSyncRequestLogLevel { switch self { case .all: - return SwiftNetworkLogLevel.all + return SwiftSyncRequestLogLevel.all case .headers: - return SwiftNetworkLogLevel.headers + return SwiftSyncRequestLogLevel.headers case .body: - return SwiftNetworkLogLevel.body + return SwiftSyncRequestLogLevel.body case .info: - return SwiftNetworkLogLevel.info + return SwiftSyncRequestLogLevel.info case .none: - return SwiftNetworkLogLevel.none + return SwiftSyncRequestLogLevel.none } } } -extension NetworkLoggerConfig { - func toKotlinConfig() -> SwiftNetworkLoggerConfig { - return SwiftNetworkLoggerConfig( +extension SyncRequestLoggerConfiguration { + func toKotlinConfig() -> SwiftRequestLoggerConfig { + return SwiftRequestLoggerConfig( logLevel: self.logLevel.toKotlin(), log: { [logger] message in logger.log(message) diff --git a/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift b/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift index 45e1553..2c2c144 100644 --- a/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift +++ b/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift @@ -61,7 +61,7 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol { options: createSyncOptions( newClient: resolvedOptions.newClientImplementation, userAgent: "PowerSync Swift SDK", - loggingConfig: resolvedOptions.clientConfiguration?.networkLogger?.toKotlinConfig() + loggingConfig: resolvedOptions.clientConfiguration?.requestLogger?.toKotlinConfig() ) ) } diff --git a/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift b/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift index a0dd2b4..9bf28f8 100644 --- a/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift +++ b/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift @@ -8,18 +8,18 @@ public struct SyncClientConfiguration { /// Optional configuration for logging PowerSync HTTP requests. /// /// When provided, network requests will be logged according to the - /// specified `NetworkLogLevel`. The `logLevel` is set during initialization + /// specified `SyncRequestLogLevel`. The `logLevel` is set during initialization /// and remains constant throughout the PowerSync session. /// - /// Set to `nil` to disable network logging entirely. + /// Set to `nil` to disable request logging entirely. /// - /// - SeeAlso: `NetworkLoggerConfig` for configuration options - public let networkLogger: NetworkLoggerConfig? + /// - SeeAlso: `SyncRequestLoggerConfiguration` for configuration options + public let requestLogger: SyncRequestLoggerConfiguration? /// Creates a new sync client configuration. - /// - Parameter networkLogger: Optional network logger configuration - public init(networkLogger: NetworkLoggerConfig? = nil) { - self.networkLogger = networkLogger + /// - Parameter requestLogger: Optional network logger configuration + public init(requestLogger: SyncRequestLoggerConfiguration? = nil) { + self.requestLogger = requestLogger } } diff --git a/Sources/PowerSync/Protocol/NetworkLogger.swift b/Sources/PowerSync/Protocol/SyncRequestLogger.swift similarity index 65% rename from Sources/PowerSync/Protocol/NetworkLogger.swift rename to Sources/PowerSync/Protocol/SyncRequestLogger.swift index f91dc74..4b5a57a 100644 --- a/Sources/PowerSync/Protocol/NetworkLogger.swift +++ b/Sources/PowerSync/Protocol/SyncRequestLogger.swift @@ -1,20 +1,20 @@ /// A logger which handles PowerSync network request logs. /// -/// Implement this protocol to receive network logging messages at the level -/// specified in `NetworkLoggerConfig`. The `log(_:)` method will be called +/// Implement this protocol to receive network request logging messages at the level +/// specified in `SyncRequestLoggerConfiguration`. The `log(_:)` method will be called /// for each network event that meets the configured logging criteria. -public protocol NetworkLogger { +public protocol SyncRequestLogger { /// Logs a network-related message. /// - Parameter message: The formatted log message to record func log(_ message: String) } -/// Level of logs to expose to a `NetworkLogger` handler. +/// Level of logs to expose to a `SyncRequestLogger` handler. /// /// Controls the verbosity of network logging for PowerSync HTTP requests. /// The log level is configured once during initialization and determines /// which network events will be logged throughout the session. -public enum NetworkLogLevel { +public enum SyncRequestLogLevel { /// Log all network activity including headers, body, and info case all /// Log only request/response headers @@ -34,20 +34,20 @@ public enum NetworkLogLevel { /// are logged, while the `logger` handles the actual log output. /// /// - Note: The log level cannot be changed after initialization. A new call to `PowerSyncDatabase.connect` is required to change the level. -public struct NetworkLoggerConfig { +public struct SyncRequestLoggerConfiguration { /// The logging level that determines which network events are logged. /// Set once during initialization and used throughout the session. - public let logLevel: NetworkLogLevel + public let logLevel: SyncRequestLogLevel - /// The logger instance that receives network log messages. - /// Must conform to `NetworkLogger` protocol. - public let logger: NetworkLogger + /// The logger instance that receives network request log messages. + /// Must conform to `SyncRequestLogger` protocol. + public let logger: SyncRequestLogger /// Creates a new network logger configuration. /// - Parameters: - /// - logLevel: The `NetworkLogLevel` to use for filtering log messages - /// - logger: A `NetworkLogger` instance to handle log output - public init(logLevel: NetworkLogLevel, logger: NetworkLogger) { + /// - logLevel: The `SyncRequestLogLevel` to use for filtering log messages + /// - logger: A `SyncRequestLogger` instance to handle log output + public init(logLevel: SyncRequestLogLevel, logger: SyncRequestLogger) { self.logLevel = logLevel self.logger = logger } diff --git a/Tests/PowerSyncTests/ConnectTests.swift b/Tests/PowerSyncTests/ConnectTests.swift index bd18627..5523cd9 100644 --- a/Tests/PowerSyncTests/ConnectTests.swift +++ b/Tests/PowerSyncTests/ConnectTests.swift @@ -96,7 +96,7 @@ final class ConnectTests: XCTestCase { let fakeUrl = "https://fakepowersyncinstance.fakepowersync.local" - struct InlineLogger: NetworkLogger { + struct InlineLogger: SyncRequestLogger { let logger: (_: String) -> Void func log(_ message: String) { @@ -130,7 +130,7 @@ final class ConnectTests: XCTestCase { connector: TestConnector(url: fakeUrl), options: ConnectOptions( clientConfiguration: SyncClientConfiguration( - networkLogger: NetworkLoggerConfig( + requestLogger: SyncRequestLoggerConfiguration( logLevel: .all, logger: testLogger From 782056fa29626304aae5b678bc2bdb51e7f815ee Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 6 Aug 2025 09:49:50 +0200 Subject: [PATCH 4/9] update demo to use latest naming --- Demo/PowerSyncExample/PowerSync/SystemManager.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Demo/PowerSyncExample/PowerSync/SystemManager.swift b/Demo/PowerSyncExample/PowerSync/SystemManager.swift index 13b468b..528f0fb 100644 --- a/Demo/PowerSyncExample/PowerSync/SystemManager.swift +++ b/Demo/PowerSyncExample/PowerSync/SystemManager.swift @@ -13,7 +13,7 @@ func getAttachmentsDirectoryPath() throws -> String { let logTag = "SystemManager" -struct InlineLogger: NetworkLogger { +struct InlineLogger: SyncRequestLogger { func log(_ message: String) { print("Network: \(message)") } @@ -80,7 +80,7 @@ class SystemManager { connector: connector, options: ConnectOptions( clientConfiguration: SyncClientConfiguration( - networkLogger: NetworkLoggerConfig( + requestLogger: SyncRequestLoggerConfiguration( logLevel: .headers, logger: InlineLogger() ) From 503f10a894edd3c79747ca2fb7e653038ebc9554 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Wed, 6 Aug 2025 12:56:21 +0200 Subject: [PATCH 5/9] Add init which accepts LoggerProtocol. Generally cleanup logger config. --- CHANGELOG.md | 30 ++++---- .../PowerSync/SystemManager.swift | 13 +--- .../Kotlin/KotlinNetworkLogger.swift | 6 +- .../Protocol/PowerSyncDatabaseProtocol.swift | 5 +- .../Protocol/SyncRequestLogger.swift | 77 +++++++++++++------ Tests/PowerSyncTests/ConnectTests.swift | 26 ++----- 6 files changed, 81 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 697bc5e..f34a2d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,23 +5,19 @@ * Added the ability to log PowerSync sync network requests. ```swift -struct InlineLogger: SyncRequestLogger { - func log(_ message: String) { - print("Network: \(message)") - } -} - -try await db.connect( - connector: connector, - options: ConnectOptions( - clientConfiguration: SyncClientConfiguration( - requestLogger: SyncRequestLoggerConfiguration( - logLevel: .headers, - logger: InlineLogger() - ) - ) - ) - ) +try await database.connect( + connector: Connector(), + options: ConnectOptions( + clientConfiguration: SyncClientConfiguration( + requestLogger: SyncRequestLoggerConfiguration( + requestLevel: .headers + ) { message in + // Handle Network request logs here + print(message) + } + ) + ) +) ``` ## 1.3.1 diff --git a/Demo/PowerSyncExample/PowerSync/SystemManager.swift b/Demo/PowerSyncExample/PowerSync/SystemManager.swift index 528f0fb..4737c52 100644 --- a/Demo/PowerSyncExample/PowerSync/SystemManager.swift +++ b/Demo/PowerSyncExample/PowerSync/SystemManager.swift @@ -13,12 +13,6 @@ func getAttachmentsDirectoryPath() throws -> String { let logTag = "SystemManager" -struct InlineLogger: SyncRequestLogger { - func log(_ message: String) { - print("Network: \(message)") - } -} - @Observable class SystemManager { let connector = SupabaseConnector() @@ -81,9 +75,10 @@ class SystemManager { options: ConnectOptions( clientConfiguration: SyncClientConfiguration( requestLogger: SyncRequestLoggerConfiguration( - logLevel: .headers, - logger: InlineLogger() - ) + requestLevel: .headers + ) { message in + self.db.logger.debug(message, tag: "SyncRequest") + } ) ) ) diff --git a/Sources/PowerSync/Kotlin/KotlinNetworkLogger.swift b/Sources/PowerSync/Kotlin/KotlinNetworkLogger.swift index 735c51f..767a550 100644 --- a/Sources/PowerSync/Kotlin/KotlinNetworkLogger.swift +++ b/Sources/PowerSync/Kotlin/KotlinNetworkLogger.swift @@ -20,9 +20,9 @@ extension SyncRequestLogLevel { extension SyncRequestLoggerConfiguration { func toKotlinConfig() -> SwiftRequestLoggerConfig { return SwiftRequestLoggerConfig( - logLevel: self.logLevel.toKotlin(), - log: { [logger] message in - logger.log(message) + logLevel: self.requestLevel.toKotlin(), + log: { [log] message in + log(message) } ) } diff --git a/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift b/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift index 9bf28f8..37a26c7 100644 --- a/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift +++ b/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift @@ -8,10 +8,7 @@ public struct SyncClientConfiguration { /// Optional configuration for logging PowerSync HTTP requests. /// /// When provided, network requests will be logged according to the - /// specified `SyncRequestLogLevel`. The `logLevel` is set during initialization - /// and remains constant throughout the PowerSync session. - /// - /// Set to `nil` to disable request logging entirely. + /// specified `SyncRequestLoggerConfiguration`. Set to `nil` to disable request logging entirely. /// /// - SeeAlso: `SyncRequestLoggerConfiguration` for configuration options public let requestLogger: SyncRequestLoggerConfiguration? diff --git a/Sources/PowerSync/Protocol/SyncRequestLogger.swift b/Sources/PowerSync/Protocol/SyncRequestLogger.swift index 4b5a57a..40c2284 100644 --- a/Sources/PowerSync/Protocol/SyncRequestLogger.swift +++ b/Sources/PowerSync/Protocol/SyncRequestLogger.swift @@ -1,14 +1,3 @@ -/// A logger which handles PowerSync network request logs. -/// -/// Implement this protocol to receive network request logging messages at the level -/// specified in `SyncRequestLoggerConfiguration`. The `log(_:)` method will be called -/// for each network event that meets the configured logging criteria. -public protocol SyncRequestLogger { - /// Logs a network-related message. - /// - Parameter message: The formatted log message to record - func log(_ message: String) -} - /// Level of logs to expose to a `SyncRequestLogger` handler. /// /// Controls the verbosity of network logging for PowerSync HTTP requests. @@ -30,25 +19,65 @@ public enum SyncRequestLogLevel { /// Configuration for PowerSync HTTP request logging. /// /// This configuration is set once during initialization and used throughout -/// the PowerSync session. The `logLevel` determines which network events -/// are logged, while the `logger` handles the actual log output. +/// the PowerSync session. The `requestLevel` determines which network events +/// are logged. /// -/// - Note: The log level cannot be changed after initialization. A new call to `PowerSyncDatabase.connect` is required to change the level. +/// - Note: The request levell cannot be changed after initialization. A new call to `PowerSyncDatabase.connect` is required to change the level. public struct SyncRequestLoggerConfiguration { - /// The logging level that determines which network events are logged. + /// The request logging level that determines which network events are logged. /// Set once during initialization and used throughout the session. - public let logLevel: SyncRequestLogLevel + public let requestLevel: SyncRequestLogLevel - /// The logger instance that receives network request log messages. - /// Must conform to `SyncRequestLogger` protocol. - public let logger: SyncRequestLogger + private let logHandler: (_ message: String) -> Void /// Creates a new network logger configuration. /// - Parameters: - /// - logLevel: The `SyncRequestLogLevel` to use for filtering log messages - /// - logger: A `SyncRequestLogger` instance to handle log output - public init(logLevel: SyncRequestLogLevel, logger: SyncRequestLogger) { - self.logLevel = logLevel - self.logger = logger + /// - requestLevel: The `SyncRequestLogLevel` to use for filtering log messages + /// - logHandler: A closure which handles log messages + public init( + requestLevel: SyncRequestLogLevel, + logHandler: @escaping (_ message: String) -> Void) + { + self.requestLevel = requestLevel + self.logHandler = logHandler + } + + public func log(_ message: String) { + logHandler(message) + } + + /// Creates a new network logger configuration using a `LoggerProtocol` instance. + /// + /// This initializer allows integration with an existing logging framework by adapting + /// a `LoggerProtocol` to conform to `SyncRequestLogger`. The specified `logSeverity` + /// controls the severity level at which log messages are recorded. An optional `logTag` + /// may be used to help categorize logs. + /// + /// - Parameters: + /// - requestLevel: The `SyncRequestLogLevel` to use for filtering which network events are logged. + /// - logger: An object conforming to `LoggerProtocol` that will receive log messages. + /// - logSeverity: The severity level to use for all log messages (defaults to `.debug`). + /// - logTag: An optional tag to include with each log message, for use by the logging backend. + public init( + requestLevel: SyncRequestLogLevel, + logger: LoggerProtocol, + logSeverity: LogSeverity = .debug, + logTag: String? = nil) + { + self.requestLevel = requestLevel + self.logHandler = { message in + switch logSeverity { + case .debug: + logger.debug(message, tag: logTag) + case .info: + logger.info(message, tag: logTag) + case .warning: + logger.warning(message, tag: logTag) + case .error: + logger.error(message, tag: logTag) + case .fault: + logger.fault(message, tag: logTag) + } + } } } diff --git a/Tests/PowerSyncTests/ConnectTests.swift b/Tests/PowerSyncTests/ConnectTests.swift index 5523cd9..44a2f50 100644 --- a/Tests/PowerSyncTests/ConnectTests.swift +++ b/Tests/PowerSyncTests/ConnectTests.swift @@ -96,21 +96,6 @@ final class ConnectTests: XCTestCase { let fakeUrl = "https://fakepowersyncinstance.fakepowersync.local" - struct InlineLogger: SyncRequestLogger { - let logger: (_: String) -> Void - - func log(_ message: String) { - logger(message) - } - } - - let testLogger = InlineLogger { message in - // We want to see a request to the specified instance - if message.contains(fakeUrl) { - expectation.fulfill() - } - } - class TestConnector: PowerSyncBackendConnector { let url: String @@ -131,10 +116,13 @@ final class ConnectTests: XCTestCase { options: ConnectOptions( clientConfiguration: SyncClientConfiguration( requestLogger: SyncRequestLoggerConfiguration( - logLevel: - .all, - logger: testLogger - ) + requestLevel: .all + ) { message in + // We want to see a request to the specified instance + if message.contains(fakeUrl) { + expectation.fulfill() + } + } ) ) ) From 1d5916883c2a2afb7229f905eacb641198a7e5b0 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Thu, 7 Aug 2025 16:10:24 +0200 Subject: [PATCH 6/9] update Kotlin core --- CHANGELOG.md | 3 ++- Package.swift | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f34a2d3..3a4b565 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 1.4.0 +## 1.4.0 (unreleased) * Added the ability to log PowerSync sync network requests. @@ -18,6 +18,7 @@ try await database.connect( ) ) ) + ``` ## 1.3.1 diff --git a/Package.swift b/Package.swift index 9596dc4..e07deee 100644 --- a/Package.swift +++ b/Package.swift @@ -31,8 +31,8 @@ if let kotlinSdkPath = localKotlinSdkOverride { // Not using a local build, so download from releases conditionalTargets.append(.binaryTarget( name: "PowerSyncKotlin", - url: "https://github.com/powersync-ja/powersync-kotlin/releases/download/v1.3.1/PowersyncKotlinRelease.zip", - checksum: "b01b72cbf88a2e7b9b67efce966799493fc48d4523b5989d8c645ed182880975" + url: "https://github.com/powersync-ja/powersync-kotlin/releases/download/v1.4.0/PowersyncKotlinRelease.zip", + checksum: "e800db216fc1c9722e66873deb4f925530267db6dbd5e2114dd845cc62c28cd9" )) } From 12b87b5674e9a070e9c3ae03da725cf0d7c2c987 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Thu, 7 Aug 2025 16:18:45 +0200 Subject: [PATCH 7/9] fix typos --- Sources/PowerSync/Protocol/SyncRequestLogger.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/PowerSync/Protocol/SyncRequestLogger.swift b/Sources/PowerSync/Protocol/SyncRequestLogger.swift index 40c2284..46b31ad 100644 --- a/Sources/PowerSync/Protocol/SyncRequestLogger.swift +++ b/Sources/PowerSync/Protocol/SyncRequestLogger.swift @@ -22,7 +22,7 @@ public enum SyncRequestLogLevel { /// the PowerSync session. The `requestLevel` determines which network events /// are logged. /// -/// - Note: The request levell cannot be changed after initialization. A new call to `PowerSyncDatabase.connect` is required to change the level. +/// - Note: The request level cannot be changed after initialization. A new call to `PowerSyncDatabase.connect` is required to change the level. public struct SyncRequestLoggerConfiguration { /// The request logging level that determines which network events are logged. /// Set once during initialization and used throughout the session. @@ -33,7 +33,7 @@ public struct SyncRequestLoggerConfiguration { /// Creates a new network logger configuration. /// - Parameters: /// - requestLevel: The `SyncRequestLogLevel` to use for filtering log messages - /// - logHandler: A closure which handles log messages + /// - logHandler: A closure which handles log messages public init( requestLevel: SyncRequestLogLevel, logHandler: @escaping (_ message: String) -> Void) From f0186627143fd11b26145386435dd88787503047 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Thu, 7 Aug 2025 16:19:48 +0200 Subject: [PATCH 8/9] Add disconnectAndClear to test --- Tests/PowerSyncTests/ConnectTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/PowerSyncTests/ConnectTests.swift b/Tests/PowerSyncTests/ConnectTests.swift index 44a2f50..3c80cb4 100644 --- a/Tests/PowerSyncTests/ConnectTests.swift +++ b/Tests/PowerSyncTests/ConnectTests.swift @@ -128,5 +128,7 @@ final class ConnectTests: XCTestCase { ) await fulfillment(of: [expectation], timeout: 5) + + try await database.disconnectAndClear() } } From 846cb9dad6b021425adee91753cd546a7ec1e845 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Thu, 7 Aug 2025 16:34:21 +0200 Subject: [PATCH 9/9] stop AI from falsely complaining --- Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift b/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift index 37a26c7..0edde56 100644 --- a/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift +++ b/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift @@ -215,7 +215,7 @@ public protocol PowerSyncDatabaseProtocol: Queries { /// The database can still be queried after this is called, but the tables /// would be empty. /// - /// - Parameter clearLocal: Set to false to preserve data in local-only tables. + /// - Parameter clearLocal: Set to false to preserve data in local-only tables. Defaults to `true`. func disconnectAndClear(clearLocal: Bool) async throws /// Close the database, releasing resources. @@ -265,8 +265,8 @@ public extension PowerSyncDatabaseProtocol { ) } - func disconnectAndClear(clearLocal: Bool = true) async throws { - try await disconnectAndClear(clearLocal: clearLocal) + func disconnectAndClear() async throws { + try await disconnectAndClear(clearLocal: true) } func getCrudBatch(limit: Int32 = 100) async throws -> CrudBatch? {