From 01dd25aa18c4b2327cac11bab5dd169587c010d4 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Fri, 17 Nov 2023 16:49:36 -0300 Subject: [PATCH 1/8] Add logger type --- Sources/Auth/AuthClient.swift | 10 ++- Sources/Auth/Internal/Dependencies.swift | 2 + Sources/_Helpers/SupabaseLogger.swift | 76 +++++++++++++++++++ .../xcshareddata/swiftpm/Package.resolved | 8 +- 4 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 Sources/_Helpers/SupabaseLogger.swift diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index 0e351cfc..44b55e87 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -75,6 +75,10 @@ public actor AuthClient { Dependencies.current.value!.currentDate } + private var logger: SupabaseLogger { + Dependencies.current.value!.logger + } + /// Returns the session, refreshing it if necessary. /// /// If no session can be found, a ``AuthError/sessionNotFound`` error is thrown. @@ -159,7 +163,8 @@ public actor AuthClient { try await self?.refreshSession(refreshToken: $0) ?? .empty } ), - codeVerifierStorage: codeVerifierStorage + codeVerifierStorage: codeVerifierStorage, + logger: SupabaseLogger(system: "GoTrue") ) ) } @@ -172,9 +177,11 @@ public actor AuthClient { session: Session? )> { let (id, stream) = eventEmitter.attachListener() + logger.log(.debug, message: "auth state change listener with id '\(id.uuidString)' attached.") Task { [id] in await emitInitialSession(forStreamWithID: id) + logger.log(.debug, message: "initial session for listener with id '\(id.uuidString)' emitted.") } return stream @@ -866,6 +873,7 @@ public actor AuthClient { private func prepareForPKCE() -> (codeChallenge: String?, codeChallengeMethod: String?) { if configuration.flowType == .pkce { + let codeVerifier = PKCE.generateCodeVerifier() do { diff --git a/Sources/Auth/Internal/Dependencies.swift b/Sources/Auth/Internal/Dependencies.swift index 33e042ca..04504ee6 100644 --- a/Sources/Auth/Internal/Dependencies.swift +++ b/Sources/Auth/Internal/Dependencies.swift @@ -1,5 +1,6 @@ import ConcurrencyExtras import Foundation +import _Helpers struct Dependencies: Sendable { static let current = LockIsolated(Dependencies?.none) @@ -12,4 +13,5 @@ struct Dependencies: Sendable { var sessionRefresher: SessionRefresher var codeVerifierStorage: CodeVerifierStorage var currentDate: @Sendable () -> Date = { Date() } + var logger: SupabaseLogger } diff --git a/Sources/_Helpers/SupabaseLogger.swift b/Sources/_Helpers/SupabaseLogger.swift new file mode 100644 index 00000000..393a7400 --- /dev/null +++ b/Sources/_Helpers/SupabaseLogger.swift @@ -0,0 +1,76 @@ +import Foundation + +public struct SupabaseLogger: Sendable { + public enum Level: Int, Codable, CustomStringConvertible, Sendable { + case debug + case warning + case error + + public var description: String { + switch self { + case .debug: + "debug" + case .warning: + "warning" + case .error: + "error" + } + } + } + + struct Message: Codable, CustomStringConvertible { + let system: String + let level: Level + let message: String + let fileID: String + let function: String + let line: UInt + let timestamp: TimeInterval + + var description: String { + let date = iso8601Formatter.string(from: Date(timeIntervalSince1970: timestamp)) + let file = self.fileID.split(separator: ".", maxSplits: 1).first.map(String.init) ?? fileID + return "\(date) [\(level)] [\(system)] [\(file).\(function):\(line)] \(message)" + } + } + + let system: String + let minLevel: Level + + public init(system: String, minLevel: Level = .debug) { + self.system = system + self.minLevel = minLevel + } + + public func log( + _ level: Level, + message: @autoclosure () -> String, + fileID: StaticString = #fileID, + function: StaticString = #function, + line: UInt = #line + ) { + guard shouldLog(level) else { return } + + let message = Message( + system: system, + level: level, + message: message(), + fileID: "\(fileID)", + function: "\(function)", + line: line, + timestamp: Date().timeIntervalSince1970 + ) + + print(message) + } + + private func shouldLog(_ level: Level) -> Bool { + level.rawValue >= minLevel.rawValue + } +} + +private let iso8601Formatter: ISO8601DateFormatter = { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime] + return formatter +}() diff --git a/supabase-swift.xcworkspace/xcshareddata/swiftpm/Package.resolved b/supabase-swift.xcworkspace/xcshareddata/swiftpm/Package.resolved index 987897dc..4d19c818 100644 --- a/supabase-swift.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/supabase-swift.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-snapshot-testing", "state" : { - "revision" : "59b663f68e69f27a87b45de48cb63264b8194605", - "version" : "1.15.1" + "revision" : "8e68404f641300bfd0e37d478683bb275926760c", + "version" : "1.15.2" } }, { @@ -86,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-syntax.git", "state" : { - "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036", - "version" : "509.0.2" + "revision" : "43c802fb7f96e090dde015344a94b5e85779eff1", + "version" : "509.1.0" } }, { From da6dcdd0a7898b5dd7f13e931af48644b0dcd449 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Mon, 15 Jan 2024 08:30:34 -0300 Subject: [PATCH 2/8] Add SupabaseLogger --- Sources/Auth/AuthClient.swift | 19 +++-- Sources/Auth/Internal/Dependencies.swift | 2 +- Sources/PostgREST/PostgrestBuilder.swift | 2 +- Sources/PostgREST/PostgrestClient.swift | 7 ++ Sources/Supabase/SupabaseClient.swift | 2 + Sources/Supabase/Types.swift | 11 ++- Sources/_Helpers/Logger.swift | 36 --------- Sources/_Helpers/SupabaseLogger.swift | 76 ------------------- Sources/_Helpers/SupabaseLogger/Entry.swift | 32 ++++++++ Sources/_Helpers/SupabaseLogger/Level.swift | 27 +++++++ .../SupabaseLogger/SupabaseLogHandler.swift | 45 +++++++++++ .../SupabaseLogger/SupabaseLogger.swift | 66 ++++++++++++++++ 12 files changed, 204 insertions(+), 121 deletions(-) delete mode 100644 Sources/_Helpers/Logger.swift delete mode 100644 Sources/_Helpers/SupabaseLogger.swift create mode 100644 Sources/_Helpers/SupabaseLogger/Entry.swift create mode 100644 Sources/_Helpers/SupabaseLogger/Level.swift create mode 100644 Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift create mode 100644 Sources/_Helpers/SupabaseLogger/SupabaseLogger.swift diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index 44b55e87..8ffe3d91 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -16,6 +16,7 @@ public actor AuthClient { public var headers: [String: String] public let flowType: AuthFlowType public let localStorage: AuthLocalStorage + public let logHandler: SupabaseLogHandler public let encoder: JSONEncoder public let decoder: JSONDecoder public let fetch: FetchHandler @@ -27,6 +28,7 @@ public actor AuthClient { /// - headers: Custom headers to be included in requests. /// - flowType: The authentication flow type. /// - localStorage: The storage mechanism for local data. + /// - logHandler: The LogHandler to use. /// - encoder: The JSON encoder to use for encoding requests. /// - decoder: The JSON decoder to use for decoding responses. /// - fetch: The asynchronous fetch handler for network requests. @@ -35,6 +37,7 @@ public actor AuthClient { headers: [String: String] = [:], flowType: AuthFlowType = Configuration.defaultFlowType, localStorage: AuthLocalStorage, + logHandler: SupabaseLogHandler = DefaultSupabaseLogHandler.shared, encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder, decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder, fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) } @@ -45,6 +48,7 @@ public actor AuthClient { self.headers = headers self.flowType = flowType self.localStorage = localStorage + self.logHandler = logHandler self.encoder = encoder self.decoder = decoder self.fetch = fetch @@ -106,6 +110,7 @@ public actor AuthClient { headers: [String: String] = [:], flowType: AuthFlowType = AuthClient.Configuration.defaultFlowType, localStorage: AuthLocalStorage, + logHandler: SupabaseLogHandler = DefaultSupabaseLogHandler.shared, encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder, decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder, fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) } @@ -116,6 +121,7 @@ public actor AuthClient { headers: headers, flowType: flowType, localStorage: localStorage, + logHandler: logHandler, encoder: encoder, decoder: decoder, fetch: fetch @@ -136,7 +142,8 @@ public actor AuthClient { codeVerifierStorage: .live, api: api, eventEmitter: .live, - sessionStorage: .live + sessionStorage: .live, + logger: SupabaseLogger(system: "AuthClient", handler: configuration.logHandler) ) } @@ -147,7 +154,8 @@ public actor AuthClient { codeVerifierStorage: CodeVerifierStorage, api: APIClient, eventEmitter: EventEmitter, - sessionStorage: SessionStorage + sessionStorage: SessionStorage, + logger: SupabaseLogger ) { mfa = AuthMFA() @@ -164,7 +172,7 @@ public actor AuthClient { } ), codeVerifierStorage: codeVerifierStorage, - logger: SupabaseLogger(system: "GoTrue") + logger: logger ) ) } @@ -177,11 +185,11 @@ public actor AuthClient { session: Session? )> { let (id, stream) = eventEmitter.attachListener() - logger.log(.debug, message: "auth state change listener with id '\(id.uuidString)' attached.") + logger.debug("auth state change listener with id '\(id.uuidString)' attached.") Task { [id] in await emitInitialSession(forStreamWithID: id) - logger.log(.debug, message: "initial session for listener with id '\(id.uuidString)' emitted.") + logger.debug("initial session for listener with id '\(id.uuidString)' emitted.") } return stream @@ -873,7 +881,6 @@ public actor AuthClient { private func prepareForPKCE() -> (codeChallenge: String?, codeChallengeMethod: String?) { if configuration.flowType == .pkce { - let codeVerifier = PKCE.generateCodeVerifier() do { diff --git a/Sources/Auth/Internal/Dependencies.swift b/Sources/Auth/Internal/Dependencies.swift index 04504ee6..57c7c66a 100644 --- a/Sources/Auth/Internal/Dependencies.swift +++ b/Sources/Auth/Internal/Dependencies.swift @@ -1,6 +1,6 @@ +import _Helpers import ConcurrencyExtras import Foundation -import _Helpers struct Dependencies: Sendable { static let current = LockIsolated(Dependencies?.none) diff --git a/Sources/PostgREST/PostgrestBuilder.swift b/Sources/PostgREST/PostgrestBuilder.swift index 539eba35..b09ee1b9 100644 --- a/Sources/PostgREST/PostgrestBuilder.swift +++ b/Sources/PostgREST/PostgrestBuilder.swift @@ -74,7 +74,7 @@ public class PostgrestBuilder: @unchecked Sendable { do { return try configuration.decoder.decode(T.self, from: data) } catch { - debug("Fail to decode type '\(T.self) with error: \(error)") + configuration.logger.error("Fail to decode type '\(T.self) with error: \(error)") throw error } } diff --git a/Sources/PostgREST/PostgrestClient.swift b/Sources/PostgREST/PostgrestClient.swift index 2d317130..b3ba4f8a 100644 --- a/Sources/PostgREST/PostgrestClient.swift +++ b/Sources/PostgREST/PostgrestClient.swift @@ -20,6 +20,8 @@ public actor PostgrestClient { public var encoder: JSONEncoder public var decoder: JSONDecoder + let logger: SupabaseLogger + /// Initializes a new configuration for the PostgREST client. /// - Parameters: /// - url: The URL of the PostgREST server. @@ -32,6 +34,7 @@ public actor PostgrestClient { url: URL, schema: String? = nil, headers: [String: String] = [:], + logHandler: SupabaseLogHandler = DefaultSupabaseLogHandler.shared, fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }, encoder: JSONEncoder = PostgrestClient.Configuration.jsonEncoder, decoder: JSONDecoder = PostgrestClient.Configuration.jsonDecoder @@ -42,6 +45,8 @@ public actor PostgrestClient { self.fetch = fetch self.encoder = encoder self.decoder = decoder + + logger = SupabaseLogger(system: "Postgrest", handler: logHandler) } } @@ -67,6 +72,7 @@ public actor PostgrestClient { url: URL, schema: String? = nil, headers: [String: String] = [:], + logHandler: SupabaseLogHandler = DefaultSupabaseLogHandler.shared, fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }, encoder: JSONEncoder = PostgrestClient.Configuration.jsonEncoder, decoder: JSONDecoder = PostgrestClient.Configuration.jsonDecoder @@ -76,6 +82,7 @@ public actor PostgrestClient { url: url, schema: schema, headers: headers, + logHandler: logHandler, fetch: fetch, encoder: encoder, decoder: decoder diff --git a/Sources/Supabase/SupabaseClient.swift b/Sources/Supabase/SupabaseClient.swift index 03f9b758..ed6a579d 100644 --- a/Sources/Supabase/SupabaseClient.swift +++ b/Sources/Supabase/SupabaseClient.swift @@ -31,6 +31,7 @@ public final class SupabaseClient: @unchecked Sendable { url: databaseURL, schema: options.db.schema, headers: defaultHeaders, + logHandler: options.global.logHandler, fetch: fetchWithAuth, encoder: options.db.encoder, decoder: options.db.decoder @@ -93,6 +94,7 @@ public final class SupabaseClient: @unchecked Sendable { headers: defaultHeaders, flowType: options.auth.flowType, localStorage: options.auth.storage, + logHandler: options.global.logHandler, encoder: options.auth.encoder, decoder: options.auth.decoder, fetch: { diff --git a/Sources/Supabase/Types.swift b/Sources/Supabase/Types.swift index 3da0cd8f..abe27ddb 100644 --- a/Sources/Supabase/Types.swift +++ b/Sources/Supabase/Types.swift @@ -1,3 +1,4 @@ +import _Helpers import Auth import Foundation import PostgREST @@ -67,9 +68,17 @@ public struct SupabaseClientOptions: Sendable { /// A session to use for making requests, defaults to `URLSession.shared`. public let session: URLSession - public init(headers: [String: String] = [:], session: URLSession = .shared) { + /// The log handler instance to use across all Supabase sub-packages. + public let logHandler: SupabaseLogHandler + + public init( + headers: [String: String] = [:], + session: URLSession = .shared, + logHandler: SupabaseLogHandler = DefaultSupabaseLogHandler.shared + ) { self.headers = headers self.session = session + self.logHandler = logHandler } } diff --git a/Sources/_Helpers/Logger.swift b/Sources/_Helpers/Logger.swift deleted file mode 100644 index 36a4a186..00000000 --- a/Sources/_Helpers/Logger.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// Logger.swift -// -// -// Created by Guilherme Souza on 11/12/23. -// - -import ConcurrencyExtras -import Foundation - -private let _debugLoggingEnabled = LockIsolated(false) -@_spi(Internal) public var debugLoggingEnabled: Bool { - get { _debugLoggingEnabled.value } - set { _debugLoggingEnabled.setValue(newValue) } -} - -private let standardError = LockIsolated(FileHandle.standardError) -@_spi(Internal) public func debug( - _ message: @autoclosure () -> String, - function: String = #function, - file: String = #file, - line: UInt = #line -) { - assert( - { - if debugLoggingEnabled { - standardError.withValue { - let logLine = "[\(function) \(file.split(separator: "/").last!):\(line)] \(message())\n" - $0.write(Data(logLine.utf8)) - } - } - - return true - }() - ) -} diff --git a/Sources/_Helpers/SupabaseLogger.swift b/Sources/_Helpers/SupabaseLogger.swift deleted file mode 100644 index 393a7400..00000000 --- a/Sources/_Helpers/SupabaseLogger.swift +++ /dev/null @@ -1,76 +0,0 @@ -import Foundation - -public struct SupabaseLogger: Sendable { - public enum Level: Int, Codable, CustomStringConvertible, Sendable { - case debug - case warning - case error - - public var description: String { - switch self { - case .debug: - "debug" - case .warning: - "warning" - case .error: - "error" - } - } - } - - struct Message: Codable, CustomStringConvertible { - let system: String - let level: Level - let message: String - let fileID: String - let function: String - let line: UInt - let timestamp: TimeInterval - - var description: String { - let date = iso8601Formatter.string(from: Date(timeIntervalSince1970: timestamp)) - let file = self.fileID.split(separator: ".", maxSplits: 1).first.map(String.init) ?? fileID - return "\(date) [\(level)] [\(system)] [\(file).\(function):\(line)] \(message)" - } - } - - let system: String - let minLevel: Level - - public init(system: String, minLevel: Level = .debug) { - self.system = system - self.minLevel = minLevel - } - - public func log( - _ level: Level, - message: @autoclosure () -> String, - fileID: StaticString = #fileID, - function: StaticString = #function, - line: UInt = #line - ) { - guard shouldLog(level) else { return } - - let message = Message( - system: system, - level: level, - message: message(), - fileID: "\(fileID)", - function: "\(function)", - line: line, - timestamp: Date().timeIntervalSince1970 - ) - - print(message) - } - - private func shouldLog(_ level: Level) -> Bool { - level.rawValue >= minLevel.rawValue - } -} - -private let iso8601Formatter: ISO8601DateFormatter = { - let formatter = ISO8601DateFormatter() - formatter.formatOptions = [.withInternetDateTime] - return formatter -}() diff --git a/Sources/_Helpers/SupabaseLogger/Entry.swift b/Sources/_Helpers/SupabaseLogger/Entry.swift new file mode 100644 index 00000000..bf6c6156 --- /dev/null +++ b/Sources/_Helpers/SupabaseLogger/Entry.swift @@ -0,0 +1,32 @@ +// +// Entry.swift +// +// +// Created by Guilherme Souza on 15/01/24. +// + +import Foundation + +extension SupabaseLogger { + public struct Entry: Codable, CustomStringConvertible { + public let system: String + public let level: Level + public let message: String + public let fileID: String + public let function: String + public let line: UInt + public let timestamp: TimeInterval + + public var description: String { + let date = iso8601Formatter.string(from: Date(timeIntervalSince1970: timestamp)) + let file = fileID.split(separator: ".", maxSplits: 1).first.map(String.init) ?? fileID + return "\(date) [\(level)] [\(system)] [\(file).\(function):\(line)] \(message)" + } + } +} + +private let iso8601Formatter: ISO8601DateFormatter = { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime] + return formatter +}() diff --git a/Sources/_Helpers/SupabaseLogger/Level.swift b/Sources/_Helpers/SupabaseLogger/Level.swift new file mode 100644 index 00000000..2a77cac8 --- /dev/null +++ b/Sources/_Helpers/SupabaseLogger/Level.swift @@ -0,0 +1,27 @@ +// +// Level.swift +// +// +// Created by Guilherme Souza on 15/01/24. +// + +import Foundation + +extension SupabaseLogger { + public enum Level: Int, Codable, CustomStringConvertible, Sendable { + case debug + case warning + case error + + public var description: String { + switch self { + case .debug: + "debug" + case .warning: + "warning" + case .error: + "error" + } + } + } +} diff --git a/Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift b/Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift new file mode 100644 index 00000000..5eadbaae --- /dev/null +++ b/Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift @@ -0,0 +1,45 @@ +// +// SupabaseLogHandler.swift +// +// +// Created by Guilherme Souza on 15/01/24. +// + +import Foundation + +public protocol SupabaseLogHandler: Sendable { + func didLog(_ entry: SupabaseLogger.Entry) +} + +public final class DefaultSupabaseLogHandler: SupabaseLogHandler { + /// The default log handler instance used across all Supabase sub-packages. + public static let shared: SupabaseLogHandler = try! DefaultSupabaseLogHandler( + localFile: FileManager.default.url( + for: .applicationSupportDirectory, + in: .userDomainMask, + appropriateFor: nil, + create: true + ).appendingPathComponent("supabase-swift.log") + ) + + let fileHandle: FileHandle + + public init(localFile url: URL) throws { + if !FileManager.default.fileExists(atPath: url.path) { + FileManager.default.createFile(atPath: url.path, contents: nil) + } + + fileHandle = try FileHandle(forWritingTo: url) + fileHandle.seekToEndOfFile() + + debugPrint("SupabaseLogHandler initialized at: \(url)") + } + + public func didLog(_ entry: SupabaseLogger.Entry) { + let logLine = "\(entry.description)\n" + guard let data = logLine.data(using: .utf8) else { return } + fileHandle.write(data) + + debugPrint(entry.description) + } +} diff --git a/Sources/_Helpers/SupabaseLogger/SupabaseLogger.swift b/Sources/_Helpers/SupabaseLogger/SupabaseLogger.swift new file mode 100644 index 00000000..6f8fa85f --- /dev/null +++ b/Sources/_Helpers/SupabaseLogger/SupabaseLogger.swift @@ -0,0 +1,66 @@ +import Foundation + +public struct SupabaseLogger: Sendable { + let system: String + let minLevel: Level + let handler: SupabaseLogHandler + + public init(system: String, handler: SupabaseLogHandler, minLevel: Level = .debug) { + self.system = system + self.handler = handler + self.minLevel = minLevel + } + + public func log( + _ level: Level, + message: @autoclosure () -> String, + fileID: StaticString = #fileID, + function: StaticString = #function, + line: UInt = #line + ) { + guard shouldLog(level) else { return } + + let entry = Entry( + system: system, + level: level, + message: message(), + fileID: "\(fileID)", + function: "\(function)", + line: line, + timestamp: Date().timeIntervalSince1970 + ) + + handler.didLog(entry) + } + + private func shouldLog(_ level: Level) -> Bool { + level.rawValue >= minLevel.rawValue + } + + public func debug( + _ message: @autoclosure () -> String, + fileID: StaticString = #fileID, + function: StaticString = #function, + line: UInt = #line + ) { + log(.debug, message: message(), fileID: fileID, function: function, line: line) + } + + public func warning( + _ message: @autoclosure () -> String, + fileID: StaticString = #fileID, + function: StaticString = #function, + line: UInt = #line + ) { + log(.warning, message: message(), fileID: fileID, function: function, line: line) + } + + public func error( + _ message: @autoclosure () -> String, + fileID: StaticString = #fileID, + function: StaticString = #function, + line: UInt = #line + ) { + log(.error, message: message(), fileID: fileID, function: function, line: line) + } +} From bba9d99037249fa00be815a21e107450c395b08d Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Mon, 15 Jan 2024 08:47:34 -0300 Subject: [PATCH 3/8] Fix tests --- .../_Helpers/SupabaseLogger/SupabaseLogHandler.swift | 6 ++++++ Tests/AuthTests/GoTrueClientTests.swift | 11 +++++------ Tests/AuthTests/Mocks/Mocks.swift | 3 ++- Tests/AuthTests/RequestsTests.swift | 3 ++- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift b/Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift index 5eadbaae..cd053b8b 100644 --- a/Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift +++ b/Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift @@ -43,3 +43,9 @@ public final class DefaultSupabaseLogHandler: SupabaseLogHandler { debugPrint(entry.description) } } + +@_spi(Internal) +public struct NoopSupabaseLogHandler: SupabaseLogHandler { + public init() {} + public func didLog(_: SupabaseLogger.Entry) {} +} diff --git a/Tests/AuthTests/GoTrueClientTests.swift b/Tests/AuthTests/GoTrueClientTests.swift index ac2e1d63..4f5ccc4b 100644 --- a/Tests/AuthTests/GoTrueClientTests.swift +++ b/Tests/AuthTests/GoTrueClientTests.swift @@ -24,9 +24,7 @@ final class AuthClientTests: XCTestCase { let events = ActorIsolated([AuthChangeEvent]()) - // We use a semaphore here instead of the nicer XCTestExpectation as that isn't fully available - // on Linux. - let semaphore = DispatchSemaphore(value: 0) + let (stream, continuation) = AsyncStream.makeStream() await withDependencies { $0.eventEmitter = .live @@ -40,11 +38,11 @@ final class AuthClientTests: XCTestCase { $0.append(event) } - semaphore.signal() + continuation.yield() } } - _ = semaphore.wait(timeout: .now() + 2.0) + _ = await stream.first { _ in true } let events = await events.value XCTAssertEqual(events, [.initialSession]) @@ -168,7 +166,8 @@ final class AuthClientTests: XCTestCase { codeVerifierStorage: .mock, api: .mock, eventEmitter: .mock, - sessionStorage: .mock + sessionStorage: .mock, + logger: SupabaseLogger(system: "AuthClientTests", handler: NoopSupabaseLogHandler()) ) addTeardownBlock { [weak sut] in diff --git a/Tests/AuthTests/Mocks/Mocks.swift b/Tests/AuthTests/Mocks/Mocks.swift index eefa19b5..35bcd97b 100644 --- a/Tests/AuthTests/Mocks/Mocks.swift +++ b/Tests/AuthTests/Mocks/Mocks.swift @@ -114,7 +114,8 @@ extension Dependencies { eventEmitter: .mock, sessionStorage: .mock, sessionRefresher: .mock, - codeVerifierStorage: .mock + codeVerifierStorage: .mock, + logger: SupabaseLogger(system: "AuthClientTests", handler: NoopSupabaseLogHandler()) ) } diff --git a/Tests/AuthTests/RequestsTests.swift b/Tests/AuthTests/RequestsTests.swift index 43414b5e..d8dd291f 100644 --- a/Tests/AuthTests/RequestsTests.swift +++ b/Tests/AuthTests/RequestsTests.swift @@ -430,7 +430,8 @@ final class RequestsTests: XCTestCase { codeVerifierStorage: .mock, api: api, eventEmitter: .mock, - sessionStorage: .mock + sessionStorage: .mock, + logger: SupabaseLogger(system: "AuthClientTests", handler: NoopSupabaseLogHandler()) ) } } From bc9d6ad8fbca91b3c2151b32fbf8d4a788ff760d Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 16 Jan 2024 05:47:59 -0300 Subject: [PATCH 4/8] Add SupabaseLoggingConfiguration --- Sources/Auth/AuthClient.swift | 18 +++++--- Sources/Auth/Internal/Dependencies.swift | 2 +- Sources/Auth/Storage/AuthLocalStorage.swift | 12 +++++ Sources/PostgREST/PostgrestClient.swift | 10 +++-- Sources/Supabase/SupabaseClient.swift | 4 +- Sources/Supabase/Types.swift | 8 ++-- Sources/_Helpers/SupabaseLogger/Entry.swift | 18 ++++---- Sources/_Helpers/SupabaseLogger/Level.swift | 27 ------------ .../SupabaseLogger/SupabaseLogHandler.swift | 44 +++++++++---------- .../SupabaseLogger/SupabaseLogLevel.swift | 25 +++++++++++ .../SupabaseLogger/SupabaseLogger.swift | 38 +++++++++++++--- Tests/AuthTests/GoTrueClientTests.swift | 8 +++- Tests/AuthTests/Mocks/Mocks.swift | 8 +++- Tests/AuthTests/RequestsTests.swift | 8 +++- 14 files changed, 144 insertions(+), 86 deletions(-) delete mode 100644 Sources/_Helpers/SupabaseLogger/Level.swift create mode 100644 Sources/_Helpers/SupabaseLogger/SupabaseLogLevel.swift diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index 8ffe3d91..e7cfdf97 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -16,7 +16,7 @@ public actor AuthClient { public var headers: [String: String] public let flowType: AuthFlowType public let localStorage: AuthLocalStorage - public let logHandler: SupabaseLogHandler + public let loggingConfiguration: SupabaseLoggingConfiguration public let encoder: JSONEncoder public let decoder: JSONDecoder public let fetch: FetchHandler @@ -28,7 +28,7 @@ public actor AuthClient { /// - headers: Custom headers to be included in requests. /// - flowType: The authentication flow type. /// - localStorage: The storage mechanism for local data. - /// - logHandler: The LogHandler to use. + /// - loggingConfiguration: The configuration used for the internal logger. /// - encoder: The JSON encoder to use for encoding requests. /// - decoder: The JSON decoder to use for decoding responses. /// - fetch: The asynchronous fetch handler for network requests. @@ -37,7 +37,7 @@ public actor AuthClient { headers: [String: String] = [:], flowType: AuthFlowType = Configuration.defaultFlowType, localStorage: AuthLocalStorage, - logHandler: SupabaseLogHandler = DefaultSupabaseLogHandler.shared, + loggingConfiguration: SupabaseLoggingConfiguration = SupabaseLoggingConfiguration(), encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder, decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder, fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) } @@ -48,7 +48,7 @@ public actor AuthClient { self.headers = headers self.flowType = flowType self.localStorage = localStorage - self.logHandler = logHandler + self.loggingConfiguration = loggingConfiguration self.encoder = encoder self.decoder = decoder self.fetch = fetch @@ -102,6 +102,7 @@ public actor AuthClient { /// - headers: Custom headers to be included in requests. /// - flowType: The authentication flow type.. /// - localStorage: The storage mechanism for local data.. + /// - loggingConfiguration: The configuration used for the internal logger. /// - encoder: The JSON encoder to use for encoding requests. /// - decoder: The JSON decoder to use for decoding responses. /// - fetch: The asynchronous fetch handler for network requests. @@ -110,7 +111,7 @@ public actor AuthClient { headers: [String: String] = [:], flowType: AuthFlowType = AuthClient.Configuration.defaultFlowType, localStorage: AuthLocalStorage, - logHandler: SupabaseLogHandler = DefaultSupabaseLogHandler.shared, + loggingConfiguration: SupabaseLoggingConfiguration = SupabaseLoggingConfiguration(), encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder, decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder, fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) } @@ -121,7 +122,7 @@ public actor AuthClient { headers: headers, flowType: flowType, localStorage: localStorage, - logHandler: logHandler, + loggingConfiguration: loggingConfiguration, encoder: encoder, decoder: decoder, fetch: fetch @@ -143,7 +144,10 @@ public actor AuthClient { api: api, eventEmitter: .live, sessionStorage: .live, - logger: SupabaseLogger(system: "AuthClient", handler: configuration.logHandler) + logger: SupabaseLogger( + system: "AuthClient", + configuration: configuration.loggingConfiguration + ) ) } diff --git a/Sources/Auth/Internal/Dependencies.swift b/Sources/Auth/Internal/Dependencies.swift index 57c7c66a..4bded161 100644 --- a/Sources/Auth/Internal/Dependencies.swift +++ b/Sources/Auth/Internal/Dependencies.swift @@ -1,4 +1,4 @@ -import _Helpers +@_spi(Internal) import _Helpers import ConcurrencyExtras import Foundation diff --git a/Sources/Auth/Storage/AuthLocalStorage.swift b/Sources/Auth/Storage/AuthLocalStorage.swift index 40e4930c..561e7211 100644 --- a/Sources/Auth/Storage/AuthLocalStorage.swift +++ b/Sources/Auth/Storage/AuthLocalStorage.swift @@ -5,3 +5,15 @@ public protocol AuthLocalStorage: Sendable { func retrieve(key: String) throws -> Data? func remove(key: String) throws } + +extension AuthClient.Configuration { + #if os(iOS) || os(macOS) || os(watchOS) || os(tvOS) + public static let defaultAuthLocalStorage = KeychainLocalStorage( + service: "supabase.gotrue.swift", + accessGroup: nil + ) + #elseif os(Windows) + public static let defaultAuthLocalStorage = + WinCredLocalStorage(service: "supabase.gotrue.swift") + #endif +} diff --git a/Sources/PostgREST/PostgrestClient.swift b/Sources/PostgREST/PostgrestClient.swift index b3ba4f8a..e0174bfb 100644 --- a/Sources/PostgREST/PostgrestClient.swift +++ b/Sources/PostgREST/PostgrestClient.swift @@ -27,6 +27,7 @@ public actor PostgrestClient { /// - url: The URL of the PostgREST server. /// - schema: The schema to use. /// - headers: The headers to include in requests. + /// - loggingConfiguration: The configuration used for the internal logger. /// - fetch: The fetch handler to use for requests. /// - encoder: The JSONEncoder to use for encoding. /// - decoder: The JSONDecoder to use for decoding. @@ -34,7 +35,7 @@ public actor PostgrestClient { url: URL, schema: String? = nil, headers: [String: String] = [:], - logHandler: SupabaseLogHandler = DefaultSupabaseLogHandler.shared, + loggingConfiguration: SupabaseLoggingConfiguration = SupabaseLoggingConfiguration(), fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }, encoder: JSONEncoder = PostgrestClient.Configuration.jsonEncoder, decoder: JSONDecoder = PostgrestClient.Configuration.jsonDecoder @@ -46,7 +47,7 @@ public actor PostgrestClient { self.encoder = encoder self.decoder = decoder - logger = SupabaseLogger(system: "Postgrest", handler: logHandler) + logger = SupabaseLogger(system: "Postgrest", configuration: loggingConfiguration) } } @@ -65,6 +66,7 @@ public actor PostgrestClient { /// - url: The URL of the PostgREST server. /// - schema: The schema to use. /// - headers: The headers to include in requests. + /// - loggingConfiguration: The configuration used for the internal logger. /// - session: The URLSession to use for requests. /// - encoder: The JSONEncoder to use for encoding. /// - decoder: The JSONDecoder to use for decoding. @@ -72,7 +74,7 @@ public actor PostgrestClient { url: URL, schema: String? = nil, headers: [String: String] = [:], - logHandler: SupabaseLogHandler = DefaultSupabaseLogHandler.shared, + loggingConfiguration: SupabaseLoggingConfiguration = SupabaseLoggingConfiguration(), fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }, encoder: JSONEncoder = PostgrestClient.Configuration.jsonEncoder, decoder: JSONDecoder = PostgrestClient.Configuration.jsonDecoder @@ -82,7 +84,7 @@ public actor PostgrestClient { url: url, schema: schema, headers: headers, - logHandler: logHandler, + loggingConfiguration: loggingConfiguration, fetch: fetch, encoder: encoder, decoder: decoder diff --git a/Sources/Supabase/SupabaseClient.swift b/Sources/Supabase/SupabaseClient.swift index ed6a579d..6a128cd1 100644 --- a/Sources/Supabase/SupabaseClient.swift +++ b/Sources/Supabase/SupabaseClient.swift @@ -31,7 +31,7 @@ public final class SupabaseClient: @unchecked Sendable { url: databaseURL, schema: options.db.schema, headers: defaultHeaders, - logHandler: options.global.logHandler, + loggingConfiguration: options.global.loggingConfiguration, fetch: fetchWithAuth, encoder: options.db.encoder, decoder: options.db.decoder @@ -94,7 +94,7 @@ public final class SupabaseClient: @unchecked Sendable { headers: defaultHeaders, flowType: options.auth.flowType, localStorage: options.auth.storage, - logHandler: options.global.logHandler, + loggingConfiguration: options.global.loggingConfiguration, encoder: options.auth.encoder, decoder: options.auth.decoder, fetch: { diff --git a/Sources/Supabase/Types.swift b/Sources/Supabase/Types.swift index abe27ddb..06cb99fc 100644 --- a/Sources/Supabase/Types.swift +++ b/Sources/Supabase/Types.swift @@ -68,17 +68,17 @@ public struct SupabaseClientOptions: Sendable { /// A session to use for making requests, defaults to `URLSession.shared`. public let session: URLSession - /// The log handler instance to use across all Supabase sub-packages. - public let logHandler: SupabaseLogHandler + /// The log configuration to use across all Supabase sub-packages. + public let loggingConfiguration: SupabaseLoggingConfiguration public init( headers: [String: String] = [:], session: URLSession = .shared, - logHandler: SupabaseLogHandler = DefaultSupabaseLogHandler.shared + loggingConfiguration: SupabaseLoggingConfiguration = SupabaseLoggingConfiguration() ) { self.headers = headers self.session = session - self.logHandler = logHandler + self.loggingConfiguration = loggingConfiguration } } diff --git a/Sources/_Helpers/SupabaseLogger/Entry.swift b/Sources/_Helpers/SupabaseLogger/Entry.swift index bf6c6156..072e752d 100644 --- a/Sources/_Helpers/SupabaseLogger/Entry.swift +++ b/Sources/_Helpers/SupabaseLogger/Entry.swift @@ -8,16 +8,16 @@ import Foundation extension SupabaseLogger { - public struct Entry: Codable, CustomStringConvertible { - public let system: String - public let level: Level - public let message: String - public let fileID: String - public let function: String - public let line: UInt - public let timestamp: TimeInterval + struct Entry: Codable, CustomStringConvertible { + let system: String + let level: SupabaseLogLevel + let message: String + let fileID: String + let function: String + let line: UInt + let timestamp: TimeInterval - public var description: String { + var description: String { let date = iso8601Formatter.string(from: Date(timeIntervalSince1970: timestamp)) let file = fileID.split(separator: ".", maxSplits: 1).first.map(String.init) ?? fileID return "\(date) [\(level)] [\(system)] [\(file).\(function):\(line)] \(message)" diff --git a/Sources/_Helpers/SupabaseLogger/Level.swift b/Sources/_Helpers/SupabaseLogger/Level.swift deleted file mode 100644 index 2a77cac8..00000000 --- a/Sources/_Helpers/SupabaseLogger/Level.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Level.swift -// -// -// Created by Guilherme Souza on 15/01/24. -// - -import Foundation - -extension SupabaseLogger { - public enum Level: Int, Codable, CustomStringConvertible, Sendable { - case debug - case warning - case error - - public var description: String { - switch self { - case .debug: - "debug" - case .warning: - "warning" - case .error: - "error" - } - } - } -} diff --git a/Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift b/Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift index cd053b8b..bceb5155 100644 --- a/Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift +++ b/Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift @@ -5,47 +5,47 @@ // Created by Guilherme Souza on 15/01/24. // +import ConcurrencyExtras import Foundation -public protocol SupabaseLogHandler: Sendable { +protocol SupabaseLogHandler: Sendable { func didLog(_ entry: SupabaseLogger.Entry) } -public final class DefaultSupabaseLogHandler: SupabaseLogHandler { - /// The default log handler instance used across all Supabase sub-packages. - public static let shared: SupabaseLogHandler = try! DefaultSupabaseLogHandler( - localFile: FileManager.default.url( - for: .applicationSupportDirectory, - in: .userDomainMask, - appropriateFor: nil, - create: true - ).appendingPathComponent("supabase-swift.log") - ) +final class DefaultSupabaseLogHandler: SupabaseLogHandler { + private static let cachedInstances = LockIsolated([URL: DefaultSupabaseLogHandler]()) + static func instance(for url: URL) -> DefaultSupabaseLogHandler { + if let instance = cachedInstances[url] { + return instance + } + + let instance = cachedInstances.withValue { + let instance = try! DefaultSupabaseLogHandler(localFile: url) + $0[url] = instance + return instance + } + + return instance + } let fileHandle: FileHandle - public init(localFile url: URL) throws { + private init(localFile url: URL) throws { if !FileManager.default.fileExists(atPath: url.path) { FileManager.default.createFile(atPath: url.path, contents: nil) } fileHandle = try FileHandle(forWritingTo: url) fileHandle.seekToEndOfFile() - - debugPrint("SupabaseLogHandler initialized at: \(url)") } - public func didLog(_ entry: SupabaseLogger.Entry) { + func didLog(_ entry: SupabaseLogger.Entry) { let logLine = "\(entry.description)\n" guard let data = logLine.data(using: .utf8) else { return } fileHandle.write(data) - debugPrint(entry.description) + #if DEBUG + print(entry.description) + #endif } } - -@_spi(Internal) -public struct NoopSupabaseLogHandler: SupabaseLogHandler { - public init() {} - public func didLog(_: SupabaseLogger.Entry) {} -} diff --git a/Sources/_Helpers/SupabaseLogger/SupabaseLogLevel.swift b/Sources/_Helpers/SupabaseLogger/SupabaseLogLevel.swift new file mode 100644 index 00000000..bc88d84b --- /dev/null +++ b/Sources/_Helpers/SupabaseLogger/SupabaseLogLevel.swift @@ -0,0 +1,25 @@ +// +// SupabaseLogLevel.swift +// +// +// Created by Guilherme Souza on 15/01/24. +// + +import Foundation + +public enum SupabaseLogLevel: Int, Codable, CustomStringConvertible, Sendable { + case debug + case warning + case error + + public var description: String { + switch self { + case .debug: + "debug" + case .warning: + "warning" + case .error: + "error" + } + } +} diff --git a/Sources/_Helpers/SupabaseLogger/SupabaseLogger.swift b/Sources/_Helpers/SupabaseLogger/SupabaseLogger.swift index 6f8fa85f..dd0ac20a 100644 --- a/Sources/_Helpers/SupabaseLogger/SupabaseLogger.swift +++ b/Sources/_Helpers/SupabaseLogger/SupabaseLogger.swift @@ -1,18 +1,42 @@ import Foundation +public struct SupabaseLoggingConfiguration: Sendable { + public let disabled: Bool + public let minLevel: SupabaseLogLevel + public let logFile: URL + + public init( + disabled: Bool = true, + minLevel: SupabaseLogLevel = .debug, + logFile: URL = Self.defaultLogFileLocationURL + ) { + self.disabled = disabled + self.minLevel = minLevel + self.logFile = logFile + } + + public static let defaultLogFileLocationURL = try! FileManager.default.url( + for: .applicationSupportDirectory, + in: .userDomainMask, + appropriateFor: nil, + create: true + ).appendingPathComponent("supabase-swift.log") +} + +@_spi(Internal) public struct SupabaseLogger: Sendable { let system: String - let minLevel: Level + let configuration: SupabaseLoggingConfiguration let handler: SupabaseLogHandler - public init(system: String, handler: SupabaseLogHandler, minLevel: Level = .debug) { + public init(system: String, configuration: SupabaseLoggingConfiguration) { self.system = system - self.handler = handler - self.minLevel = minLevel + handler = DefaultSupabaseLogHandler.instance(for: configuration.logFile) + self.configuration = configuration } public func log( - _ level: Level, + _ level: SupabaseLogLevel, message: @autoclosure () -> String, fileID: StaticString = #fileID, function: StaticString = #function, @@ -33,8 +57,8 @@ public struct SupabaseLogger: Sendable { handler.didLog(entry) } - private func shouldLog(_ level: Level) -> Bool { - level.rawValue >= minLevel.rawValue + private func shouldLog(_ level: SupabaseLogLevel) -> Bool { + !configuration.disabled && level.rawValue >= configuration.minLevel.rawValue } public func debug( diff --git a/Tests/AuthTests/GoTrueClientTests.swift b/Tests/AuthTests/GoTrueClientTests.swift index 4f5ccc4b..516119db 100644 --- a/Tests/AuthTests/GoTrueClientTests.swift +++ b/Tests/AuthTests/GoTrueClientTests.swift @@ -167,7 +167,13 @@ final class AuthClientTests: XCTestCase { api: .mock, eventEmitter: .mock, sessionStorage: .mock, - logger: SupabaseLogger(system: "AuthClientTests", handler: NoopSupabaseLogHandler()) + logger: SupabaseLogger( + system: "AuthClientTests", + configuration: SupabaseLoggingConfiguration( + logFile: URL(fileURLWithPath: NSTemporaryDirectory()) + .appendingPathComponent("supabase-swift.log") + ) + ) ) addTeardownBlock { [weak sut] in diff --git a/Tests/AuthTests/Mocks/Mocks.swift b/Tests/AuthTests/Mocks/Mocks.swift index 35bcd97b..59bfed90 100644 --- a/Tests/AuthTests/Mocks/Mocks.swift +++ b/Tests/AuthTests/Mocks/Mocks.swift @@ -115,7 +115,13 @@ extension Dependencies { sessionStorage: .mock, sessionRefresher: .mock, codeVerifierStorage: .mock, - logger: SupabaseLogger(system: "AuthClientTests", handler: NoopSupabaseLogHandler()) + logger: SupabaseLogger( + system: "AuthClientTests", + configuration: SupabaseLoggingConfiguration( + logFile: URL(fileURLWithPath: NSTemporaryDirectory()) + .appendingPathComponent("supabase-swift.log") + ) + ) ) } diff --git a/Tests/AuthTests/RequestsTests.swift b/Tests/AuthTests/RequestsTests.swift index d8dd291f..8b2a0ec0 100644 --- a/Tests/AuthTests/RequestsTests.swift +++ b/Tests/AuthTests/RequestsTests.swift @@ -431,7 +431,13 @@ final class RequestsTests: XCTestCase { api: api, eventEmitter: .mock, sessionStorage: .mock, - logger: SupabaseLogger(system: "AuthClientTests", handler: NoopSupabaseLogHandler()) + logger: SupabaseLogger( + system: "AuthClientTests", + configuration: SupabaseLoggingConfiguration( + logFile: URL(fileURLWithPath: NSTemporaryDirectory()) + .appendingPathComponent("supabase-swift.log") + ) + ) ) } } From 303495fd9516f54da1326bc916bc00eb6bbd6269 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 16 Jan 2024 11:51:50 -0300 Subject: [PATCH 5/8] Simplify logger --- Examples/UserManagement/Supabase.swift | 26 ++++- Package.resolved | 11 +- Sources/Auth/AuthClient.swift | 32 +++--- Sources/Auth/Internal/Dependencies.swift | 2 +- Sources/PostgREST/PostgrestBuilder.swift | 4 +- Sources/PostgREST/PostgrestClient.swift | 15 ++- Sources/Realtime/RealtimeClient.swift | 17 ++- Sources/Storage/StorageApi.swift | 2 +- Sources/Storage/SupabaseStorage.swift | 6 +- Sources/Supabase/SupabaseClient.swift | 14 ++- Sources/Supabase/Types.swift | 8 +- Sources/_Helpers/Request.swift | 7 +- Sources/_Helpers/SupabaseLogger.swift | 103 ++++++++++++++++++ Sources/_Helpers/SupabaseLogger/Entry.swift | 32 ------ .../SupabaseLogger/SupabaseLogHandler.swift | 51 --------- .../SupabaseLogger/SupabaseLogLevel.swift | 25 ----- .../SupabaseLogger/SupabaseLogger.swift | 90 --------------- Tests/AuthTests/GoTrueClientTests.swift | 8 +- Tests/AuthTests/Mocks/Mocks.swift | 8 +- Tests/AuthTests/RequestsTests.swift | 10 +- 20 files changed, 202 insertions(+), 269 deletions(-) create mode 100644 Sources/_Helpers/SupabaseLogger.swift delete mode 100644 Sources/_Helpers/SupabaseLogger/Entry.swift delete mode 100644 Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift delete mode 100644 Sources/_Helpers/SupabaseLogger/SupabaseLogLevel.swift delete mode 100644 Sources/_Helpers/SupabaseLogger/SupabaseLogger.swift diff --git a/Examples/UserManagement/Supabase.swift b/Examples/UserManagement/Supabase.swift index fdc04748..9b132003 100644 --- a/Examples/UserManagement/Supabase.swift +++ b/Examples/UserManagement/Supabase.swift @@ -6,13 +6,31 @@ // import Foundation +import OSLog import Supabase let supabase = SupabaseClient( supabaseURL: URL(string: "https://PROJECT_ID.supabase.co")!, supabaseKey: "YOUR_SUPABASE_ANON_KEY", - options: .init(auth: .init(storage: KeychainLocalStorage( - service: "supabase.gotrue.swift", - accessGroup: nil - ))) + options: .init( + auth: .init( + storage: KeychainLocalStorage(service: "supabase.gotrue.swift", accessGroup: nil) + ), + global: .init(logger: AppLogger()) + ) ) + +struct AppLogger: SupabaseLogger { + let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "supabase") + + func log(message: SupabaseLogMessage) { + switch message.level { + case .verbose: + logger.log(level: .info, "\(message.description)") + case .debug: + logger.log(level: .debug, "\(message.description)") + case .warning, .error: + logger.log(level: .error, "\(message.description)") + } + } +} diff --git a/Package.resolved b/Package.resolved index 5c8656bb..4ac5291a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "keychainaccess", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kishikawakatsumi/KeychainAccess", + "state" : { + "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7", + "version" : "4.2.2" + } + }, { "identity" : "swift-concurrency-extras", "kind" : "remoteSourceControl", @@ -47,4 +56,4 @@ } ], "version" : 2 -} \ No newline at end of file +} diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index e7cfdf97..35521872 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -16,7 +16,7 @@ public actor AuthClient { public var headers: [String: String] public let flowType: AuthFlowType public let localStorage: AuthLocalStorage - public let loggingConfiguration: SupabaseLoggingConfiguration + public let logger: SupabaseLogger? public let encoder: JSONEncoder public let decoder: JSONDecoder public let fetch: FetchHandler @@ -28,7 +28,7 @@ public actor AuthClient { /// - headers: Custom headers to be included in requests. /// - flowType: The authentication flow type. /// - localStorage: The storage mechanism for local data. - /// - loggingConfiguration: The configuration used for the internal logger. + /// - logger: The logger to use. /// - encoder: The JSON encoder to use for encoding requests. /// - decoder: The JSON decoder to use for decoding responses. /// - fetch: The asynchronous fetch handler for network requests. @@ -37,7 +37,7 @@ public actor AuthClient { headers: [String: String] = [:], flowType: AuthFlowType = Configuration.defaultFlowType, localStorage: AuthLocalStorage, - loggingConfiguration: SupabaseLoggingConfiguration = SupabaseLoggingConfiguration(), + logger: SupabaseLogger? = nil, encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder, decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder, fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) } @@ -48,7 +48,7 @@ public actor AuthClient { self.headers = headers self.flowType = flowType self.localStorage = localStorage - self.loggingConfiguration = loggingConfiguration + self.logger = logger self.encoder = encoder self.decoder = decoder self.fetch = fetch @@ -79,7 +79,7 @@ public actor AuthClient { Dependencies.current.value!.currentDate } - private var logger: SupabaseLogger { + private var logger: SupabaseLogger? { Dependencies.current.value!.logger } @@ -102,7 +102,7 @@ public actor AuthClient { /// - headers: Custom headers to be included in requests. /// - flowType: The authentication flow type.. /// - localStorage: The storage mechanism for local data.. - /// - loggingConfiguration: The configuration used for the internal logger. + /// - logger: The logger to use. /// - encoder: The JSON encoder to use for encoding requests. /// - decoder: The JSON decoder to use for decoding responses. /// - fetch: The asynchronous fetch handler for network requests. @@ -111,7 +111,7 @@ public actor AuthClient { headers: [String: String] = [:], flowType: AuthFlowType = AuthClient.Configuration.defaultFlowType, localStorage: AuthLocalStorage, - loggingConfiguration: SupabaseLoggingConfiguration = SupabaseLoggingConfiguration(), + logger: SupabaseLogger? = nil, encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder, decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder, fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) } @@ -122,7 +122,7 @@ public actor AuthClient { headers: headers, flowType: flowType, localStorage: localStorage, - loggingConfiguration: loggingConfiguration, + logger: logger, encoder: encoder, decoder: decoder, fetch: fetch @@ -135,7 +135,10 @@ public actor AuthClient { /// - Parameters: /// - configuration: The client configuration. public init(configuration: Configuration) { - let api = APIClient.live(http: HTTPClient(fetchHandler: configuration.fetch)) + let api = APIClient.live(http: HTTPClient( + logger: configuration.logger, + fetchHandler: configuration.fetch + )) self.init( configuration: configuration, @@ -144,10 +147,7 @@ public actor AuthClient { api: api, eventEmitter: .live, sessionStorage: .live, - logger: SupabaseLogger( - system: "AuthClient", - configuration: configuration.loggingConfiguration - ) + logger: configuration.logger ) } @@ -159,7 +159,7 @@ public actor AuthClient { api: APIClient, eventEmitter: EventEmitter, sessionStorage: SessionStorage, - logger: SupabaseLogger + logger: SupabaseLogger? ) { mfa = AuthMFA() @@ -189,11 +189,11 @@ public actor AuthClient { session: Session? )> { let (id, stream) = eventEmitter.attachListener() - logger.debug("auth state change listener with id '\(id.uuidString)' attached.") + logger?.debug("auth state change listener with id '\(id.uuidString)' attached.") Task { [id] in await emitInitialSession(forStreamWithID: id) - logger.debug("initial session for listener with id '\(id.uuidString)' emitted.") + logger?.debug("initial session for listener with id '\(id.uuidString)' emitted.") } return stream diff --git a/Sources/Auth/Internal/Dependencies.swift b/Sources/Auth/Internal/Dependencies.swift index 4bded161..99722b0b 100644 --- a/Sources/Auth/Internal/Dependencies.swift +++ b/Sources/Auth/Internal/Dependencies.swift @@ -13,5 +13,5 @@ struct Dependencies: Sendable { var sessionRefresher: SessionRefresher var codeVerifierStorage: CodeVerifierStorage var currentDate: @Sendable () -> Date = { Date() } - var logger: SupabaseLogger + var logger: SupabaseLogger? } diff --git a/Sources/PostgREST/PostgrestBuilder.swift b/Sources/PostgREST/PostgrestBuilder.swift index b09ee1b9..d2bcea42 100644 --- a/Sources/PostgREST/PostgrestBuilder.swift +++ b/Sources/PostgREST/PostgrestBuilder.swift @@ -26,7 +26,7 @@ public class PostgrestBuilder: @unchecked Sendable { request: Request ) { self.configuration = configuration - http = HTTPClient(fetchHandler: configuration.fetch) + http = HTTPClient(logger: configuration.logger, fetchHandler: configuration.fetch) mutableState = LockIsolated( MutableState( @@ -74,7 +74,7 @@ public class PostgrestBuilder: @unchecked Sendable { do { return try configuration.decoder.decode(T.self, from: data) } catch { - configuration.logger.error("Fail to decode type '\(T.self) with error: \(error)") + configuration.logger?.error("Fail to decode type '\(T.self) with error: \(error)") throw error } } diff --git a/Sources/PostgREST/PostgrestClient.swift b/Sources/PostgREST/PostgrestClient.swift index e0174bfb..f171cc0a 100644 --- a/Sources/PostgREST/PostgrestClient.swift +++ b/Sources/PostgREST/PostgrestClient.swift @@ -20,14 +20,14 @@ public actor PostgrestClient { public var encoder: JSONEncoder public var decoder: JSONDecoder - let logger: SupabaseLogger + let logger: SupabaseLogger? /// Initializes a new configuration for the PostgREST client. /// - Parameters: /// - url: The URL of the PostgREST server. /// - schema: The schema to use. /// - headers: The headers to include in requests. - /// - loggingConfiguration: The configuration used for the internal logger. + /// - logger: The logger to use. /// - fetch: The fetch handler to use for requests. /// - encoder: The JSONEncoder to use for encoding. /// - decoder: The JSONDecoder to use for decoding. @@ -35,7 +35,7 @@ public actor PostgrestClient { url: URL, schema: String? = nil, headers: [String: String] = [:], - loggingConfiguration: SupabaseLoggingConfiguration = SupabaseLoggingConfiguration(), + logger: SupabaseLogger? = nil, fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }, encoder: JSONEncoder = PostgrestClient.Configuration.jsonEncoder, decoder: JSONDecoder = PostgrestClient.Configuration.jsonDecoder @@ -43,11 +43,10 @@ public actor PostgrestClient { self.url = url self.schema = schema self.headers = headers + self.logger = logger self.fetch = fetch self.encoder = encoder self.decoder = decoder - - logger = SupabaseLogger(system: "Postgrest", configuration: loggingConfiguration) } } @@ -66,7 +65,7 @@ public actor PostgrestClient { /// - url: The URL of the PostgREST server. /// - schema: The schema to use. /// - headers: The headers to include in requests. - /// - loggingConfiguration: The configuration used for the internal logger. + /// - logger: The logger to use. /// - session: The URLSession to use for requests. /// - encoder: The JSONEncoder to use for encoding. /// - decoder: The JSONDecoder to use for decoding. @@ -74,7 +73,7 @@ public actor PostgrestClient { url: URL, schema: String? = nil, headers: [String: String] = [:], - loggingConfiguration: SupabaseLoggingConfiguration = SupabaseLoggingConfiguration(), + logger: SupabaseLogger? = nil, fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }, encoder: JSONEncoder = PostgrestClient.Configuration.jsonEncoder, decoder: JSONDecoder = PostgrestClient.Configuration.jsonDecoder @@ -84,7 +83,7 @@ public actor PostgrestClient { url: url, schema: schema, headers: headers, - loggingConfiguration: loggingConfiguration, + logger: logger, fetch: fetch, encoder: encoder, decoder: decoder diff --git a/Sources/Realtime/RealtimeClient.swift b/Sources/Realtime/RealtimeClient.swift index 3f9d1659..9ab9fe31 100644 --- a/Sources/Realtime/RealtimeClient.swift +++ b/Sources/Realtime/RealtimeClient.swift @@ -186,14 +186,16 @@ public class RealtimeClient: PhoenixTransportDelegate { _ endPoint: String, headers: [String: String] = [:], params: Payload? = nil, - vsn: String = Defaults.vsn + vsn: String = Defaults.vsn, + logger: SupabaseLogger? = nil ) { self.init( endPoint: endPoint, headers: headers, transport: { url in URLSessionTransport(url: url) }, paramsClosure: { params }, - vsn: vsn + vsn: vsn, + logger: logger ) } @@ -202,14 +204,16 @@ public class RealtimeClient: PhoenixTransportDelegate { _ endPoint: String, headers: [String: String] = [:], paramsClosure: PayloadClosure?, - vsn: String = Defaults.vsn + vsn: String = Defaults.vsn, + logger: SupabaseLogger? = nil ) { self.init( endPoint: endPoint, headers: headers, transport: { url in URLSessionTransport(url: url) }, paramsClosure: paramsClosure, - vsn: vsn + vsn: vsn, + logger: logger ) } @@ -218,7 +222,8 @@ public class RealtimeClient: PhoenixTransportDelegate { headers: [String: String] = [:], transport: @escaping ((URL) -> PhoenixTransport), paramsClosure: PayloadClosure? = nil, - vsn: String = Defaults.vsn + vsn: String = Defaults.vsn, + logger: SupabaseLogger? = nil ) { self.transport = transport self.paramsClosure = paramsClosure @@ -230,7 +235,7 @@ public class RealtimeClient: PhoenixTransportDelegate { headers["X-Client-Info"] = "realtime-swift/\(version)" } self.headers = headers - http = HTTPClient(fetchHandler: { try await URLSession.shared.data(for: $0) }) + http = HTTPClient(logger: logger, fetchHandler: { try await URLSession.shared.data(for: $0) }) let params = paramsClosure?() if let jwt = (params?["Authorization"] as? String)?.split(separator: " ").last { diff --git a/Sources/Storage/StorageApi.swift b/Sources/Storage/StorageApi.swift index 57b115bf..69f3421f 100644 --- a/Sources/Storage/StorageApi.swift +++ b/Sources/Storage/StorageApi.swift @@ -16,7 +16,7 @@ public class StorageApi: @unchecked Sendable { configuration.headers["X-Client-Info"] = "storage-swift/\(version)" } self.configuration = configuration - http = HTTPClient(fetchHandler: configuration.session.fetch) + http = HTTPClient(logger: configuration.logger, fetchHandler: configuration.session.fetch) } @discardableResult diff --git a/Sources/Storage/SupabaseStorage.swift b/Sources/Storage/SupabaseStorage.swift index 1b7c283c..9fc2d5f5 100644 --- a/Sources/Storage/SupabaseStorage.swift +++ b/Sources/Storage/SupabaseStorage.swift @@ -1,3 +1,4 @@ +import _Helpers import Foundation public struct StorageClientConfiguration { @@ -6,19 +7,22 @@ public struct StorageClientConfiguration { public let encoder: JSONEncoder public let decoder: JSONDecoder public let session: StorageHTTPSession + public let logger: SupabaseLogger? public init( url: URL, headers: [String: String], encoder: JSONEncoder = .defaultStorageEncoder, decoder: JSONDecoder = .defaultStorageDecoder, - session: StorageHTTPSession = .init() + session: StorageHTTPSession = .init(), + logger: SupabaseLogger? = nil ) { self.url = url self.headers = headers self.encoder = encoder self.decoder = decoder self.session = session + self.logger = logger } } diff --git a/Sources/Supabase/SupabaseClient.swift b/Sources/Supabase/SupabaseClient.swift index 6a128cd1..087e58d7 100644 --- a/Sources/Supabase/SupabaseClient.swift +++ b/Sources/Supabase/SupabaseClient.swift @@ -11,6 +11,10 @@ import Foundation import FoundationNetworking #endif +public typealias SupabaseLogger = _Helpers.SupabaseLogger +public typealias SupabaseLogLevel = _Helpers.SupabaseLogLevel +public typealias SupabaseLogMessage = _Helpers.SupabaseLogMessage + let version = _Helpers.version /// Supabase Client. @@ -31,7 +35,7 @@ public final class SupabaseClient: @unchecked Sendable { url: databaseURL, schema: options.db.schema, headers: defaultHeaders, - loggingConfiguration: options.global.loggingConfiguration, + logger: options.global.logger, fetch: fetchWithAuth, encoder: options.db.encoder, decoder: options.db.decoder @@ -42,7 +46,8 @@ public final class SupabaseClient: @unchecked Sendable { configuration: StorageClientConfiguration( url: storageURL, headers: defaultHeaders, - session: StorageHTTPSession(fetch: fetchWithAuth, upload: uploadWithAuth) + session: StorageHTTPSession(fetch: fetchWithAuth, upload: uploadWithAuth), + logger: options.global.logger ) ) @@ -94,7 +99,7 @@ public final class SupabaseClient: @unchecked Sendable { headers: defaultHeaders, flowType: options.auth.flowType, localStorage: options.auth.storage, - loggingConfiguration: options.global.loggingConfiguration, + logger: options.global.logger, encoder: options.auth.encoder, decoder: options.auth.decoder, fetch: { @@ -106,7 +111,8 @@ public final class SupabaseClient: @unchecked Sendable { realtime = RealtimeClient( supabaseURL.appendingPathComponent("/realtime/v1").absoluteString, headers: defaultHeaders, - params: defaultHeaders + params: defaultHeaders, + logger: options.global.logger ) listenForAuthEvents() diff --git a/Sources/Supabase/Types.swift b/Sources/Supabase/Types.swift index 06cb99fc..5fba375d 100644 --- a/Sources/Supabase/Types.swift +++ b/Sources/Supabase/Types.swift @@ -68,17 +68,17 @@ public struct SupabaseClientOptions: Sendable { /// A session to use for making requests, defaults to `URLSession.shared`. public let session: URLSession - /// The log configuration to use across all Supabase sub-packages. - public let loggingConfiguration: SupabaseLoggingConfiguration + /// The logger to use across all Supabase sub-packages. + public let logger: SupabaseLogger? public init( headers: [String: String] = [:], session: URLSession = .shared, - loggingConfiguration: SupabaseLoggingConfiguration = SupabaseLoggingConfiguration() + logger: SupabaseLogger? = nil ) { self.headers = headers self.session = session - self.loggingConfiguration = loggingConfiguration + self.logger = logger } } diff --git a/Sources/_Helpers/Request.swift b/Sources/_Helpers/Request.swift index 3fc13be2..ca9c20e5 100644 --- a/Sources/_Helpers/Request.swift +++ b/Sources/_Helpers/Request.swift @@ -8,15 +8,20 @@ import Foundation public struct HTTPClient: Sendable { public typealias FetchHandler = @Sendable (URLRequest) async throws -> (Data, URLResponse) + let logger: SupabaseLogger? let fetchHandler: FetchHandler - public init(fetchHandler: @escaping FetchHandler) { + public init(logger: SupabaseLogger?, fetchHandler: @escaping FetchHandler) { + self.logger = logger self.fetchHandler = fetchHandler } public func fetch(_ request: Request, baseURL: URL) async throws -> Response { + let id = UUID().uuidString let urlRequest = try request.urlRequest(withBaseURL: baseURL) + logger?.verbose("Request [\(id)]: \(urlRequest)") let (data, response) = try await fetchHandler(urlRequest) + logger?.verbose("Response [\(id)]: \(response)") guard let httpResponse = response as? HTTPURLResponse else { throw URLError(.badServerResponse) diff --git a/Sources/_Helpers/SupabaseLogger.swift b/Sources/_Helpers/SupabaseLogger.swift new file mode 100644 index 00000000..d181a696 --- /dev/null +++ b/Sources/_Helpers/SupabaseLogger.swift @@ -0,0 +1,103 @@ +import Foundation + +public enum SupabaseLogLevel: Int, Codable, CustomStringConvertible, Sendable { + case verbose + case debug + case warning + case error + + public var description: String { + switch self { + case .verbose: "verbose" + case .debug: "debug" + case .warning: "warning" + case .error: "error" + } + } +} + +public struct SupabaseLogMessage: Codable, CustomStringConvertible { + public let system: String + public let level: SupabaseLogLevel + public let message: String + public let fileID: String + public let function: String + public let line: UInt + public let timestamp: TimeInterval + + public var description: String { + let date = iso8601Formatter.string(from: Date(timeIntervalSince1970: timestamp)) + let file = fileID.split(separator: ".", maxSplits: 1).first.map(String.init) ?? fileID + return "\(date) [\(level)] [\(system)] [\(file).\(function):\(line)] \(message)" + } +} + +private let iso8601Formatter: ISO8601DateFormatter = { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime] + return formatter +}() + +public protocol SupabaseLogger: Sendable { + func log(message: SupabaseLogMessage) +} + +extension SupabaseLogger { + public func log( + _ level: SupabaseLogLevel, + message: @autoclosure () -> String, + fileID: StaticString = #fileID, + function: StaticString = #function, + line: UInt = #line + ) { + let system = "\(fileID)".split(separator: "/").first ?? "" + + log( + message: SupabaseLogMessage( + system: "\(system)", + level: level, + message: message(), + fileID: "\(fileID)", + function: "\(function)", + line: line, + timestamp: Date().timeIntervalSince1970 + ) + ) + } + + public func verbose( + _ message: @autoclosure () -> String, + fileID: StaticString = #fileID, + function: StaticString = #function, + line: UInt = #line + ) { + log(.verbose, message: message(), fileID: fileID, function: function, line: line) + } + + public func debug( + _ message: @autoclosure () -> String, + fileID: StaticString = #fileID, + function: StaticString = #function, + line: UInt = #line + ) { + log(.debug, message: message(), fileID: fileID, function: function, line: line) + } + + public func warning( + _ message: @autoclosure () -> String, + fileID: StaticString = #fileID, + function: StaticString = #function, + line: UInt = #line + ) { + log(.warning, message: message(), fileID: fileID, function: function, line: line) + } + + public func error( + _ message: @autoclosure () -> String, + fileID: StaticString = #fileID, + function: StaticString = #function, + line: UInt = #line + ) { + log(.error, message: message(), fileID: fileID, function: function, line: line) + } +} diff --git a/Sources/_Helpers/SupabaseLogger/Entry.swift b/Sources/_Helpers/SupabaseLogger/Entry.swift deleted file mode 100644 index 072e752d..00000000 --- a/Sources/_Helpers/SupabaseLogger/Entry.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// Entry.swift -// -// -// Created by Guilherme Souza on 15/01/24. -// - -import Foundation - -extension SupabaseLogger { - struct Entry: Codable, CustomStringConvertible { - let system: String - let level: SupabaseLogLevel - let message: String - let fileID: String - let function: String - let line: UInt - let timestamp: TimeInterval - - var description: String { - let date = iso8601Formatter.string(from: Date(timeIntervalSince1970: timestamp)) - let file = fileID.split(separator: ".", maxSplits: 1).first.map(String.init) ?? fileID - return "\(date) [\(level)] [\(system)] [\(file).\(function):\(line)] \(message)" - } - } -} - -private let iso8601Formatter: ISO8601DateFormatter = { - let formatter = ISO8601DateFormatter() - formatter.formatOptions = [.withInternetDateTime] - return formatter -}() diff --git a/Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift b/Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift deleted file mode 100644 index bceb5155..00000000 --- a/Sources/_Helpers/SupabaseLogger/SupabaseLogHandler.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// SupabaseLogHandler.swift -// -// -// Created by Guilherme Souza on 15/01/24. -// - -import ConcurrencyExtras -import Foundation - -protocol SupabaseLogHandler: Sendable { - func didLog(_ entry: SupabaseLogger.Entry) -} - -final class DefaultSupabaseLogHandler: SupabaseLogHandler { - private static let cachedInstances = LockIsolated([URL: DefaultSupabaseLogHandler]()) - static func instance(for url: URL) -> DefaultSupabaseLogHandler { - if let instance = cachedInstances[url] { - return instance - } - - let instance = cachedInstances.withValue { - let instance = try! DefaultSupabaseLogHandler(localFile: url) - $0[url] = instance - return instance - } - - return instance - } - - let fileHandle: FileHandle - - private init(localFile url: URL) throws { - if !FileManager.default.fileExists(atPath: url.path) { - FileManager.default.createFile(atPath: url.path, contents: nil) - } - - fileHandle = try FileHandle(forWritingTo: url) - fileHandle.seekToEndOfFile() - } - - func didLog(_ entry: SupabaseLogger.Entry) { - let logLine = "\(entry.description)\n" - guard let data = logLine.data(using: .utf8) else { return } - fileHandle.write(data) - - #if DEBUG - print(entry.description) - #endif - } -} diff --git a/Sources/_Helpers/SupabaseLogger/SupabaseLogLevel.swift b/Sources/_Helpers/SupabaseLogger/SupabaseLogLevel.swift deleted file mode 100644 index bc88d84b..00000000 --- a/Sources/_Helpers/SupabaseLogger/SupabaseLogLevel.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// SupabaseLogLevel.swift -// -// -// Created by Guilherme Souza on 15/01/24. -// - -import Foundation - -public enum SupabaseLogLevel: Int, Codable, CustomStringConvertible, Sendable { - case debug - case warning - case error - - public var description: String { - switch self { - case .debug: - "debug" - case .warning: - "warning" - case .error: - "error" - } - } -} diff --git a/Sources/_Helpers/SupabaseLogger/SupabaseLogger.swift b/Sources/_Helpers/SupabaseLogger/SupabaseLogger.swift deleted file mode 100644 index dd0ac20a..00000000 --- a/Sources/_Helpers/SupabaseLogger/SupabaseLogger.swift +++ /dev/null @@ -1,90 +0,0 @@ -import Foundation - -public struct SupabaseLoggingConfiguration: Sendable { - public let disabled: Bool - public let minLevel: SupabaseLogLevel - public let logFile: URL - - public init( - disabled: Bool = true, - minLevel: SupabaseLogLevel = .debug, - logFile: URL = Self.defaultLogFileLocationURL - ) { - self.disabled = disabled - self.minLevel = minLevel - self.logFile = logFile - } - - public static let defaultLogFileLocationURL = try! FileManager.default.url( - for: .applicationSupportDirectory, - in: .userDomainMask, - appropriateFor: nil, - create: true - ).appendingPathComponent("supabase-swift.log") -} - -@_spi(Internal) -public struct SupabaseLogger: Sendable { - let system: String - let configuration: SupabaseLoggingConfiguration - let handler: SupabaseLogHandler - - public init(system: String, configuration: SupabaseLoggingConfiguration) { - self.system = system - handler = DefaultSupabaseLogHandler.instance(for: configuration.logFile) - self.configuration = configuration - } - - public func log( - _ level: SupabaseLogLevel, - message: @autoclosure () -> String, - fileID: StaticString = #fileID, - function: StaticString = #function, - line: UInt = #line - ) { - guard shouldLog(level) else { return } - - let entry = Entry( - system: system, - level: level, - message: message(), - fileID: "\(fileID)", - function: "\(function)", - line: line, - timestamp: Date().timeIntervalSince1970 - ) - - handler.didLog(entry) - } - - private func shouldLog(_ level: SupabaseLogLevel) -> Bool { - !configuration.disabled && level.rawValue >= configuration.minLevel.rawValue - } - - public func debug( - _ message: @autoclosure () -> String, - fileID: StaticString = #fileID, - function: StaticString = #function, - line: UInt = #line - ) { - log(.debug, message: message(), fileID: fileID, function: function, line: line) - } - - public func warning( - _ message: @autoclosure () -> String, - fileID: StaticString = #fileID, - function: StaticString = #function, - line: UInt = #line - ) { - log(.warning, message: message(), fileID: fileID, function: function, line: line) - } - - public func error( - _ message: @autoclosure () -> String, - fileID: StaticString = #fileID, - function: StaticString = #function, - line: UInt = #line - ) { - log(.error, message: message(), fileID: fileID, function: function, line: line) - } -} diff --git a/Tests/AuthTests/GoTrueClientTests.swift b/Tests/AuthTests/GoTrueClientTests.swift index 516119db..e0640a45 100644 --- a/Tests/AuthTests/GoTrueClientTests.swift +++ b/Tests/AuthTests/GoTrueClientTests.swift @@ -167,13 +167,7 @@ final class AuthClientTests: XCTestCase { api: .mock, eventEmitter: .mock, sessionStorage: .mock, - logger: SupabaseLogger( - system: "AuthClientTests", - configuration: SupabaseLoggingConfiguration( - logFile: URL(fileURLWithPath: NSTemporaryDirectory()) - .appendingPathComponent("supabase-swift.log") - ) - ) + logger: nil ) addTeardownBlock { [weak sut] in diff --git a/Tests/AuthTests/Mocks/Mocks.swift b/Tests/AuthTests/Mocks/Mocks.swift index 59bfed90..d163fbba 100644 --- a/Tests/AuthTests/Mocks/Mocks.swift +++ b/Tests/AuthTests/Mocks/Mocks.swift @@ -115,13 +115,7 @@ extension Dependencies { sessionStorage: .mock, sessionRefresher: .mock, codeVerifierStorage: .mock, - logger: SupabaseLogger( - system: "AuthClientTests", - configuration: SupabaseLoggingConfiguration( - logFile: URL(fileURLWithPath: NSTemporaryDirectory()) - .appendingPathComponent("supabase-swift.log") - ) - ) + logger: nil ) } diff --git a/Tests/AuthTests/RequestsTests.swift b/Tests/AuthTests/RequestsTests.swift index 8b2a0ec0..5564328c 100644 --- a/Tests/AuthTests/RequestsTests.swift +++ b/Tests/AuthTests/RequestsTests.swift @@ -422,7 +422,7 @@ final class RequestsTests: XCTestCase { } ) - let api = APIClient.live(http: HTTPClient(fetchHandler: configuration.fetch)) + let api = APIClient.live(http: HTTPClient(logger: nil, fetchHandler: configuration.fetch)) return AuthClient( configuration: configuration, @@ -431,13 +431,7 @@ final class RequestsTests: XCTestCase { api: api, eventEmitter: .mock, sessionStorage: .mock, - logger: SupabaseLogger( - system: "AuthClientTests", - configuration: SupabaseLoggingConfiguration( - logFile: URL(fileURLWithPath: NSTemporaryDirectory()) - .appendingPathComponent("supabase-swift.log") - ) - ) + logger: nil ) } } From 3106185709b109c4d5e5cc82fc9a92bc3ab0967b Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 16 Jan 2024 17:02:28 -0300 Subject: [PATCH 6/8] Inline log methods --- Examples/UserManagement/Supabase.swift | 3 --- Sources/Supabase/Types.swift | 30 ++++++++++++++++++++++++++ Sources/_Helpers/SupabaseLogger.swift | 24 +++++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/Examples/UserManagement/Supabase.swift b/Examples/UserManagement/Supabase.swift index 9b132003..14b19d90 100644 --- a/Examples/UserManagement/Supabase.swift +++ b/Examples/UserManagement/Supabase.swift @@ -13,9 +13,6 @@ let supabase = SupabaseClient( supabaseURL: URL(string: "https://PROJECT_ID.supabase.co")!, supabaseKey: "YOUR_SUPABASE_ANON_KEY", options: .init( - auth: .init( - storage: KeychainLocalStorage(service: "supabase.gotrue.swift", accessGroup: nil) - ), global: .init(logger: AppLogger()) ) ) diff --git a/Sources/Supabase/Types.swift b/Sources/Supabase/Types.swift index 5fba375d..29ba6734 100644 --- a/Sources/Supabase/Types.swift +++ b/Sources/Supabase/Types.swift @@ -92,3 +92,33 @@ public struct SupabaseClientOptions: Sendable { self.global = global } } + +extension SupabaseClientOptions { + #if !os(Linux) + public init( + db: DatabaseOptions = .init(), + global: GlobalOptions = .init() + ) { + self.db = db + auth = .init() + self.global = global + } + #endif +} + +extension SupabaseClientOptions.AuthOptions { + #if !os(Linux) + public init( + flowType: AuthFlowType = AuthClient.Configuration.defaultFlowType, + encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder, + decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder + ) { + self.init( + storage: AuthClient.Configuration.defaultAuthLocalStorage, + flowType: flowType, + encoder: encoder, + decoder: decoder + ) + } + #endif +} diff --git a/Sources/_Helpers/SupabaseLogger.swift b/Sources/_Helpers/SupabaseLogger.swift index d181a696..ec9c5ca5 100644 --- a/Sources/_Helpers/SupabaseLogger.swift +++ b/Sources/_Helpers/SupabaseLogger.swift @@ -25,6 +25,25 @@ public struct SupabaseLogMessage: Codable, CustomStringConvertible { public let line: UInt public let timestamp: TimeInterval + @usableFromInline + init( + system: String, + level: SupabaseLogLevel, + message: String, + fileID: String, + function: String, + line: UInt, + timestamp: TimeInterval + ) { + self.system = system + self.level = level + self.message = message + self.fileID = fileID + self.function = function + self.line = line + self.timestamp = timestamp + } + public var description: String { let date = iso8601Formatter.string(from: Date(timeIntervalSince1970: timestamp)) let file = fileID.split(separator: ".", maxSplits: 1).first.map(String.init) ?? fileID @@ -43,6 +62,7 @@ public protocol SupabaseLogger: Sendable { } extension SupabaseLogger { + @inlinable public func log( _ level: SupabaseLogLevel, message: @autoclosure () -> String, @@ -65,6 +85,7 @@ extension SupabaseLogger { ) } + @inlinable public func verbose( _ message: @autoclosure () -> String, fileID: StaticString = #fileID, @@ -74,6 +95,7 @@ extension SupabaseLogger { log(.verbose, message: message(), fileID: fileID, function: function, line: line) } + @inlinable public func debug( _ message: @autoclosure () -> String, fileID: StaticString = #fileID, @@ -83,6 +105,7 @@ extension SupabaseLogger { log(.debug, message: message(), fileID: fileID, function: function, line: line) } + @inlinable public func warning( _ message: @autoclosure () -> String, fileID: StaticString = #fileID, @@ -92,6 +115,7 @@ extension SupabaseLogger { log(.warning, message: message(), fileID: fileID, function: function, line: line) } + @inlinable public func error( _ message: @autoclosure () -> String, fileID: StaticString = #fileID, From a859df79d8da65c30e69db592efbfb392bdb7f19 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 16 Jan 2024 17:49:24 -0300 Subject: [PATCH 7/8] Revert some breaking changes and deprecate some initializers --- Sources/Auth/Deprecated.swift | 76 ++++++++++++++++++++ Sources/Auth/Storage/AuthLocalStorage.swift | 17 ++--- Sources/PostgREST/Deprecated.swift | 80 +++++++++++++++++++++ Sources/Realtime/Deprecated.swift | 67 +++++++++++++++++ Sources/Storage/Deprecated.swift | 32 +++++++++ Sources/Supabase/Types.swift | 2 +- 6 files changed, 265 insertions(+), 9 deletions(-) create mode 100644 Sources/PostgREST/Deprecated.swift create mode 100644 Sources/Realtime/Deprecated.swift create mode 100644 Sources/Storage/Deprecated.swift diff --git a/Sources/Auth/Deprecated.swift b/Sources/Auth/Deprecated.swift index 3e1721a2..73d9800b 100644 --- a/Sources/Auth/Deprecated.swift +++ b/Sources/Auth/Deprecated.swift @@ -45,3 +45,79 @@ extension JSONDecoder { AuthClient.Configuration.jsonDecoder } } + +extension AuthClient.Configuration { + /// Initializes a AuthClient Configuration with optional parameters. + /// + /// - Parameters: + /// - url: The base URL of the Auth server. + /// - headers: Custom headers to be included in requests. + /// - flowType: The authentication flow type. + /// - localStorage: The storage mechanism for local data. + /// - encoder: The JSON encoder to use for encoding requests. + /// - decoder: The JSON decoder to use for decoding responses. + /// - fetch: The asynchronous fetch handler for network requests. + @available( + *, + deprecated, + message: "Replace usages of this initializer with new init(url:headers:flowType:localStorage:logger:encoder:decoder:fetch)" + ) + public init( + url: URL, + headers: [String: String] = [:], + flowType: AuthFlowType = Self.defaultFlowType, + localStorage: AuthLocalStorage, + encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder, + decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder, + fetch: @escaping AuthClient.FetchHandler = { try await URLSession.shared.data(for: $0) } + ) { + self.init( + url: url, + headers: headers, + flowType: flowType, + localStorage: localStorage, + logger: nil, + encoder: encoder, + decoder: decoder, + fetch: fetch + ) + } +} + +extension AuthClient { + /// Initializes a AuthClient Configuration with optional parameters. + /// + /// - Parameters: + /// - url: The base URL of the Auth server. + /// - headers: Custom headers to be included in requests. + /// - flowType: The authentication flow type. + /// - localStorage: The storage mechanism for local data. + /// - encoder: The JSON encoder to use for encoding requests. + /// - decoder: The JSON decoder to use for decoding responses. + /// - fetch: The asynchronous fetch handler for network requests. + @available( + *, + deprecated, + message: "Replace usages of this initializer with new init(url:headers:flowType:localStorage:logger:encoder:decoder:fetch)" + ) + public init( + url: URL, + headers: [String: String] = [:], + flowType: AuthFlowType = Configuration.defaultFlowType, + localStorage: AuthLocalStorage, + encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder, + decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder, + fetch: @escaping AuthClient.FetchHandler = { try await URLSession.shared.data(for: $0) } + ) { + self.init( + url: url, + headers: headers, + flowType: flowType, + localStorage: localStorage, + logger: nil, + encoder: encoder, + decoder: decoder, + fetch: fetch + ) + } +} diff --git a/Sources/Auth/Storage/AuthLocalStorage.swift b/Sources/Auth/Storage/AuthLocalStorage.swift index 561e7211..2b28f7e1 100644 --- a/Sources/Auth/Storage/AuthLocalStorage.swift +++ b/Sources/Auth/Storage/AuthLocalStorage.swift @@ -7,13 +7,14 @@ public protocol AuthLocalStorage: Sendable { } extension AuthClient.Configuration { - #if os(iOS) || os(macOS) || os(watchOS) || os(tvOS) - public static let defaultAuthLocalStorage = KeychainLocalStorage( - service: "supabase.gotrue.swift", - accessGroup: nil - ) - #elseif os(Windows) - public static let defaultAuthLocalStorage = + public static let defaultLocalStorage: AuthLocalStorage = { + #if os(iOS) || os(macOS) || os(watchOS) || os(tvOS) + KeychainLocalStorage( + service: "supabase.gotrue.swift", + accessGroup: nil + ) + #elseif os(Windows) WinCredLocalStorage(service: "supabase.gotrue.swift") - #endif + #endif + }() } diff --git a/Sources/PostgREST/Deprecated.swift b/Sources/PostgREST/Deprecated.swift new file mode 100644 index 00000000..1dcd9884 --- /dev/null +++ b/Sources/PostgREST/Deprecated.swift @@ -0,0 +1,80 @@ +// +// Deprecated.swift +// +// +// Created by Guilherme Souza on 16/01/24. +// + +import Foundation + +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +extension PostgrestClient.Configuration { + /// Initializes a new configuration for the PostgREST client. + /// - Parameters: + /// - url: The URL of the PostgREST server. + /// - schema: The schema to use. + /// - headers: The headers to include in requests. + /// - fetch: The fetch handler to use for requests. + /// - encoder: The JSONEncoder to use for encoding. + /// - decoder: The JSONDecoder to use for decoding. + @available( + *, + deprecated, + message: "Replace usages of this initializer with new init(url:schema:headers:logger:fetch:encoder:decoder:)" + ) + public init( + url: URL, + schema: String? = nil, + headers: [String: String] = [:], + fetch: @escaping PostgrestClient.FetchHandler = { try await URLSession.shared.data(for: $0) }, + encoder: JSONEncoder = PostgrestClient.Configuration.jsonEncoder, + decoder: JSONDecoder = PostgrestClient.Configuration.jsonDecoder + ) { + self.init( + url: url, + schema: schema, + headers: headers, + logger: nil, + fetch: fetch, + encoder: encoder, + decoder: decoder + ) + } +} + +extension PostgrestClient { + /// Creates a PostgREST client with the specified parameters. + /// - Parameters: + /// - url: The URL of the PostgREST server. + /// - schema: The schema to use. + /// - headers: The headers to include in requests. + /// - session: The URLSession to use for requests. + /// - encoder: The JSONEncoder to use for encoding. + /// - decoder: The JSONDecoder to use for decoding. + @available( + *, + deprecated, + message: "Replace usages of this initializer with new init(url:schema:headers:logger:fetch:encoder:decoder:)" + ) + public init( + url: URL, + schema: String? = nil, + headers: [String: String] = [:], + fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }, + encoder: JSONEncoder = PostgrestClient.Configuration.jsonEncoder, + decoder: JSONDecoder = PostgrestClient.Configuration.jsonDecoder + ) { + self.init( + url: url, + schema: schema, + headers: headers, + logger: nil, + fetch: fetch, + encoder: encoder, + decoder: decoder + ) + } +} diff --git a/Sources/Realtime/Deprecated.swift b/Sources/Realtime/Deprecated.swift new file mode 100644 index 00000000..99238b03 --- /dev/null +++ b/Sources/Realtime/Deprecated.swift @@ -0,0 +1,67 @@ +// +// Deprecated.swift +// +// +// Created by Guilherme Souza on 16/01/24. +// + +import Foundation + +extension RealtimeClient { + @available( + *, + deprecated, + message: "Replace usages of this initializer with new init(_:headers:params:vsn:logger)" + ) + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public convenience init( + _ endPoint: String, + headers: [String: String] = [:], + params: Payload? = nil, + vsn: String = Defaults.vsn + ) { + self.init(endPoint, headers: headers, params: params, vsn: vsn, logger: nil) + } + + @available( + *, + deprecated, + message: "Replace usages of this initializer with new init(_:headers:paramsClosure:vsn:logger)" + ) + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public convenience init( + _ endPoint: String, + headers: [String: String] = [:], + paramsClosure: PayloadClosure?, + vsn: String = Defaults.vsn + ) { + self.init( + endPoint, + headers: headers, paramsClosure: paramsClosure, + vsn: vsn, + logger: nil + ) + } + + @available( + *, + deprecated, + message: "Replace usages of this initializer with new init(endPoint:headers:transport:paramsClosure:vsn:logger)" + ) + public convenience init( + endPoint: String, + headers: [String: String] = [:], + transport: @escaping ((URL) -> PhoenixTransport), + paramsClosure: PayloadClosure? = nil, + vsn: String = Defaults.vsn + ) { + self.init( + endPoint: endPoint, + headers: headers, + transport: transport, + paramsClosure: paramsClosure, + vsn: vsn, + logger: nil + ) + } +} diff --git a/Sources/Storage/Deprecated.swift b/Sources/Storage/Deprecated.swift new file mode 100644 index 00000000..c4ffccc6 --- /dev/null +++ b/Sources/Storage/Deprecated.swift @@ -0,0 +1,32 @@ +// +// Deprecated.swift +// +// +// Created by Guilherme Souza on 16/01/24. +// + +import Foundation + +extension StorageClientConfiguration { + @available( + *, + deprecated, + message: "Replace usages of this initializer with new init(url:headers:encoder:decoder:session:logger)" + ) + public init( + url: URL, + headers: [String: String], + encoder: JSONEncoder = .defaultStorageEncoder, + decoder: JSONDecoder = .defaultStorageDecoder, + session: StorageHTTPSession = .init() + ) { + self.init( + url: url, + headers: headers, + encoder: encoder, + decoder: decoder, + session: session, + logger: nil + ) + } +} diff --git a/Sources/Supabase/Types.swift b/Sources/Supabase/Types.swift index 29ba6734..d659d2b0 100644 --- a/Sources/Supabase/Types.swift +++ b/Sources/Supabase/Types.swift @@ -114,7 +114,7 @@ extension SupabaseClientOptions.AuthOptions { decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder ) { self.init( - storage: AuthClient.Configuration.defaultAuthLocalStorage, + storage: AuthClient.Configuration.defaultLocalStorage, flowType: flowType, encoder: encoder, decoder: decoder From c9893fa7d8abb047205c4502e8679bb96bb0e435 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 16 Jan 2024 18:40:12 -0300 Subject: [PATCH 8/8] trying to fix linux build --- Sources/Auth/Deprecated.swift | 5 +++++ Sources/Auth/Storage/AuthLocalStorage.swift | 17 ++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Sources/Auth/Deprecated.swift b/Sources/Auth/Deprecated.swift index 73d9800b..8f428c24 100644 --- a/Sources/Auth/Deprecated.swift +++ b/Sources/Auth/Deprecated.swift @@ -5,8 +5,13 @@ // Created by Guilherme Souza on 14/12/23. // +import _Helpers import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + @available(*, deprecated, renamed: "AuthClient") public typealias GoTrueClient = AuthClient diff --git a/Sources/Auth/Storage/AuthLocalStorage.swift b/Sources/Auth/Storage/AuthLocalStorage.swift index 2b28f7e1..e00234cd 100644 --- a/Sources/Auth/Storage/AuthLocalStorage.swift +++ b/Sources/Auth/Storage/AuthLocalStorage.swift @@ -7,14 +7,13 @@ public protocol AuthLocalStorage: Sendable { } extension AuthClient.Configuration { - public static let defaultLocalStorage: AuthLocalStorage = { - #if os(iOS) || os(macOS) || os(watchOS) || os(tvOS) - KeychainLocalStorage( - service: "supabase.gotrue.swift", - accessGroup: nil - ) - #elseif os(Windows) + #if os(iOS) || os(macOS) || os(watchOS) || os(tvOS) + public static let defaultLocalStorage: AuthLocalStorage = KeychainLocalStorage( + service: "supabase.gotrue.swift", + accessGroup: nil + ) + #elseif os(Windows) + public static let defaultLocalStorage: AuthLocalStorage = WinCredLocalStorage(service: "supabase.gotrue.swift") - #endif - }() + #endif }