Skip to content

Commit a8ed053

Browse files
authored
feat: improve logging on token refresh logic (#410)
1 parent 24f6a76 commit a8ed053

File tree

4 files changed

+202
-95
lines changed

4 files changed

+202
-95
lines changed

Sources/Auth/Internal/SessionManager.swift

Lines changed: 74 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -32,57 +32,62 @@ private actor LiveSessionManager {
3232
private var scheduledNextRefreshTask: Task<Void, Never>?
3333

3434
func session() async throws -> Session {
35-
guard let currentSession = try storage.getSession() else {
36-
throw AuthError.sessionNotFound
37-
}
35+
try await trace(using: logger) {
36+
guard let currentSession = try storage.getSession() else {
37+
throw AuthError.sessionNotFound
38+
}
3839

39-
if !currentSession.isExpired {
40-
scheduleNextTokenRefresh(currentSession)
40+
if !currentSession.isExpired {
41+
await scheduleNextTokenRefresh(currentSession)
4142

42-
return currentSession
43-
}
43+
return currentSession
44+
}
4445

45-
return try await refreshSession(currentSession.refreshToken)
46+
return try await refreshSession(currentSession.refreshToken)
47+
}
4648
}
4749

4850
func refreshSession(_ refreshToken: String) async throws -> Session {
49-
logger?.debug("begin")
50-
defer { logger?.debug("end") }
51-
52-
if let inFlightRefreshTask {
53-
logger?.debug("refresh already in flight")
54-
return try await inFlightRefreshTask.value
55-
}
56-
57-
inFlightRefreshTask = Task {
58-
logger?.debug("refresh task started")
59-
60-
defer {
61-
inFlightRefreshTask = nil
62-
logger?.debug("refresh task ended")
51+
try await SupabaseLoggerTaskLocal.$additionalContext.withValue(
52+
merging: ["refreshID": .string(UUID().uuidString)]
53+
) {
54+
try await trace(using: logger) {
55+
if let inFlightRefreshTask {
56+
logger?.debug("refresh already in flight")
57+
return try await inFlightRefreshTask.value
58+
}
59+
60+
inFlightRefreshTask = Task {
61+
logger?.debug("refresh task started")
62+
63+
defer {
64+
inFlightRefreshTask = nil
65+
logger?.debug("refresh task ended")
66+
}
67+
68+
let session = try await api.execute(
69+
HTTPRequest(
70+
url: configuration.url.appendingPathComponent("token"),
71+
method: .post,
72+
query: [
73+
URLQueryItem(name: "grant_type", value: "refresh_token"),
74+
],
75+
body: configuration.encoder.encode(UserCredentials(refreshToken: refreshToken))
76+
)
77+
)
78+
.decoded(as: Session.self, decoder: configuration.decoder)
79+
80+
update(session)
81+
eventEmitter.emit(.tokenRefreshed, session: session)
82+
83+
await scheduleNextTokenRefresh(session)
84+
85+
return session
86+
}
87+
88+
return try await inFlightRefreshTask!.value
6389
}
64-
65-
let session = try await api.execute(
66-
HTTPRequest(
67-
url: configuration.url.appendingPathComponent("token"),
68-
method: .post,
69-
query: [
70-
URLQueryItem(name: "grant_type", value: "refresh_token"),
71-
],
72-
body: configuration.encoder.encode(UserCredentials(refreshToken: refreshToken))
73-
)
74-
)
75-
.decoded(as: Session.self, decoder: configuration.decoder)
76-
77-
update(session)
78-
eventEmitter.emit(.tokenRefreshed, session: session)
79-
80-
scheduleNextTokenRefresh(session)
81-
82-
return session
8390
}
84-
85-
return try await inFlightRefreshTask!.value
8691
}
8792

8893
func update(_ session: Session) {
@@ -101,37 +106,41 @@ private actor LiveSessionManager {
101106
}
102107
}
103108

104-
private func scheduleNextTokenRefresh(_ refreshedSession: Session, source: StaticString = #function) {
105-
logger?.debug("source: \(source)")
109+
private func scheduleNextTokenRefresh(_ refreshedSession: Session, caller: StaticString = #function) async {
110+
await SupabaseLoggerTaskLocal.$additionalContext.withValue(
111+
merging: ["caller": .string("\(caller)")]
112+
) {
113+
guard configuration.autoRefreshToken else {
114+
logger?.debug("auto refresh token disabled")
115+
return
116+
}
106117

107-
guard configuration.autoRefreshToken else {
108-
logger?.debug("auto refresh token disabled")
109-
return
110-
}
118+
guard scheduledNextRefreshTask == nil else {
119+
logger?.debug("refresh task already scheduled")
120+
return
121+
}
111122

112-
guard scheduledNextRefreshTask == nil else {
113-
logger?.debug("source: \(source) refresh task already scheduled")
114-
return
115-
}
123+
scheduledNextRefreshTask = Task {
124+
await trace(using: logger) {
125+
defer { scheduledNextRefreshTask = nil }
116126

117-
scheduledNextRefreshTask = Task {
118-
defer { scheduledNextRefreshTask = nil }
127+
let expiresAt = Date(timeIntervalSince1970: refreshedSession.expiresAt)
128+
let expiresIn = expiresAt.timeIntervalSinceNow
119129

120-
let expiresAt = Date(timeIntervalSince1970: refreshedSession.expiresAt)
121-
let expiresIn = expiresAt.timeIntervalSinceNow
130+
// if expiresIn < 0, it will refresh right away.
131+
let timeToRefresh = max(expiresIn * 0.9, 0)
122132

123-
// if expiresIn < 0, it will refresh right away.
124-
let timeToRefresh = max(expiresIn * 0.9, 0)
133+
logger?.debug("scheduled next token refresh in: \(timeToRefresh)s")
125134

126-
logger?.debug("source: \(source) scheduled next token refresh in: \(timeToRefresh)s")
135+
try? await Task.sleep(nanoseconds: NSEC_PER_SEC * UInt64(timeToRefresh))
127136

128-
try? await Task.sleep(nanoseconds: NSEC_PER_SEC * UInt64(timeToRefresh))
137+
if Task.isCancelled {
138+
return
139+
}
129140

130-
if Task.isCancelled {
131-
return
141+
_ = try? await refreshSession(refreshedSession.refreshToken)
142+
}
132143
}
133-
134-
_ = try? await refreshSession(refreshedSession.refreshToken)
135144
}
136145
}
137146
}

Sources/_Helpers/HTTP/LoggerInterceptor.swift

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,30 @@ package struct LoggerInterceptor: HTTPClientInterceptor {
1919
next: @Sendable (HTTPRequest) async throws -> HTTPResponse
2020
) async throws -> HTTPResponse {
2121
let id = UUID().uuidString
22-
logger.verbose(
23-
"""
24-
Request [\(id)]: \(request.method.rawValue) \(request.url.absoluteString
25-
.removingPercentEncoding ?? "")
26-
Body: \(stringfy(request.body))
27-
"""
28-
)
29-
30-
do {
31-
let response = try await next(request)
22+
return try await SupabaseLoggerTaskLocal.$additionalContext.withValue(merging: ["requestID": .string(id)]) {
3223
logger.verbose(
3324
"""
34-
Response [\(id)]: Status code: \(response.statusCode) Content-Length: \(
35-
response.underlyingResponse.expectedContentLength
36-
)
37-
Body: \(stringfy(response.data))
25+
Request: \(request.method.rawValue) \(request.url.absoluteString
26+
.removingPercentEncoding ?? "")
27+
Body: \(stringfy(request.body))
3828
"""
3929
)
40-
return response
41-
} catch {
42-
logger.error("Response [\(id)]: Failure \(error)")
43-
throw error
30+
31+
do {
32+
let response = try await next(request)
33+
logger.verbose(
34+
"""
35+
Response: Status code: \(response.statusCode) Content-Length: \(
36+
response.underlyingResponse.expectedContentLength
37+
)
38+
Body: \(stringfy(response.data))
39+
"""
40+
)
41+
return response
42+
} catch {
43+
logger.error("Response: Failure \(error)")
44+
throw error
45+
}
4446
}
4547
}
4648
}

Sources/_Helpers/SupabaseLogger.swift

Lines changed: 82 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ public enum SupabaseLogLevel: Int, Codable, CustomStringConvertible, Sendable {
1616
}
1717
}
1818

19+
@usableFromInline
20+
package enum SupabaseLoggerTaskLocal {
21+
@TaskLocal
22+
@usableFromInline
23+
package static var additionalContext: JSONObject = [:]
24+
}
25+
1926
public struct SupabaseLogMessage: Codable, CustomStringConvertible, Sendable {
2027
public let system: String
2128
public let level: SupabaseLogLevel
@@ -24,6 +31,7 @@ public struct SupabaseLogMessage: Codable, CustomStringConvertible, Sendable {
2431
public let function: String
2532
public let line: UInt
2633
public let timestamp: TimeInterval
34+
public let additionalContext: JSONObject
2735

2836
@usableFromInline
2937
init(
@@ -33,7 +41,8 @@ public struct SupabaseLogMessage: Codable, CustomStringConvertible, Sendable {
3341
fileID: String,
3442
function: String,
3543
line: UInt,
36-
timestamp: TimeInterval
44+
timestamp: TimeInterval,
45+
additionalContext: JSONObject
3746
) {
3847
self.system = system
3948
self.level = level
@@ -42,12 +51,17 @@ public struct SupabaseLogMessage: Codable, CustomStringConvertible, Sendable {
4251
self.function = function
4352
self.line = line
4453
self.timestamp = timestamp
54+
self.additionalContext = additionalContext
4555
}
4656

4757
public var description: String {
4858
let date = iso8601Formatter.string(from: Date(timeIntervalSince1970: timestamp))
4959
let file = fileID.split(separator: ".", maxSplits: 1).first.map(String.init) ?? fileID
50-
return "\(date) [\(level)] [\(system)] [\(file).\(function):\(line)] \(message)"
60+
var description = "\(date) [\(level)] [\(system)] [\(file).\(function):\(line)] \(message)"
61+
if !additionalContext.isEmpty {
62+
description += "\ncontext: \(additionalContext.description)"
63+
}
64+
return description
5165
}
5266
}
5367

@@ -68,7 +82,8 @@ extension SupabaseLogger {
6882
message: @autoclosure () -> String,
6983
fileID: StaticString = #fileID,
7084
function: StaticString = #function,
71-
line: UInt = #line
85+
line: UInt = #line,
86+
additionalContext: JSONObject = [:]
7287
) {
7388
let system = "\(fileID)".split(separator: "/").first ?? ""
7489

@@ -80,7 +95,11 @@ extension SupabaseLogger {
8095
fileID: "\(fileID)",
8196
function: "\(function)",
8297
line: line,
83-
timestamp: Date().timeIntervalSince1970
98+
timestamp: Date().timeIntervalSince1970,
99+
additionalContext: additionalContext.merging(
100+
SupabaseLoggerTaskLocal.additionalContext,
101+
uniquingKeysWith: { _, new in new }
102+
)
84103
)
85104
)
86105
}
@@ -90,38 +109,89 @@ extension SupabaseLogger {
90109
_ message: @autoclosure () -> String,
91110
fileID: StaticString = #fileID,
92111
function: StaticString = #function,
93-
line: UInt = #line
112+
line: UInt = #line,
113+
additionalContext: JSONObject = [:]
94114
) {
95-
log(.verbose, message: message(), fileID: fileID, function: function, line: line)
115+
log(
116+
.verbose,
117+
message: message(),
118+
fileID: fileID,
119+
function: function,
120+
line: line,
121+
additionalContext: additionalContext
122+
)
96123
}
97124

98125
@inlinable
99126
public func debug(
100127
_ message: @autoclosure () -> String,
101128
fileID: StaticString = #fileID,
102129
function: StaticString = #function,
103-
line: UInt = #line
130+
line: UInt = #line,
131+
additionalContext: JSONObject = [:]
104132
) {
105-
log(.debug, message: message(), fileID: fileID, function: function, line: line)
133+
log(
134+
.debug,
135+
message: message(),
136+
fileID: fileID,
137+
function: function,
138+
line: line,
139+
additionalContext: additionalContext
140+
)
106141
}
107142

108143
@inlinable
109144
public func warning(
110145
_ message: @autoclosure () -> String,
111146
fileID: StaticString = #fileID,
112147
function: StaticString = #function,
113-
line: UInt = #line
148+
line: UInt = #line,
149+
additionalContext: JSONObject = [:]
114150
) {
115-
log(.warning, message: message(), fileID: fileID, function: function, line: line)
151+
log(
152+
.warning,
153+
message: message(),
154+
fileID: fileID,
155+
function: function,
156+
line: line,
157+
additionalContext: additionalContext
158+
)
116159
}
117160

118161
@inlinable
119162
public func error(
120163
_ message: @autoclosure () -> String,
121164
fileID: StaticString = #fileID,
122165
function: StaticString = #function,
123-
line: UInt = #line
166+
line: UInt = #line,
167+
additionalContext: JSONObject = [:]
124168
) {
125-
log(.error, message: message(), fileID: fileID, function: function, line: line)
169+
log(
170+
.error,
171+
message: message(),
172+
fileID: fileID,
173+
function: function,
174+
line: line,
175+
additionalContext: additionalContext
176+
)
177+
}
178+
}
179+
180+
@inlinable
181+
package func trace<R>(
182+
using logger: (any SupabaseLogger)?,
183+
@_inheritActorContext _ operation: @Sendable () async throws -> R,
184+
fileID: StaticString = #fileID,
185+
function: StaticString = #function,
186+
line: UInt = #line
187+
) async rethrows -> R {
188+
logger?.debug("begin", fileID: fileID, function: function, line: line)
189+
defer { logger?.debug("end", fileID: fileID, function: function, line: line) }
190+
191+
do {
192+
return try await operation()
193+
} catch {
194+
logger?.debug("error: \(error)", fileID: fileID, function: function, line: line)
195+
throw error
126196
}
127197
}

0 commit comments

Comments
 (0)