Skip to content

Commit 9822407

Browse files
author
Guilherme Souza
committed
Implement log backup
1 parent 9f102ea commit 9822407

File tree

3 files changed

+114
-16
lines changed

3 files changed

+114
-16
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import PackageDescription
55

66
let package = Package(
77
name: "swift-log-supabase",
8-
platforms: [.iOS(.v13), .macOS(.v10_15)],
8+
platforms: [.iOS(.v15), .macOS(.v12)],
99
products: [
1010
.library(
1111
name: "SupabaseLogger",
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import Foundation
2+
3+
actor LogsCache {
4+
5+
private let maximumNumberOfLogsToPopAtOnce = 100
6+
7+
private var cachedLogs: [[String: Any]] = []
8+
9+
func push(_ log: [String: Any]) {
10+
cachedLogs.append(log)
11+
}
12+
13+
func push(_ logs: [[String: Any]]) {
14+
cachedLogs.append(contentsOf: logs)
15+
}
16+
17+
func pop() -> [[String: Any]] {
18+
let sliceSize = min(maximumNumberOfLogsToPopAtOnce, cachedLogs.count)
19+
let poppedLogs = Array(cachedLogs[..<sliceSize])
20+
cachedLogs.removeFirst(sliceSize)
21+
return poppedLogs
22+
}
23+
24+
func backupCache() {
25+
do {
26+
let data = try JSONSerialization.data(withJSONObject: cachedLogs)
27+
try data.write(to: LogsCache.fileURL())
28+
self.cachedLogs = []
29+
} catch {
30+
print("Error saving Logs cache.")
31+
}
32+
}
33+
34+
private static func fileURL() -> URL {
35+
try! FileManager.default.url(
36+
for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false
37+
)
38+
.appendingPathComponent("supabase-log-cache")
39+
}
40+
41+
static let shared = LogsCache()
42+
43+
private init() {
44+
do {
45+
let data = try Data(contentsOf: LogsCache.fileURL())
46+
try FileManager.default.removeItem(at: LogsCache.fileURL())
47+
48+
let logs = try JSONSerialization.jsonObject(with: data) as? [[String: Any]] ?? []
49+
self.cachedLogs = logs
50+
} catch {
51+
print("Error recovering logs from cache.")
52+
}
53+
}
54+
}

Sources/SupabaseLogger/SupabaseLogHandler.swift

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import Foundation
22
import Logging
33

4+
#if os(iOS)
5+
import UIKit
6+
#elseif os(macOS)
7+
import AppKit
8+
#endif
9+
410
public struct SupabaseLogConfig {
511
let supabaseURL: String
612
let supabaseAnonKey: String
@@ -59,23 +65,40 @@ public struct SupabaseLogHandler: LogHandler {
5965

6066
final class SupabaseLogManager {
6167

62-
let queue = DispatchQueue(label: "co.binaryscraping.supabase-log-manager", qos: .background)
63-
var payloads: [[String: Any]] = []
68+
let cache: LogsCache
6469
let config: SupabaseLogConfig
6570

6671
init(config: SupabaseLogConfig) {
6772
self.config = config
73+
self.cache = LogsCache.shared
74+
75+
#if os(macOS)
76+
NotificationCenter.default.addObserver(
77+
self, selector: #selector(appWillTerminate), name: NSApplication.willTerminateNotification,
78+
object: nil)
79+
#elseif os(iOS)
80+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
81+
// We need to use a delay with these type of notifications because they fire on app load which causes a double load of the cache from disk
82+
NotificationCenter.default.addObserver(
83+
self, selector: #selector(self.didEnterForeground),
84+
name: UIApplication.willEnterForegroundNotification, object: nil)
85+
NotificationCenter.default.addObserver(
86+
self, selector: #selector(self.didEnterBackground),
87+
name: UIApplication.didEnterBackgroundNotification, object: nil)
88+
}
89+
#endif
6890
}
6991

7092
func log(_ payload: [String: Any]) {
71-
queue.async {
72-
self.payloads.append(payload)
73-
}
93+
Task { await cache.push(payload) }
7494
}
7595

76-
func uploadLogs() {
77-
queue.async {
78-
let data = try! JSONSerialization.data(withJSONObject: self.payloads)
96+
private func checkForLogsAndSend() {
97+
Task {
98+
let logs = await cache.pop()
99+
if logs.isEmpty { return }
100+
101+
let data = try! JSONSerialization.data(withJSONObject: logs)
79102
guard
80103
let url = URL(string: self.config.supabaseURL)?.appendingPathComponent(self.config.table)
81104
else {
@@ -86,14 +109,35 @@ final class SupabaseLogManager {
86109
request.httpMethod = "POST"
87110
request.httpBody = data
88111

89-
URLSession.shared.dataTask(with: request) { data, response, error in
90-
if let error = error {
91-
print(error)
92-
return
93-
}
112+
do {
113+
let (_, response) = try await URLSession.shared.data(for: request)
114+
if let httpResponse = response as? HTTPURLResponse {
115+
if 200..<300 ~= httpResponse.statusCode {
116+
return
117+
}
94118

95-
self.payloads = []
96-
}.resume()
119+
await cache.push(logs)
120+
}
121+
} catch {
122+
await cache.push(logs)
123+
}
97124
}
98125
}
99126
}
127+
128+
extension SupabaseLogManager {
129+
@objc func appWillTerminate() {
130+
Task { await cache.backupCache() }
131+
}
132+
133+
#if os(iOS)
134+
@objc func didEnterForeground() {
135+
}
136+
137+
@objc func didEnterBackground() {
138+
Task {
139+
await cache.backupCache()
140+
}
141+
}
142+
#endif
143+
}

0 commit comments

Comments
 (0)