-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6fd8b25
commit f698b2f
Showing
11 changed files
with
636 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// | ||
// Debouncer.swift | ||
// hyperswitch | ||
// | ||
// Created by Kuntimaddi Manideep on 24/01/25. | ||
// | ||
import Foundation | ||
|
||
final class Debouncer { | ||
private let delay: TimeInterval | ||
private var workItem: DispatchWorkItem? | ||
private let queue: DispatchQueue | ||
private let lock = NSLock() | ||
|
||
init(delayInMillis: TimeInterval, queue: DispatchQueue = .main) { | ||
self.delay = delayInMillis / 1000.0 | ||
self.queue = queue | ||
} | ||
|
||
func debounce(action: @escaping @Sendable () -> Void) { | ||
lock.lock() | ||
defer { lock.unlock() } | ||
workItem?.cancel() | ||
let task = DispatchWorkItem { action() } | ||
workItem = task | ||
queue.asyncAfter(deadline: .now() + delay, execute: task) | ||
} | ||
|
||
func cancel() { | ||
lock.lock() | ||
defer { lock.unlock() } | ||
|
||
workItem?.cancel() | ||
workItem = nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
// | ||
// HyperLogManager.swift | ||
// hyperswitch | ||
// | ||
// Created by Kuntimaddi Manideep on 24/01/25. | ||
// | ||
|
||
import Foundation | ||
|
||
final class HyperLogManager { | ||
private static var logsBatch: [Log] = [] | ||
private static var publishableKey: String = "" | ||
private static var loggingEndPoint: String? | ||
private static let fileManager = LogFileManager() | ||
private static let queue = DispatchQueue(label: "hyperlog.queue") | ||
private static let debouncer = Debouncer(delayInMillis: 2000.0) | ||
|
||
private static func formatPayload(logs: [String]) -> String { | ||
return "[" + logs.joined(separator: ",") | ||
.replacingOccurrences(of: " ", with: "") | ||
.replacingOccurrences(of: "\n", with: "") + "]" | ||
} | ||
|
||
private static func getStringifiedLogs(_ logBatch: [Log]) -> [String] { | ||
return logBatch.compactMap { $0.toJson() } | ||
} | ||
|
||
static func initialize(publishableKey: String) { | ||
queue.async { | ||
self.publishableKey = publishableKey | ||
self.loggingEndPoint = SDKEnvironment.loggingURL(for: publishableKey) | ||
} | ||
if !publishableKey.isEmpty { | ||
sendLogsFromFile() | ||
} | ||
} | ||
|
||
static func sendLogsFromFile() { | ||
queue.async { | ||
guard let endpoint = loggingEndPoint else { return } | ||
let logs = fileManager.getStoredLogsInArray() | ||
let payload = formatPayload(logs: logs) | ||
Task { | ||
do { | ||
let response = try await HyperNetworking.makeHttpRequest( | ||
urlString: endpoint, | ||
method: "POST", | ||
headers: ["Content-Type": "application/json"], | ||
body: payload) | ||
if !response.isEmpty { | ||
fileManager.clearFile() | ||
} | ||
} catch { | ||
print("Network error") | ||
} | ||
} | ||
} | ||
} | ||
|
||
static func saveLogsToFile() { | ||
queue.async { | ||
let logs = getStringifiedLogs(logsBatch) | ||
fileManager.addLogs(logs: logs) | ||
} | ||
} | ||
|
||
static func saveLogsToFile(_ logsBatch: [Log]) { | ||
queue.async { | ||
let logs = getStringifiedLogs(logsBatch) | ||
fileManager.addLogs(logs: logs) | ||
} | ||
} | ||
|
||
static func addLog(_ log: Log) { | ||
queue.async { | ||
logsBatch.append(log) | ||
debouncer.debounce { | ||
sendLogsOverNetwork() | ||
} | ||
} | ||
} | ||
|
||
private static func sendLogsOverNetwork() { | ||
queue.async { | ||
guard !logsBatch.isEmpty, let endpoint = loggingEndPoint else { return } | ||
var copiedLogs = logsBatch | ||
logsBatch.removeAll() | ||
|
||
for index in copiedLogs.indices { | ||
copiedLogs[index].merchant_id = HyperLogManager.publishableKey | ||
} | ||
|
||
let logsToSend = getStringifiedLogs(copiedLogs) | ||
|
||
guard !logsToSend.isEmpty else { return } | ||
|
||
let payload = formatPayload(logs: logsToSend) | ||
|
||
Task { | ||
do { | ||
let response = try await HyperNetworking.makeHttpRequest( | ||
urlString: endpoint, | ||
method: "POST", | ||
headers: ["Content-Type": "application/json"], | ||
body: payload) | ||
|
||
if response.isEmpty { | ||
saveLogsToFile(copiedLogs) | ||
} | ||
} catch { | ||
saveLogsToFile(copiedLogs) | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// | ||
// LogFileManager.swift | ||
// hyperswitch | ||
// | ||
// Created by Kuntimaddi Manideep on 24/01/25. | ||
// | ||
import Foundation | ||
|
||
class LogFileManager { | ||
private let logFileName = "crash_logs.json" | ||
private let fileManager = FileManager.default | ||
private let queue = DispatchQueue(label: "logfile.queue", attributes: .concurrent) | ||
|
||
private func getLogFileName() -> URL? { | ||
guard let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { | ||
print("Failed to get document directory") | ||
return nil | ||
} | ||
return documentDirectory.appendingPathComponent(logFileName) | ||
} | ||
|
||
func addLogs(logs: [String]) { | ||
queue.async(flags: .barrier) { [weak self] in | ||
guard let self = self, let fileURL = self.getLogFileName() else { return } | ||
|
||
do { | ||
var existingLogs = self.getStoredLogs() ?? "[]" | ||
var logsArray = self.parseLogs(existingLogs) | ||
|
||
logsArray.append(contentsOf: logs) | ||
|
||
let jsonData = try JSONSerialization.data(withJSONObject: logsArray, options: .prettyPrinted) | ||
|
||
try jsonData.write(to: fileURL, options: .atomic) | ||
} catch { | ||
print("Failed to write logs: \(error.localizedDescription)") | ||
} | ||
} | ||
} | ||
|
||
func getStoredLogs() -> String? { | ||
guard let fileURL = getLogFileName(), fileManager.fileExists(atPath: fileURL.path) else { | ||
return nil | ||
} | ||
|
||
do { | ||
let data = try Data(contentsOf: fileURL) | ||
if let jsonString = String(data: data, encoding: .utf8) { | ||
return jsonString | ||
} else { | ||
return nil | ||
} | ||
|
||
} catch { | ||
print("Error reading logs: \(error.localizedDescription)") | ||
return nil | ||
} | ||
} | ||
|
||
|
||
private func parseLogs(_ logs: String) -> [String] { | ||
if let data = logs.data(using: .utf8), | ||
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String] { | ||
return json | ||
} | ||
return [] | ||
} | ||
|
||
func getStoredLogsInArray() -> [String] { | ||
return self.parseLogs(self.getStoredLogs() ?? "") | ||
} | ||
|
||
func clearFile() { | ||
queue.async(flags: .barrier) { [weak self] in | ||
guard let self = self, let fileURL = self.getLogFileName() else { return } | ||
do { | ||
if self.fileManager.fileExists(atPath: fileURL.path) { | ||
try self.fileManager.removeItem(at: fileURL) | ||
} | ||
} catch { | ||
print("Error clearing crash log file: \(error.localizedDescription)") | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// | ||
// LogBuilder.swift | ||
// hyperswitch | ||
// | ||
// Created by Kuntimaddi Manideep on 24/01/25. | ||
// | ||
|
||
import Foundation | ||
|
||
enum LogType: String, Codable { | ||
case DEBUG, INFO, ERROR, WARNING | ||
} | ||
|
||
enum LogCategory: String, Codable { | ||
case API, USER_ERROR, USER_EVENT, MERCHANT_EVENT, OTA_LIFE_CYCLE | ||
} | ||
|
||
enum EventName: String, Codable { | ||
case HYPER_OTA_INIT, HYPER_OTA_FINISH , HYPER_OTA_EVENT | ||
} | ||
|
||
struct Log: Codable { | ||
let timestamp: String | ||
let log_type: LogType | ||
let component: String | ||
let category: LogCategory | ||
let version: String | ||
let code_push_version: String | ||
let client_core_version: String | ||
let value: String | ||
let internal_metadata: String | ||
let session_id: String | ||
var merchant_id: String | ||
let payment_id: String | ||
let app_id: String? | ||
let platform: String | ||
let user_agent: String | ||
let event_name: EventName | ||
let latency: String? | ||
let first_event: String | ||
let payment_method: String? | ||
let payment_experience: String? | ||
let source: String | ||
|
||
func toJson() -> String? { | ||
do { | ||
let jsonData = try JSONEncoder().encode(self) | ||
return String(data: jsonData, encoding: .utf8) | ||
} catch { | ||
print("Error encoding log to JSON: \(error.localizedDescription)") | ||
return nil | ||
} | ||
} | ||
} | ||
|
||
|
||
|
||
|
||
class LogBuilder { | ||
private var timestamp: String = "" | ||
private var logType: LogType = .INFO | ||
private var component: String = "MOBILE" | ||
private var category: LogCategory = .OTA_LIFE_CYCLE | ||
private var version: String = SDKVersion.current | ||
private var codePushVersion: String = "0.0.2" | ||
private var clientCoreVersion: String = "" | ||
private var value: String = "" | ||
private var internalMetadata: String = "" | ||
private var sessionId: String = "" | ||
private var merchantId: String = "" | ||
private var paymentId: String = "" | ||
private var appId: String? = nil | ||
private var platform: String = "IOS" | ||
private var userAgent: String = "" | ||
private var eventName: EventName = .HYPER_OTA_INIT | ||
private var latency: String? = nil | ||
private var firstEvent: Bool = false | ||
private var paymentMethod: String? = nil | ||
private var paymentExperience: String? = nil | ||
private var source: String = "" | ||
|
||
func setLogType(_ logType: String) -> LogBuilder { | ||
switch logType.uppercased() { | ||
case "ERROR": | ||
self.logType = .ERROR | ||
break | ||
case "WARNING": | ||
self.logType = .WARNING | ||
break | ||
case "DEBUG": | ||
self.logType = .DEBUG | ||
break | ||
default: | ||
self.logType = .INFO | ||
} | ||
return self | ||
} | ||
|
||
func setClientCoreVersion(_ clientCoreVersion: String) -> LogBuilder { | ||
self.clientCoreVersion = clientCoreVersion | ||
return self | ||
} | ||
|
||
func setValue(_ value: String) -> LogBuilder { | ||
self.value = value | ||
return self | ||
} | ||
|
||
func setEventName(_ eventName: EventName) -> LogBuilder { | ||
self.eventName = eventName | ||
return self | ||
} | ||
|
||
func build() -> Log { | ||
self.timestamp = String(Int(Date().timeIntervalSince1970 * 1000)) | ||
return Log( | ||
timestamp: timestamp, | ||
log_type: logType, | ||
component: component, | ||
category: category, | ||
version: version, | ||
code_push_version: codePushVersion, | ||
client_core_version: clientCoreVersion, | ||
value: value, | ||
internal_metadata: internalMetadata, | ||
session_id: sessionId, | ||
merchant_id: merchantId, | ||
payment_id: paymentId, | ||
app_id: appId, | ||
platform: platform, | ||
user_agent: userAgent, | ||
event_name: eventName, | ||
latency: latency, | ||
first_event: firstEvent ? "true" : "false", | ||
payment_method: paymentMethod, | ||
payment_experience: paymentExperience, | ||
source: source | ||
) | ||
} | ||
} | ||
|
||
|
Oops, something went wrong.