Skip to content

Commit 42ca887

Browse files
authored
feat: Add SupabaseLogger (#219)
* Add logger type * Add SupabaseLogger * Fix tests * Add SupabaseLoggingConfiguration * Simplify logger * Inline log methods * Revert some breaking changes and deprecate some initializers
1 parent e398853 commit 42ca887

23 files changed

+550
-72
lines changed

Examples/UserManagement/Supabase.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,28 @@
66
//
77

88
import Foundation
9+
import OSLog
910
import Supabase
1011

1112
let supabase = SupabaseClient(
1213
supabaseURL: URL(string: "https://PROJECT_ID.supabase.co")!,
1314
supabaseKey: "YOUR_SUPABASE_ANON_KEY",
14-
options: .init(auth: .init(storage: KeychainLocalStorage(
15-
service: "supabase.gotrue.swift",
16-
accessGroup: nil
17-
)))
15+
options: .init(
16+
global: .init(logger: AppLogger())
17+
)
1818
)
19+
20+
struct AppLogger: SupabaseLogger {
21+
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "supabase")
22+
23+
func log(message: SupabaseLogMessage) {
24+
switch message.level {
25+
case .verbose:
26+
logger.log(level: .info, "\(message.description)")
27+
case .debug:
28+
logger.log(level: .debug, "\(message.description)")
29+
case .warning, .error:
30+
logger.log(level: .error, "\(message.description)")
31+
}
32+
}
33+
}

Package.resolved

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/Auth/AuthClient.swift

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public actor AuthClient {
1616
public var headers: [String: String]
1717
public let flowType: AuthFlowType
1818
public let localStorage: AuthLocalStorage
19+
public let logger: SupabaseLogger?
1920
public let encoder: JSONEncoder
2021
public let decoder: JSONDecoder
2122
public let fetch: FetchHandler
@@ -27,6 +28,7 @@ public actor AuthClient {
2728
/// - headers: Custom headers to be included in requests.
2829
/// - flowType: The authentication flow type.
2930
/// - localStorage: The storage mechanism for local data.
31+
/// - logger: The logger to use.
3032
/// - encoder: The JSON encoder to use for encoding requests.
3133
/// - decoder: The JSON decoder to use for decoding responses.
3234
/// - fetch: The asynchronous fetch handler for network requests.
@@ -35,6 +37,7 @@ public actor AuthClient {
3537
headers: [String: String] = [:],
3638
flowType: AuthFlowType = Configuration.defaultFlowType,
3739
localStorage: AuthLocalStorage,
40+
logger: SupabaseLogger? = nil,
3841
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
3942
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
4043
fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }
@@ -45,6 +48,7 @@ public actor AuthClient {
4548
self.headers = headers
4649
self.flowType = flowType
4750
self.localStorage = localStorage
51+
self.logger = logger
4852
self.encoder = encoder
4953
self.decoder = decoder
5054
self.fetch = fetch
@@ -75,6 +79,10 @@ public actor AuthClient {
7579
Dependencies.current.value!.currentDate
7680
}
7781

82+
private var logger: SupabaseLogger? {
83+
Dependencies.current.value!.logger
84+
}
85+
7886
/// Returns the session, refreshing it if necessary.
7987
///
8088
/// If no session can be found, a ``AuthError/sessionNotFound`` error is thrown.
@@ -94,6 +102,7 @@ public actor AuthClient {
94102
/// - headers: Custom headers to be included in requests.
95103
/// - flowType: The authentication flow type..
96104
/// - localStorage: The storage mechanism for local data..
105+
/// - logger: The logger to use.
97106
/// - encoder: The JSON encoder to use for encoding requests.
98107
/// - decoder: The JSON decoder to use for decoding responses.
99108
/// - fetch: The asynchronous fetch handler for network requests.
@@ -102,6 +111,7 @@ public actor AuthClient {
102111
headers: [String: String] = [:],
103112
flowType: AuthFlowType = AuthClient.Configuration.defaultFlowType,
104113
localStorage: AuthLocalStorage,
114+
logger: SupabaseLogger? = nil,
105115
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
106116
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
107117
fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }
@@ -112,6 +122,7 @@ public actor AuthClient {
112122
headers: headers,
113123
flowType: flowType,
114124
localStorage: localStorage,
125+
logger: logger,
115126
encoder: encoder,
116127
decoder: decoder,
117128
fetch: fetch
@@ -124,15 +135,19 @@ public actor AuthClient {
124135
/// - Parameters:
125136
/// - configuration: The client configuration.
126137
public init(configuration: Configuration) {
127-
let api = APIClient.live(http: HTTPClient(fetchHandler: configuration.fetch))
138+
let api = APIClient.live(http: HTTPClient(
139+
logger: configuration.logger,
140+
fetchHandler: configuration.fetch
141+
))
128142

129143
self.init(
130144
configuration: configuration,
131145
sessionManager: .live,
132146
codeVerifierStorage: .live,
133147
api: api,
134148
eventEmitter: .live,
135-
sessionStorage: .live
149+
sessionStorage: .live,
150+
logger: configuration.logger
136151
)
137152
}
138153

@@ -143,7 +158,8 @@ public actor AuthClient {
143158
codeVerifierStorage: CodeVerifierStorage,
144159
api: APIClient,
145160
eventEmitter: EventEmitter,
146-
sessionStorage: SessionStorage
161+
sessionStorage: SessionStorage,
162+
logger: SupabaseLogger?
147163
) {
148164
mfa = AuthMFA()
149165

@@ -159,7 +175,8 @@ public actor AuthClient {
159175
try await self?.refreshSession(refreshToken: $0) ?? .empty
160176
}
161177
),
162-
codeVerifierStorage: codeVerifierStorage
178+
codeVerifierStorage: codeVerifierStorage,
179+
logger: logger
163180
)
164181
)
165182
}
@@ -172,9 +189,11 @@ public actor AuthClient {
172189
session: Session?
173190
)> {
174191
let (id, stream) = eventEmitter.attachListener()
192+
logger?.debug("auth state change listener with id '\(id.uuidString)' attached.")
175193

176194
Task { [id] in
177195
await emitInitialSession(forStreamWithID: id)
196+
logger?.debug("initial session for listener with id '\(id.uuidString)' emitted.")
178197
}
179198

180199
return stream

Sources/Auth/Deprecated.swift

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@
55
// Created by Guilherme Souza on 14/12/23.
66
//
77

8+
import _Helpers
89
import Foundation
910

11+
#if canImport(FoundationNetworking)
12+
import FoundationNetworking
13+
#endif
14+
1015
@available(*, deprecated, renamed: "AuthClient")
1116
public typealias GoTrueClient = AuthClient
1217

@@ -45,3 +50,79 @@ extension JSONDecoder {
4550
AuthClient.Configuration.jsonDecoder
4651
}
4752
}
53+
54+
extension AuthClient.Configuration {
55+
/// Initializes a AuthClient Configuration with optional parameters.
56+
///
57+
/// - Parameters:
58+
/// - url: The base URL of the Auth server.
59+
/// - headers: Custom headers to be included in requests.
60+
/// - flowType: The authentication flow type.
61+
/// - localStorage: The storage mechanism for local data.
62+
/// - encoder: The JSON encoder to use for encoding requests.
63+
/// - decoder: The JSON decoder to use for decoding responses.
64+
/// - fetch: The asynchronous fetch handler for network requests.
65+
@available(
66+
*,
67+
deprecated,
68+
message: "Replace usages of this initializer with new init(url:headers:flowType:localStorage:logger:encoder:decoder:fetch)"
69+
)
70+
public init(
71+
url: URL,
72+
headers: [String: String] = [:],
73+
flowType: AuthFlowType = Self.defaultFlowType,
74+
localStorage: AuthLocalStorage,
75+
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
76+
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
77+
fetch: @escaping AuthClient.FetchHandler = { try await URLSession.shared.data(for: $0) }
78+
) {
79+
self.init(
80+
url: url,
81+
headers: headers,
82+
flowType: flowType,
83+
localStorage: localStorage,
84+
logger: nil,
85+
encoder: encoder,
86+
decoder: decoder,
87+
fetch: fetch
88+
)
89+
}
90+
}
91+
92+
extension AuthClient {
93+
/// Initializes a AuthClient Configuration with optional parameters.
94+
///
95+
/// - Parameters:
96+
/// - url: The base URL of the Auth server.
97+
/// - headers: Custom headers to be included in requests.
98+
/// - flowType: The authentication flow type.
99+
/// - localStorage: The storage mechanism for local data.
100+
/// - encoder: The JSON encoder to use for encoding requests.
101+
/// - decoder: The JSON decoder to use for decoding responses.
102+
/// - fetch: The asynchronous fetch handler for network requests.
103+
@available(
104+
*,
105+
deprecated,
106+
message: "Replace usages of this initializer with new init(url:headers:flowType:localStorage:logger:encoder:decoder:fetch)"
107+
)
108+
public init(
109+
url: URL,
110+
headers: [String: String] = [:],
111+
flowType: AuthFlowType = Configuration.defaultFlowType,
112+
localStorage: AuthLocalStorage,
113+
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
114+
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
115+
fetch: @escaping AuthClient.FetchHandler = { try await URLSession.shared.data(for: $0) }
116+
) {
117+
self.init(
118+
url: url,
119+
headers: headers,
120+
flowType: flowType,
121+
localStorage: localStorage,
122+
logger: nil,
123+
encoder: encoder,
124+
decoder: decoder,
125+
fetch: fetch
126+
)
127+
}
128+
}

Sources/Auth/Internal/Dependencies.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
@_spi(Internal) import _Helpers
12
import ConcurrencyExtras
23
import Foundation
34

@@ -12,4 +13,5 @@ struct Dependencies: Sendable {
1213
var sessionRefresher: SessionRefresher
1314
var codeVerifierStorage: CodeVerifierStorage
1415
var currentDate: @Sendable () -> Date = { Date() }
16+
var logger: SupabaseLogger?
1517
}

Sources/Auth/Storage/AuthLocalStorage.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,15 @@ public protocol AuthLocalStorage: Sendable {
55
func retrieve(key: String) throws -> Data?
66
func remove(key: String) throws
77
}
8+
9+
extension AuthClient.Configuration {
10+
#if os(iOS) || os(macOS) || os(watchOS) || os(tvOS)
11+
public static let defaultLocalStorage: AuthLocalStorage = KeychainLocalStorage(
12+
service: "supabase.gotrue.swift",
13+
accessGroup: nil
14+
)
15+
#elseif os(Windows)
16+
public static let defaultLocalStorage: AuthLocalStorage =
17+
WinCredLocalStorage(service: "supabase.gotrue.swift")
18+
#endif
19+
}

Sources/PostgREST/Deprecated.swift

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//
2+
// Deprecated.swift
3+
//
4+
//
5+
// Created by Guilherme Souza on 16/01/24.
6+
//
7+
8+
import Foundation
9+
10+
#if canImport(FoundationNetworking)
11+
import FoundationNetworking
12+
#endif
13+
14+
extension PostgrestClient.Configuration {
15+
/// Initializes a new configuration for the PostgREST client.
16+
/// - Parameters:
17+
/// - url: The URL of the PostgREST server.
18+
/// - schema: The schema to use.
19+
/// - headers: The headers to include in requests.
20+
/// - fetch: The fetch handler to use for requests.
21+
/// - encoder: The JSONEncoder to use for encoding.
22+
/// - decoder: The JSONDecoder to use for decoding.
23+
@available(
24+
*,
25+
deprecated,
26+
message: "Replace usages of this initializer with new init(url:schema:headers:logger:fetch:encoder:decoder:)"
27+
)
28+
public init(
29+
url: URL,
30+
schema: String? = nil,
31+
headers: [String: String] = [:],
32+
fetch: @escaping PostgrestClient.FetchHandler = { try await URLSession.shared.data(for: $0) },
33+
encoder: JSONEncoder = PostgrestClient.Configuration.jsonEncoder,
34+
decoder: JSONDecoder = PostgrestClient.Configuration.jsonDecoder
35+
) {
36+
self.init(
37+
url: url,
38+
schema: schema,
39+
headers: headers,
40+
logger: nil,
41+
fetch: fetch,
42+
encoder: encoder,
43+
decoder: decoder
44+
)
45+
}
46+
}
47+
48+
extension PostgrestClient {
49+
/// Creates a PostgREST client with the specified parameters.
50+
/// - Parameters:
51+
/// - url: The URL of the PostgREST server.
52+
/// - schema: The schema to use.
53+
/// - headers: The headers to include in requests.
54+
/// - session: The URLSession to use for requests.
55+
/// - encoder: The JSONEncoder to use for encoding.
56+
/// - decoder: The JSONDecoder to use for decoding.
57+
@available(
58+
*,
59+
deprecated,
60+
message: "Replace usages of this initializer with new init(url:schema:headers:logger:fetch:encoder:decoder:)"
61+
)
62+
public init(
63+
url: URL,
64+
schema: String? = nil,
65+
headers: [String: String] = [:],
66+
fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) },
67+
encoder: JSONEncoder = PostgrestClient.Configuration.jsonEncoder,
68+
decoder: JSONDecoder = PostgrestClient.Configuration.jsonDecoder
69+
) {
70+
self.init(
71+
url: url,
72+
schema: schema,
73+
headers: headers,
74+
logger: nil,
75+
fetch: fetch,
76+
encoder: encoder,
77+
decoder: decoder
78+
)
79+
}
80+
}

Sources/PostgREST/PostgrestBuilder.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class PostgrestBuilder: @unchecked Sendable {
2626
request: Request
2727
) {
2828
self.configuration = configuration
29-
http = HTTPClient(fetchHandler: configuration.fetch)
29+
http = HTTPClient(logger: configuration.logger, fetchHandler: configuration.fetch)
3030

3131
mutableState = LockIsolated(
3232
MutableState(
@@ -74,7 +74,7 @@ public class PostgrestBuilder: @unchecked Sendable {
7474
do {
7575
return try configuration.decoder.decode(T.self, from: data)
7676
} catch {
77-
debug("Fail to decode type '\(T.self) with error: \(error)")
77+
configuration.logger?.error("Fail to decode type '\(T.self) with error: \(error)")
7878
throw error
7979
}
8080
}

0 commit comments

Comments
 (0)