Skip to content

Commit dc2586b

Browse files
add logging
1 parent 3209c98 commit dc2586b

File tree

9 files changed

+376
-5
lines changed

9 files changed

+376
-5
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
# Changelog
22

3+
# 1.0.0-Beta.10 (unreleased)
4+
5+
* Added the ability to specify a custom logging implementation
6+
```swift
7+
let db = PowerSyncDatabase(
8+
schema: Schema(
9+
tables: [
10+
Table(
11+
name: "users",
12+
columns: [
13+
.text("name"),
14+
.text("email")
15+
]
16+
)
17+
]
18+
),
19+
logger: DefaultLogger(minSeverity: .debug)
20+
)
21+
```
22+
* added `.close()` method on `PowerSyncDatabaseProtocol`
23+
324
## 1.0.0-Beta.9
425

526
* Update PowerSync SQLite core extension to 0.3.12.
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import PowerSyncKotlin
2+
3+
/// Maps a Kermit `Severity` level to a local `LogSeverity`.
4+
///
5+
/// - Parameter severity: The Kermit log severity value from Kotlin.
6+
/// - Returns: The corresponding `LogSeverity` used in Swift.
7+
private func mapKermitSeverity(_ severity: PowerSyncKotlin.Kermit_coreSeverity) -> LogSeverity {
8+
switch severity {
9+
case PowerSyncKotlin.Kermit_coreSeverity.verbose:
10+
return LogSeverity.debug
11+
case PowerSyncKotlin.Kermit_coreSeverity.debug:
12+
return LogSeverity.debug
13+
case PowerSyncKotlin.Kermit_coreSeverity.info:
14+
return LogSeverity.info
15+
case PowerSyncKotlin.Kermit_coreSeverity.warn:
16+
return LogSeverity.warning
17+
case PowerSyncKotlin.Kermit_coreSeverity.error:
18+
return LogSeverity.error
19+
case PowerSyncKotlin.Kermit_coreSeverity.assert:
20+
return LogSeverity.fault
21+
}
22+
}
23+
24+
/// Maps a local `LogSeverity` to a Kermit-compatible `Kermit_coreSeverity`.
25+
///
26+
/// - Parameter severity: The Swift-side `LogSeverity`.
27+
/// - Returns: The equivalent Kermit log severity.
28+
private func mapSeverity(_ severity: LogSeverity) -> PowerSyncKotlin.Kermit_coreSeverity {
29+
switch severity {
30+
case .debug:
31+
return PowerSyncKotlin.Kermit_coreSeverity.debug
32+
case .info:
33+
return PowerSyncKotlin.Kermit_coreSeverity.info
34+
case .warning:
35+
return PowerSyncKotlin.Kermit_coreSeverity.warn
36+
case .error:
37+
return PowerSyncKotlin.Kermit_coreSeverity.error
38+
case .fault:
39+
return PowerSyncKotlin.Kermit_coreSeverity.assert
40+
}
41+
}
42+
43+
/// Adapts a Swift `LogWritterProtocol` to Kermit's `LogWriter` interface.
44+
///
45+
/// This allows Kotlin logging (via Kermit) to call into the Swift logging implementation.
46+
private class KermitLogWriterAdapter: Kermit_coreLogWriter {
47+
/// The underlying Swift log writer to forward log messages to.
48+
var adapter: LogWriterProtocol
49+
50+
/// Initializes a new adapter.
51+
///
52+
/// - Parameter adapter: A Swift log writer that will handle log output.
53+
init(adapter: LogWriterProtocol) {
54+
self.adapter = adapter
55+
super.init()
56+
}
57+
58+
/// Called by Kermit to log a message.
59+
///
60+
/// - Parameters:
61+
/// - severity: The severity level of the log.
62+
/// - message: The content of the log message.
63+
/// - tag: An optional string categorizing the log.
64+
/// - throwable: An optional Kotlin exception (ignored here).
65+
override func log(severity: Kermit_coreSeverity, message: String, tag: String, throwable: KotlinThrowable?) {
66+
adapter.log(
67+
severity: mapKermitSeverity(severity),
68+
message: message,
69+
tag: tag
70+
)
71+
}
72+
}
73+
74+
/// A logger implementation that integrates with PowerSync's Kotlin backend using Kermit.
75+
///
76+
/// This class bridges Swift log writers with the Kotlin logging system and supports
77+
/// runtime configuration of severity levels and writer lists.
78+
public class DatabaseLogger: LoggerProtocol {
79+
/// The underlying Kermit logger instance provided by the PowerSyncKotlin SDK.
80+
internal let kLogger = PowerSyncKotlin.generateLogger(logger: nil)
81+
82+
/// Initializes a new logger with an optional list of writers.
83+
///
84+
/// - Parameter writers: An array of Swift log writers. Defaults to an empty array.
85+
init(writers: [any LogWriterProtocol] = []) {
86+
setWriters(writers)
87+
}
88+
89+
/// Sets the minimum severity level that will be logged.
90+
///
91+
/// Messages below this level will be ignored.
92+
///
93+
/// - Parameter severity: The minimum `LogSeverity` to allow through.
94+
public func setMinSeverity(_ severity: LogSeverity) {
95+
kLogger.mutableConfig.setMinSeverity(
96+
mapSeverity(severity)
97+
)
98+
}
99+
100+
/// Sets the list of log writers that will receive log messages.
101+
///
102+
/// This updates both the internal writer list and the Kermit logger's configuration.
103+
///
104+
/// - Parameter writers: An array of Swift `LogWritterProtocol` implementations.
105+
public func setWriters(_ writers: [any LogWriterProtocol]) {
106+
kLogger.mutableConfig.setLogWriterList(
107+
writers.map { item in KermitLogWriterAdapter(adapter: item) }
108+
)
109+
}
110+
111+
/// Logs a debug-level message.
112+
public func debug(_ message: String, tag: String) {
113+
kLogger.d(messageString: message, throwable: nil, tag: tag)
114+
}
115+
116+
/// Logs an info-level message.
117+
public func info(_ message: String, tag: String) {
118+
kLogger.i(messageString: message, throwable: nil, tag: tag)
119+
}
120+
121+
/// Logs a warning-level message.
122+
public func warning(_ message: String, tag: String) {
123+
kLogger.w(messageString: message, throwable: nil, tag: tag)
124+
}
125+
126+
/// Logs an error-level message.
127+
public func error(_ message: String, tag: String) {
128+
kLogger.e(messageString: message, throwable: nil, tag: tag)
129+
}
130+
131+
/// Logs a fault (assert-level) message, typically used for critical issues.
132+
public func fault(_ message: String, tag: String) {
133+
kLogger.a(messageString: message, throwable: nil, tag: tag)
134+
}
135+
}

Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol {
88

99
init(
1010
schema: Schema,
11-
dbFilename: String
11+
dbFilename: String,
12+
logger: DatabaseLogger? = nil
1213
) {
1314
let factory = PowerSyncKotlin.DatabaseDriverFactory()
1415
kotlinDatabase = PowerSyncDatabase(
1516
factory: factory,
1617
schema: KotlinAdapter.Schema.toKotlin(schema),
17-
dbFilename: dbFilename
18+
dbFilename: dbFilename,
19+
logger: logger?.kLogger
1820
)
1921
}
2022

@@ -232,4 +234,8 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol {
232234
func readTransaction<R>(callback: @escaping (any PowerSyncTransaction) throws -> R) async throws -> R {
233235
return try safeCast(await kotlinDatabase.readTransaction(callback: TransactionCallback(callback: callback)), to: R.self)
234236
}
237+
238+
func close() async throws{
239+
try await kotlinDatabase.close()
240+
}
235241
}

Sources/PowerSync/Logger.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import OSLog
2+
3+
/// A log writer that bridges custom `LogSeverity` levels to Apple's unified `Logger` framework.
4+
///
5+
/// This writer uses `os.Logger` on iOS 14+ and falls back to `print` for earlier versions.
6+
/// Tags are optionally prefixed to messages in square brackets.
7+
public class SwiftLogWriter: LogWriterProtocol {
8+
9+
/// Logs a message with a given severity and optional tag.
10+
///
11+
/// - Parameters:
12+
/// - severity: The severity level of the message.
13+
/// - message: The content of the log message.
14+
/// - tag: An optional tag used to categorize the message. If empty, no brackets are shown.
15+
public func log(severity: LogSeverity, message: String, tag: String) {
16+
let tagPrefix = tag.isEmpty ? "" : "[\(tag)] "
17+
let message = "\(tagPrefix) \(message)"
18+
if #available(iOS 14.0, *) {
19+
let l = Logger()
20+
21+
switch severity {
22+
case .info:
23+
l.info("\(message)")
24+
case .error:
25+
l.error("\(message)")
26+
case .debug:
27+
l.debug("\(message)")
28+
case .warning:
29+
l.warning("\(message)")
30+
case .fault:
31+
l.fault("\(message)")
32+
}
33+
} else {
34+
print("\(severity.rawValue): \(message)")
35+
}
36+
}
37+
}
38+
39+
/// A default logger configuration that uses `SwiftLogWritter` and filters messages by minimum severity.
40+
///
41+
/// This logger integrates with your custom logging system and uses `os.Logger` under the hood.
42+
public class DefaultLogger: DatabaseLogger {
43+
44+
/// Initializes the default logger with an optional minimum severity level.
45+
///
46+
/// - Parameter minSeverity: The minimum severity level to log. Defaults to `.debug`.
47+
public init(minSeverity: LogSeverity = .debug) {
48+
super.init()
49+
setMinSeverity(minSeverity)
50+
setWriters([SwiftLogWriter()])
51+
}
52+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/// Represents the severity level of a log message.
2+
public enum LogSeverity: String, CaseIterable {
3+
/// Informational messages that highlight the progress of the application.
4+
case info = "INFO"
5+
6+
/// Error events that might still allow the application to continue running.
7+
case error = "ERROR"
8+
9+
/// Detailed information typically used for debugging.
10+
case debug = "DEBUG"
11+
12+
/// Potentially harmful situations that are not necessarily errors.
13+
case warning = "WARNING"
14+
15+
/// Serious errors indicating critical failures, often unrecoverable.
16+
case fault = "FAULT"
17+
}
18+
19+
/// A protocol for writing log messages to a specific backend or output.
20+
///
21+
/// Conformers handle the actual writing or forwarding of log messages.
22+
public protocol LogWriterProtocol {
23+
/// Logs a message with the given severity and optional tag.
24+
///
25+
/// - Parameters:
26+
/// - severity: The severity level of the log message.
27+
/// - message: The content of the log message.
28+
/// - tag: An optional tag to categorize or group the log message.
29+
func log(severity: LogSeverity, message: String, tag: String)
30+
}
31+
32+
/// A protocol defining the interface for a logger that supports severity filtering and multiple writers.
33+
///
34+
/// Conformers provide logging APIs and manage attached log writers.
35+
public protocol LoggerProtocol {
36+
/// Sets the minimum severity level to be logged.
37+
///
38+
/// Log messages below this severity will be ignored.
39+
///
40+
/// - Parameter severity: The minimum severity level to log.
41+
func setMinSeverity(_ severity: LogSeverity)
42+
43+
/// Sets the list of log writers that will handle log output.
44+
///
45+
/// - Parameter writters: An array of `LogWritterProtocol` conformers.
46+
func setWriters(_ writters: [LogWriterProtocol])
47+
48+
/// Logs an informational message.
49+
///
50+
/// - Parameters:
51+
/// - message: The content of the log message.
52+
/// - tag: An optional tag to categorize the message.
53+
func info(_ message: String, tag: String)
54+
55+
/// Logs an error message.
56+
///
57+
/// - Parameters:
58+
/// - message: The content of the log message.
59+
/// - tag: An optional tag to categorize the message.
60+
func error(_ message: String, tag: String)
61+
62+
/// Logs a debug message.
63+
///
64+
/// - Parameters:
65+
/// - message: The content of the log message.
66+
/// - tag: An optional tag to categorize the message.
67+
func debug(_ message: String, tag: String)
68+
69+
/// Logs a warning message.
70+
///
71+
/// - Parameters:
72+
/// - message: The content of the log message.
73+
/// - tag: An optional tag to categorize the message.
74+
func warning(_ message: String, tag: String)
75+
76+
/// Logs a fault message, typically used for critical system-level failures.
77+
///
78+
/// - Parameters:
79+
/// - message: The content of the log message.
80+
/// - tag: An optional tag to categorize the message.
81+
func fault(_ message: String, tag: String)
82+
}

Sources/PowerSync/PowerSyncDatabase.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@ public let DEFAULT_DB_FILENAME = "powersync.db"
77
/// - Parameters:
88
/// - schema: The database schema
99
/// - dbFilename: The database filename. Defaults to "powersync.db"
10+
/// - logger: Optional logging interface
1011
/// - Returns: A configured PowerSyncDatabase instance
1112
public func PowerSyncDatabase(
1213
schema: Schema,
13-
dbFilename: String = DEFAULT_DB_FILENAME
14+
dbFilename: String = DEFAULT_DB_FILENAME,
15+
logger: DatabaseLogger = DefaultLogger()
1416
) -> PowerSyncDatabaseProtocol {
1517

1618
return KotlinPowerSyncDatabaseImpl(
1719
schema: schema,
18-
dbFilename: dbFilename
20+
dbFilename: dbFilename,
21+
logger: logger
1922
)
2023
}

Sources/PowerSync/PowerSyncDatabaseProtocol.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ public protocol PowerSyncDatabaseProtocol: Queries {
100100
///
101101
/// - Parameter clearLocal: Set to false to preserve data in local-only tables.
102102
func disconnectAndClear(clearLocal: Bool) async throws
103+
104+
/// Close the database, releasing resources.
105+
/// Also disconnects any active connection.
106+
///
107+
/// Once close is called, this database cannot be used again - a new one must be constructed.
108+
func close() async throws
103109
}
104110

105111
public extension PowerSyncDatabaseProtocol {

0 commit comments

Comments
 (0)