diff --git a/Package.swift b/Package.swift index b47e552ce..50b782517 100644 --- a/Package.swift +++ b/Package.swift @@ -23,6 +23,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "4.0.0"), + .package(url: "https://github.com/apple/swift-log", from: "1.6.1"), .package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.1.0"), .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.2"), .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.17.2"), @@ -32,6 +33,7 @@ let package = Package( .target( name: "Helpers", dependencies: [ + .product(name: "Logging", package: "swift-log"), .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), ] ), @@ -47,6 +49,7 @@ let package = Package( dependencies: [ .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), .product(name: "Crypto", package: "swift-crypto"), + .product(name: "Logging", package: "swift-log"), "Helpers", ] ), @@ -66,7 +69,13 @@ let package = Package( ], resources: [.process("Resources")] ), - .target(name: "Functions", dependencies: ["Helpers"]), + .target( + name: "Functions", + dependencies: [ + "Helpers", + .product(name: "Logging", package: "swift-log"), + ] + ), .testTarget( name: "FunctionsTests", dependencies: [ @@ -97,6 +106,7 @@ let package = Package( name: "PostgREST", dependencies: [ .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), + .product(name: "Logging", package: "swift-log"), "Helpers", ] ), @@ -113,6 +123,7 @@ let package = Package( name: "Realtime", dependencies: [ .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), + .product(name: "Logging", package: "swift-log"), "Helpers", ] ), @@ -130,6 +141,7 @@ let package = Package( .target( name: "Storage", dependencies: [ + .product(name: "Logging", package: "swift-log"), "Helpers", ] ), @@ -146,6 +158,7 @@ let package = Package( dependencies: [ .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), .product(name: "IssueReporting", package: "xctest-dynamic-overlay"), + .product(name: "Logging", package: "swift-log"), "Auth", "Functions", "PostgREST", diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index 712990568..56e0fbab9 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -1,6 +1,7 @@ import ConcurrencyExtras import Foundation import Helpers +import Logging #if canImport(AuthenticationServices) import AuthenticationServices @@ -12,6 +13,8 @@ import Helpers typealias AuthClientID = UUID +let log = Logger(label: "supabase.auth") + public final class AuthClient: Sendable { let clientID = AuthClientID() @@ -452,6 +455,7 @@ public final class AuthClient: Sendable { let codeVerifier = codeVerifierStorage.get() if codeVerifier == nil { + log.error("code verifier not found, a code verifier should exist when calling this method.") logger?.error("code verifier not found, a code verifier should exist when calling this method.") } @@ -662,6 +666,7 @@ public final class AuthClient: Sendable { do { try await session(from: url) } catch { + log.error("Failure loading session from url '\(url)' error: \(error)") logger?.error("Failure loading session from url '\(url)' error: \(error)") } } @@ -670,6 +675,7 @@ public final class AuthClient: Sendable { /// Gets the session data from a OAuth2 callback URL. @discardableResult public func session(from url: URL) async throws -> Session { + log.debug("received \(url)") logger?.debug("received \(url)") let params = extractParams(from: url) diff --git a/Sources/Auth/AuthClientConfiguration.swift b/Sources/Auth/AuthClientConfiguration.swift index 4b724a0a1..896c84e22 100644 --- a/Sources/Auth/AuthClientConfiguration.swift +++ b/Sources/Auth/AuthClientConfiguration.swift @@ -28,6 +28,7 @@ extension AuthClient { /// Optional key name used for storing tokens in local storage. public var storageKey: String? public let localStorage: any AuthLocalStorage + @available(*, deprecated, message: "SupabaseLogger is deprecated in favor of Logging from apple/swift-log") public let logger: (any SupabaseLogger)? public let encoder: JSONEncoder public let decoder: JSONDecoder diff --git a/Sources/Auth/Internal/APIClient.swift b/Sources/Auth/Internal/APIClient.swift index bab98fe07..ceb874d0d 100644 --- a/Sources/Auth/Internal/APIClient.swift +++ b/Sources/Auth/Internal/APIClient.swift @@ -5,7 +5,7 @@ extension HTTPClient { init(configuration: AuthClient.Configuration) { var interceptors: [any HTTPClientInterceptor] = [] if let logger = configuration.logger { - interceptors.append(LoggerInterceptor(logger: logger)) + interceptors.append(LoggerInterceptor(logger: logger, log: log)) } interceptors.append( @@ -35,8 +35,8 @@ struct APIClient: Sendable { var request = request request.headers = HTTPHeaders(configuration.headers).merged(with: request.headers) - if request.headers[API_VERSION_HEADER_NAME] == nil { - request.headers[API_VERSION_HEADER_NAME] = API_VERSIONS[._20240101]!.name.rawValue + if request.headers[apiVersionHeaderName] == nil { + request.headers[apiVersionHeaderName] = apiVersions[._20240101]!.name.rawValue } let response = try await http.send(request) @@ -77,7 +77,7 @@ struct APIClient: Sendable { let responseAPIVersion = parseResponseAPIVersion(response) - let errorCode: ErrorCode? = if let responseAPIVersion, responseAPIVersion >= API_VERSIONS[._20240101]!.timestamp, let code = error.code { + let errorCode: ErrorCode? = if let responseAPIVersion, responseAPIVersion >= apiVersions[._20240101]!.timestamp, let code = error.code { ErrorCode(code) } else { error.errorCode @@ -106,7 +106,7 @@ struct APIClient: Sendable { } private func parseResponseAPIVersion(_ response: HTTPResponse) -> Date? { - guard let apiVersion = response.headers[API_VERSION_HEADER_NAME] else { return nil } + guard let apiVersion = response.headers[apiVersionHeaderName] else { return nil } let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] diff --git a/Sources/Auth/Internal/CodeVerifierStorage.swift b/Sources/Auth/Internal/CodeVerifierStorage.swift index 1df1e2c2e..3d3bb974a 100644 --- a/Sources/Auth/Internal/CodeVerifierStorage.swift +++ b/Sources/Auth/Internal/CodeVerifierStorage.swift @@ -10,17 +10,19 @@ struct CodeVerifierStorage: Sendable { extension CodeVerifierStorage { static func live(clientID: AuthClientID) -> Self { var configuration: AuthClient.Configuration { Dependencies[clientID].configuration } - var key: String { "\(configuration.storageKey ?? STORAGE_KEY)-code-verifier" } + var key: String { "\(configuration.storageKey ?? defaultStorageKey)-code-verifier" } return Self( get: { do { guard let data = try configuration.localStorage.retrieve(key: key) else { + log.debug("Code verifier not found.") configuration.logger?.debug("Code verifier not found.") return nil } return String(decoding: data, as: UTF8.self) } catch { + log.error("Failure loading code verifier: \(error.localizedDescription)") configuration.logger?.error("Failure loading code verifier: \(error.localizedDescription)") return nil } @@ -32,9 +34,11 @@ extension CodeVerifierStorage { } else if code == nil { try configuration.localStorage.remove(key: key) } else { + log.error("Code verifier is not a valid UTF8 string.") configuration.logger?.error("Code verifier is not a valid UTF8 string.") } } catch { + log.error("Failure storing code verifier: \(error.localizedDescription)") configuration.logger?.error("Failure storing code verifier: \(error.localizedDescription)") } } diff --git a/Sources/Auth/Internal/Contants.swift b/Sources/Auth/Internal/Contants.swift index de3881975..087dd8d27 100644 --- a/Sources/Auth/Internal/Contants.swift +++ b/Sources/Auth/Internal/Contants.swift @@ -7,11 +7,11 @@ import Foundation -let EXPIRY_MARGIN: TimeInterval = 30 -let STORAGE_KEY = "supabase.auth.token" +let defaultExpiryMargin: TimeInterval = 30 +let defaultStorageKey = "supabase.auth.token" -let API_VERSION_HEADER_NAME = "X-Supabase-Api-Version" -let API_VERSIONS: [APIVersion.Name: APIVersion] = [ +let apiVersionHeaderName = "X-Supabase-Api-Version" +let apiVersions: [APIVersion.Name: APIVersion] = [ ._20240101: ._20240101, ] diff --git a/Sources/Auth/Internal/EventEmitter.swift b/Sources/Auth/Internal/EventEmitter.swift index 53b66b766..20e9738bb 100644 --- a/Sources/Auth/Internal/EventEmitter.swift +++ b/Sources/Auth/Internal/EventEmitter.swift @@ -11,6 +11,7 @@ struct AuthStateChangeEventEmitter { guard let event else { return } listener(event.0, event.1) + log.debug("Auth state changed: \(event)") logger?.verbose("Auth state changed: \(event)") } } diff --git a/Sources/Auth/Internal/SessionManager.swift b/Sources/Auth/Internal/SessionManager.swift index 2f07f47c1..155263745 100644 --- a/Sources/Auth/Internal/SessionManager.swift +++ b/Sources/Auth/Internal/SessionManager.swift @@ -59,15 +59,18 @@ private actor LiveSessionManager { ) { try await trace(using: logger) { if let inFlightRefreshTask { + log.debug("refresh already in flight") logger?.debug("refresh already in flight") return try await inFlightRefreshTask.value } inFlightRefreshTask = Task { + log.debug("refresh task started") logger?.debug("refresh task started") defer { inFlightRefreshTask = nil + log.debug("refresh task ended") logger?.debug("refresh task ended") } @@ -100,6 +103,7 @@ private actor LiveSessionManager { do { try sessionStorage.store(session) } catch { + log.error("Failed to store session: \(error)") logger?.error("Failed to store session: \(error)") } } @@ -108,6 +112,7 @@ private actor LiveSessionManager { do { try sessionStorage.delete() } catch { + log.error("Failed to remove session: \(error)") logger?.error("Failed to remove session: \(error)") } } @@ -117,11 +122,13 @@ private actor LiveSessionManager { merging: ["caller": .string("\(caller)")] ) { guard configuration.autoRefreshToken else { + log.debug("auto refresh token disabled") logger?.debug("auto refresh token disabled") return } guard scheduledNextRefreshTask == nil else { + log.debug("refresh task already scheduled") logger?.debug("refresh task already scheduled") return } @@ -136,6 +143,7 @@ private actor LiveSessionManager { // if expiresIn < 0, it will refresh right away. let timeToRefresh = max(expiresIn * 0.9, 0) + log.debug("scheduled next token refresh in: \(timeToRefresh)s") logger?.debug("scheduled next token refresh in: \(timeToRefresh)s") try? await Task.sleep(nanoseconds: NSEC_PER_SEC * UInt64(timeToRefresh)) diff --git a/Sources/Auth/Internal/SessionStorage.swift b/Sources/Auth/Internal/SessionStorage.swift index 79bde470a..49e0d6828 100644 --- a/Sources/Auth/Internal/SessionStorage.swift +++ b/Sources/Auth/Internal/SessionStorage.swift @@ -19,7 +19,7 @@ extension SessionStorage { /// /// It uses value from ``AuthClient/Configuration/storageKey`` or default to `supabase.auth.token` if not provided. static func key(_ clientID: AuthClientID) -> String { - Dependencies[clientID].configuration.storageKey ?? STORAGE_KEY + Dependencies[clientID].configuration.storageKey ?? defaultStorageKey } static func live(clientID: AuthClientID) -> SessionStorage { diff --git a/Sources/Auth/Types.swift b/Sources/Auth/Types.swift index d850b5424..979a59730 100644 --- a/Sources/Auth/Types.swift +++ b/Sources/Auth/Types.swift @@ -114,7 +114,7 @@ public struct Session: Codable, Hashable, Sendable { /// The 30 second buffer is to account for latency issues. public var isExpired: Bool { let expiresAt = Date(timeIntervalSince1970: expiresAt) - return expiresAt.timeIntervalSinceNow < EXPIRY_MARGIN + return expiresAt.timeIntervalSinceNow < defaultExpiryMargin } } diff --git a/Sources/Functions/FunctionsClient.swift b/Sources/Functions/FunctionsClient.swift index f4b95638e..58ccca54e 100644 --- a/Sources/Functions/FunctionsClient.swift +++ b/Sources/Functions/FunctionsClient.swift @@ -1,6 +1,7 @@ import ConcurrencyExtras import Foundation import Helpers +import Logging #if canImport(FoundationNetworking) import FoundationNetworking @@ -8,6 +9,8 @@ import Helpers let version = Helpers.version +let log = Logger(label: "supabase.functions") + /// An actor representing a client for invoking functions. public final class FunctionsClient: Sendable { /// Fetch handler used to make requests. @@ -52,7 +55,7 @@ public final class FunctionsClient: Sendable { ) { var interceptors: [any HTTPClientInterceptor] = [] if let logger { - interceptors.append(LoggerInterceptor(logger: logger)) + interceptors.append(LoggerInterceptor(logger: logger, log: log)) } let http = HTTPClient(fetch: fetch, interceptors: interceptors) diff --git a/Sources/Helpers/HTTP/HTTPClient.swift b/Sources/Helpers/HTTP/HTTPClient.swift index 164463037..2ac9b208d 100644 --- a/Sources/Helpers/HTTP/HTTPClient.swift +++ b/Sources/Helpers/HTTP/HTTPClient.swift @@ -6,6 +6,7 @@ // import Foundation +import Logging #if canImport(FoundationNetworking) import FoundationNetworking diff --git a/Sources/Helpers/HTTP/HTTPResponse.swift b/Sources/Helpers/HTTP/HTTPResponse.swift index fdb61ad9b..745fdd0c5 100644 --- a/Sources/Helpers/HTTP/HTTPResponse.swift +++ b/Sources/Helpers/HTTP/HTTPResponse.swift @@ -27,7 +27,10 @@ package struct HTTPResponse: Sendable { } extension HTTPResponse { - package func decoded(as _: T.Type = T.self, decoder: JSONDecoder = JSONDecoder()) throws -> T { + package func decoded( + as _: T.Type = T.self, + decoder: JSONDecoder = JSONDecoder() + ) throws -> T { try decoder.decode(T.self, from: data) } } diff --git a/Sources/Helpers/HTTP/LoggerInterceptor.swift b/Sources/Helpers/HTTP/LoggerInterceptor.swift index e58819535..07c064aae 100644 --- a/Sources/Helpers/HTTP/LoggerInterceptor.swift +++ b/Sources/Helpers/HTTP/LoggerInterceptor.swift @@ -6,12 +6,15 @@ // import Foundation +import Logging package struct LoggerInterceptor: HTTPClientInterceptor { let logger: any SupabaseLogger + let log: Logger - package init(logger: any SupabaseLogger) { + package init(logger: any SupabaseLogger, log: Logger) { self.logger = logger + self.log = log } package func intercept( @@ -28,9 +31,23 @@ package struct LoggerInterceptor: HTTPClientInterceptor { Body: \(stringfy(request.body)) """ ) + log.trace( + """ + Request: \(urlRequest.httpMethod ?? "") \(urlRequest.url?.absoluteString.removingPercentEncoding ?? "") + Body: \(stringfy(request.body)) + """ + ) do { let response = try await next(request) + log.trace( + """ + Response: Status code: \(response.statusCode) Content-Length: \( + response.underlyingResponse.expectedContentLength + ) + Body: \(stringfy(response.data)) + """ + ) logger.verbose( """ Response: Status code: \(response.statusCode) Content-Length: \( @@ -41,6 +58,7 @@ package struct LoggerInterceptor: HTTPClientInterceptor { ) return response } catch { + log.error("Response: Failure \(error)") logger.error("Response: Failure \(error)") throw error } diff --git a/Sources/Helpers/SupabaseLogger.swift b/Sources/Helpers/SupabaseLogger.swift index 0352a334e..5f3d5ae57 100644 --- a/Sources/Helpers/SupabaseLogger.swift +++ b/Sources/Helpers/SupabaseLogger.swift @@ -1,5 +1,6 @@ import Foundation +@available(*, deprecated, message: "SupabaseLogger is deprecated in favor of Logging from apple/swift-log") public enum SupabaseLogLevel: Int, Codable, CustomStringConvertible, Sendable { case verbose case debug @@ -23,6 +24,7 @@ package enum SupabaseLoggerTaskLocal { package static var additionalContext: JSONObject = [:] } +@available(*, deprecated, message: "SupabaseLogger is deprecated in favor of Logging from apple/swift-log") public struct SupabaseLogMessage: Codable, CustomStringConvertible, Sendable { public let system: String public let level: SupabaseLogLevel @@ -65,6 +67,7 @@ public struct SupabaseLogMessage: Codable, CustomStringConvertible, Sendable { } } +@available(*, deprecated, message: "SupabaseLogger is deprecated in favor of Logging from apple/swift-log") public protocol SupabaseLogger: Sendable { func log(message: SupabaseLogMessage) } diff --git a/Sources/PostgREST/PostgrestBuilder.swift b/Sources/PostgREST/PostgrestBuilder.swift index 2b38ae324..7e35a903e 100644 --- a/Sources/PostgREST/PostgrestBuilder.swift +++ b/Sources/PostgREST/PostgrestBuilder.swift @@ -29,7 +29,7 @@ public class PostgrestBuilder: @unchecked Sendable { var interceptors: [any HTTPClientInterceptor] = [] if let logger = configuration.logger { - interceptors.append(LoggerInterceptor(logger: logger)) + interceptors.append(LoggerInterceptor(logger: logger, log: log)) } http = HTTPClient(fetch: configuration.fetch, interceptors: interceptors) diff --git a/Sources/PostgREST/PostgrestClient.swift b/Sources/PostgREST/PostgrestClient.swift index 4fb1b5ba1..84b0b609d 100644 --- a/Sources/PostgREST/PostgrestClient.swift +++ b/Sources/PostgREST/PostgrestClient.swift @@ -1,6 +1,7 @@ import ConcurrencyExtras import Foundation import Helpers +import Logging public typealias PostgrestError = Helpers.PostgrestError public typealias HTTPError = Helpers.HTTPError @@ -10,6 +11,8 @@ public typealias AnyJSON = Helpers.AnyJSON import FoundationNetworking #endif +let log = Logger(label: "supabase.postgrest") + /// PostgREST client. public final class PostgrestClient: Sendable { public typealias FetchHandler = @Sendable (_ request: URLRequest) async throws -> ( diff --git a/Sources/Realtime/V2/RealtimeClientV2.swift b/Sources/Realtime/V2/RealtimeClientV2.swift index 8f09e2a85..4b247299b 100644 --- a/Sources/Realtime/V2/RealtimeClientV2.swift +++ b/Sources/Realtime/V2/RealtimeClientV2.swift @@ -8,11 +8,14 @@ import ConcurrencyExtras import Foundation import Helpers +import Logging #if canImport(FoundationNetworking) import FoundationNetworking #endif +let log = Logger(label: "supabase.realtime") + public typealias JSONObject = Helpers.JSONObject public final class RealtimeClientV2: Sendable { @@ -132,7 +135,7 @@ public final class RealtimeClientV2: Sendable { var interceptors: [any HTTPClientInterceptor] = [] if let logger = options.logger { - interceptors.append(LoggerInterceptor(logger: logger)) + interceptors.append(LoggerInterceptor(logger: logger, log: log)) } self.init( diff --git a/Sources/Storage/StorageApi.swift b/Sources/Storage/StorageApi.swift index a635b1a4f..035c38cba 100644 --- a/Sources/Storage/StorageApi.swift +++ b/Sources/Storage/StorageApi.swift @@ -1,10 +1,13 @@ import Foundation import Helpers +import Logging #if canImport(FoundationNetworking) import FoundationNetworking #endif +let log = Logger(label: "supabase.storage") + public class StorageApi: @unchecked Sendable { public let configuration: StorageClientConfiguration @@ -19,7 +22,7 @@ public class StorageApi: @unchecked Sendable { var interceptors: [any HTTPClientInterceptor] = [] if let logger = configuration.logger { - interceptors.append(LoggerInterceptor(logger: logger)) + interceptors.append(LoggerInterceptor(logger: logger, log: log)) } http = HTTPClient( diff --git a/Sources/Supabase/SupabaseClient.swift b/Sources/Supabase/SupabaseClient.swift index 65218754c..ee7eceb89 100644 --- a/Sources/Supabase/SupabaseClient.swift +++ b/Sources/Supabase/SupabaseClient.swift @@ -4,6 +4,7 @@ import Foundation @_exported import Functions import Helpers import IssueReporting +import Logging @_exported import PostgREST @_exported import Realtime @_exported import Storage @@ -18,6 +19,8 @@ public typealias SupabaseLogMessage = Helpers.SupabaseLogMessage let version = Helpers.version +let logger = Logger(label: "supabase") + /// Supabase Client. public final class SupabaseClient: Sendable { let options: SupabaseClientOptions @@ -145,6 +148,8 @@ public final class SupabaseClient: Sendable { supabaseKey: String, options: SupabaseClientOptions ) { + logger.info("SupabaseClient initialized") + self.supabaseURL = supabaseURL self.supabaseKey = supabaseKey self.options = options diff --git a/Supabase.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Supabase.xcworkspace/xcshareddata/swiftpm/Package.resolved index a92350636..68d62e449 100644 --- a/Supabase.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Supabase.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -36,15 +36,6 @@ "version" : "4.1.1" } }, - { - "identity" : "multipartformdata", - "kind" : "remoteSourceControl", - "location" : "https://github.com/grdsdev/MultipartFormData", - "state" : { - "revision" : "ed7abea9cfc6c3b5e77a73fe6842c57a372d2017", - "version" : "0.1.0" - } - }, { "identity" : "svgview", "kind" : "remoteSourceControl", @@ -108,6 +99,15 @@ "version" : "1.1.0" } }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log", + "state" : { + "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", + "version" : "1.6.1" + } + }, { "identity" : "swift-snapshot-testing", "kind" : "remoteSourceControl",