diff --git a/ios/ConduitModule/AppLogger.swift b/ios/ConduitModule/AppLogger.swift
new file mode 100644
index 0000000..6e0c78c
--- /dev/null
+++ b/ios/ConduitModule/AppLogger.swift
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2024, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+
+import Foundation
+import Puppy
+import Logging
+
+struct JSONLogFormatter : LogFormattable {
+
+ let jsonEncoder: JSONEncoder = JSONEncoder()
+
+ func formatMessage(_ level: LogLevel, message: String, tag: String, function: String,
+ file: String, line: UInt, swiftLogInfo: [String: String],
+ label: String, date: Date, threadID: UInt64) -> String {
+
+ let category = swiftLogInfo["label"]! // Category must always be set.
+
+ do {
+ var metadata: GenericJSON? = nil
+ if let metadataString = swiftLogInfo["metadata"] {
+ // TODO: Puppy API limits us to encoding and decoding metadata.
+ let jsonObj = try JSONSerialization.jsonObject(with: metadataString.data(using: .utf8)!)
+ metadata = try GenericJSON(jsonObj)
+ }
+
+ let log = Log(
+ timestamp: date,
+ level: FeedbackLogLevel(from: level),
+ category: category,
+ message: message,
+ data: metadata
+ )
+
+ let jsonData = try self.jsonEncoder.encode(log)
+ guard let logString = String(data: jsonData, encoding: .utf8) else {
+ throw Err("Failed to convert JSON data to String")
+ }
+ return logString
+
+ } catch {
+ os_log(.fault, "Failed to encode log")
+ return "Error formatting log: \(String(describing: error))"
+ }
+ }
+}
+
+
+/// Manages an instance of Puppy as backend for swift-log.
+enum AppLogger {
+
+ #if DEBUG
+ static let minLogLevel = Logging.Logger.Level.trace
+ #else
+ static let minLogLevel = Logging.Logger.Level.info
+ #endif
+
+ static let subsystem: String = Bundle.main.bundleIdentifier!
+ private static let maxArchivedCount: UInt8 = 2
+
+ private static let baseFileURL = {
+ var appSupportDir = try getApplicationSupportDirectory()
+ if #available(iOS 16.0, *) {
+ appSupportDir.append(components: Self.subsystem, "appLogs", "app.log")
+ } else {
+ appSupportDir.appendPathComponent(Self.subsystem)
+ appSupportDir.appendPathComponent("appLogs")
+ appSupportDir.appendPathComponent("app.log")
+ }
+
+ return appSupportDir.absoluteURL
+ }
+
+ static func initializePuppy() -> Puppy {
+ var puppy = Puppy()
+
+ let fileLogger = try! FileRotationLogger(
+ "\(AppLogger.subsystem).log.file", // DispatchQueue label
+ logLevel: Self.minLogLevel.toPuppy(),
+ logFormat: JSONLogFormatter(),
+ fileURL: AppLogger.baseFileURL(),
+ filePermission: "600",
+ rotationConfig: RotationConfig(
+ suffixExtension: .numbering,
+ maxFileSize: 100 * 1024,
+ maxArchivedFilesCount: AppLogger.maxArchivedCount
+ )
+ )
+
+ puppy.add(fileLogger)
+ return puppy
+ }
+
+ static func readLogs() throws -> ([Log], [ParseError]) {
+
+ var files = [URL]()
+ for i in 0...maxArchivedCount {
+ var fileURL = try! baseFileURL()
+ if i > 0 {
+ fileURL = fileURL.appendingPathExtension("\(i)")
+ }
+ files.append(fileURL)
+ }
+
+ return try readLogFiles(withLogType: Log.self, paths: files, transform: { $0 })
+ }
+
+}
diff --git a/ios/ConduitModule/ConduitManager.swift b/ios/ConduitModule/ConduitManager.swift
index 8cc549d..dc37221 100644
--- a/ios/ConduitModule/ConduitManager.swift
+++ b/ios/ConduitModule/ConduitManager.swift
@@ -17,16 +17,14 @@
*
*/
+import Collections
import Foundation
import PsiphonTunnel
-import Collections
+import Logging
-extension Logger {
- private static var subsystem = Bundle.main.bundleIdentifier!
-
- static let conduitMan = Logger(subsystem: subsystem, category: "ConduitManager")
-
- static let psiphonTunnel = Logger(subsystem: subsystem, category: "PsiphonTunnel")
+extension Logging.Logger {
+ static let conduitMan = Logger(label: "ConduitManager")
+ static let psiphonTunnel = Logger(label: "PsiphonTunnel")
}
struct ConduitParams: Equatable {
@@ -331,7 +329,7 @@ fileprivate final class PsiphonTunnelListener: NSObject, TunneledAppDelegate {
let data = try Data(contentsOf: ResourceFile.embeddedServerEntries.url)
return String(data: data, encoding: .utf8)
} catch {
- Logger.conduitMan.fault("Failed to read embedded server entries")
+ Logger.conduitMan.critical("Failed to read embedded server entries")
return nil
}
}
@@ -371,7 +369,7 @@ fileprivate final class PsiphonTunnelListener: NSObject, TunneledAppDelegate {
return config
} catch {
- Logger.conduitMan.error("getPsiphonConfig failed: \(error, privacy: .public)")
+ Logger.conduitMan.error("getPsiphonConfig failed", metadata: ["error": "\(error)"])
return nil
}
}
@@ -401,7 +399,7 @@ fileprivate final class PsiphonTunnelListener: NSObject, TunneledAppDelegate {
}
func onDiagnosticMessage(_ message: String, withTimestamp timestamp: String) {
- Logger.psiphonTunnel.debug("\(message, privacy: .public)")
+ Logger.psiphonTunnel.debug("\(message)")
}
}
diff --git a/ios/ConduitModule/ConduitModule.swift b/ios/ConduitModule/ConduitModule.swift
index 82016de..b0cbdd4 100644
--- a/ios/ConduitModule/ConduitModule.swift
+++ b/ios/ConduitModule/ConduitModule.swift
@@ -19,15 +19,11 @@
import Foundation
import PsiphonTunnel
-import OSLog
+import Logging
-
-extension Logger {
- private static var subsystem = Bundle.main.bundleIdentifier!
-
- static let conduitModule = Logger(subsystem: subsystem, category: "ConduitModule")
-
- static let feedbackUploadService = Logger(subsystem: subsystem, category: "FeedbackUploadService")
+extension Logging.Logger {
+ static let conduitModule = Logger(label: "ConduitModule")
+ static let feedbackUploadService = Logger(label: "FeedbackUploadService")
}
/// A type that is used for cross-langauge interaction with JavaScript codebase.
@@ -188,8 +184,20 @@ final class ConduitModule: RCTEventEmitter {
// synchronous execution to reuse the same thread.
// Note that using `.sync` and targeting the same queue will result in a deadlock.
let dispatchQueue: dispatch_queue_t
-
+
override init() {
+
+ LoggingSystem.bootstrap { label in
+ MultiplexLogHandler([
+ OSLogger(subsystem: AppLogger.subsystem,
+ label: label,
+ logLevel: AppLogger.minLogLevel),
+ PsiphonLogHandler(label: label,
+ logLevel: AppLogger.minLogLevel,
+ puppy: AppLogger.initializePuppy()),
+ ])
+ }
+
dispatchQueue = DispatchQueue(label: "ca.psiphon.conduit.module", qos: .default)
super.init()
@@ -223,7 +231,7 @@ final class ConduitModule: RCTEventEmitter {
func sendEvent(_ event: ConduitEvent) {
sendEvent(withName: ConduitEvent.eventName, body: event.asDictionary)
- Logger.conduitModule.debug("ConduitEvent: \(String(describing: event))")
+ Logger.conduitModule.trace("ConduitEvent", metadata: ["event": "\(String(describing: event))"])
}
}
@@ -252,8 +260,7 @@ extension ConduitModule {
}
} catch {
sendEvent(.proxyError(.inProxyStartFailed))
- Logger.conduitModule.error(
- "Proxy start failed: \(String(describing: error), privacy: .public)")
+ Logger.conduitModule.error( "Proxy start failed", metadata: ["error": "\(error)"])
}
case .started:
await self.conduitManager.stopConduit()
@@ -316,7 +323,6 @@ extension ConduitModule {
) {
do {
- // Read psiphon-tunnel-core notices.
let dataRootDirectory = try getApplicationSupportDirectory()
@@ -326,21 +332,30 @@ extension ConduitModule {
olderNoticesFilePath(dataRootDirectory: dataRootDirectory)
]
- let (tunnelCoreEntries, parseErrors) = try readDiagnosticLogFiles(
- TunnelCoreLog.self,
+ let (tunnelCoreLogs, parseErrors) = try readLogFiles(
+ withLogType: TunnelCoreLog.self,
paths: tunnelCoreNoticesPath,
- transform: DiagnosticEntry.create(from:))
-
+ transform: Log.create(from:))
if parseErrors.count > 0 {
- Logger.conduitModule.error(
- "Log parse errors: \(String(describing: parseErrors), privacy: .public)")
+ os_log(.error, "Log parse error: \(parseErrors)")
+ }
+
+ let (appLogs, appLogParseErrors) = try AppLogger.readLogs()
+ if appLogParseErrors.count > 0 {
+ os_log(.error, "Log parse error: \(parseErrors)")
}
+ var allLogs = tunnelCoreLogs
+ allLogs.append(contentsOf: appLogs)
+ allLogs.append(contentsOf: (parseErrors + appLogParseErrors).map {
+ Log(timestamp: Date(), level: .error, category: "LogParser", message: $0.message)
+ })
+ allLogs.sort()
// Prepare Feedback Diagnostic Report
let feedbackId = try generateFeedbackId()
- Logger.conduitModule.info("Preparing feedback report with ID = \(feedbackId, privacy: .public)")
+ Logger.conduitModule.info("Preparing feedback report", metadata: ["feedback.id": "\(feedbackId)"])
let psiphonConfig = try defaultPsiphonConfig()
@@ -358,26 +373,26 @@ extension ConduitModule {
inproxyId: inproxyId
)
- let report = FeedbackDiagnosticReport(
- metadata: Metadata(
+ let report = FeedbackDiagnosticReportV2(
+ metadata: MetadataV2(
id: feedbackId,
appName: "conduit",
- platform: ClientPlatform.platformString,
- date: Date()
+ platform: ClientPlatform.platformString
),
- feedback: nil,
- diagnosticInfo: DiagnosticInfo(
- systemInformation: SystemInformation(
- build: DeviceInfo.gatherDeviceInfo(device: .current),
- tunnelCoreBuildInfo: PsiphonTunnel.getBuildInfo(),
- psiphonInfo: psiphonInfo,
- isAppStoreBuild: true,
- isJailbroken: false,
- language: getLanguageMinimalIdentifier(),
- // TODO: get networkTypeName
- networkTypeName: "WIFI"),
- diagnosticHistory: tunnelCoreEntries
- ))
+ systemInformation: SystemInformationV2(
+ build: DeviceInfo.gatherDeviceInfo(device: .current),
+ tunnelCoreBuildInfo: PsiphonTunnel.getBuildInfo(),
+ isAppStoreBuild: true,
+ isJailbroken: false,
+ language: getLanguageMinimalIdentifier(),
+ // TODO: get networkTypeName
+ networkTypeName: "WIFI"),
+ psiphonInfo: psiphonInfo,
+ applicationInfo: ApplicationInfo(
+ applicationId: getApplicagtionId(),
+ clientVersion: getClientVersion()),
+ logs: allLogs
+ )
let json = String(data: try JSONEncoder().encode(report), encoding: .utf8)!
@@ -391,42 +406,45 @@ extension ConduitModule {
uploadPath: "")
resolve(nil)
- Logger.conduitModule.info("Finished uploading feedback diagnostic report.")
+ Logger.conduitModule.info("Finished uploading feedback diagnostic report", metadata: ["feedback.id": "\(feedbackId)"])
} catch {
reject("error", "Feedback upload failed", nil)
- Logger.conduitModule.error(
- "Feedback upload failed: \(String(describing: error), privacy: .public)")
+ Logger.conduitModule.error("Feedback upload failed", metadata: [
+ "feedback.id": "\(feedbackId)",
+ "error": "\(error)"
+ ])
}
}
} catch {
- reject("error", "Feedback upload failed", nil)
- Logger.conduitModule.error(
- "Feedback upload failed: \(String(describing: error), privacy: .public)")
+ reject("error", "Feedback preparation failed", nil)
+ Logger.conduitModule.error("Feedback preparation failed", metadata: ["error": "\(error)"])
}
}
@objc(logInfo:msg:withResolver:withRejecter:)
func logInfo(_ tag: String, msg: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
- Logger.conduitModule.info("\(tag, privacy: .public): \(msg, privacy: .public)")
+ let logger = Logger(label: tag)
+ logger.info("\(msg)")
resolve(nil)
}
@objc(logWarn:msg:withResolver:withRejecter:)
func logWarn(_ tag: String, msg: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
- Logger.conduitModule.info("\(tag, privacy: .public): \(msg, privacy: .public)")
+ let logger = Logger(label: tag)
+ logger.warning("\(msg)")
resolve(nil)
}
@objc(logError:msg:withResolver:withRejecter:)
func logError(_ tag: String, msg: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
- Logger.conduitModule.info("\(tag, privacy: .public): \(msg, privacy: .public)")
+ let logger = Logger(label: tag)
+ logger.error("\(msg)")
resolve(nil)
}
}
-
extension ConduitModule: ConduitManager.Listener {
func onConduitStatusUpdate(_ status: ConduitManager.ConduitStatus,
@@ -480,7 +498,7 @@ extension ConduitModule: ConduitManager.Listener {
extension ConduitModule: FeedbackUploadService.Listener {
func onDiagnosticMessage(_ message: String, withTimestamp timestamp: String) {
- Logger.feedbackUploadService.info("DiagnosticMessage: \(timestamp, privacy: .public) \(message, privacy: .public)")
+ Logger.feedbackUploadService.info("DiagnosticMessage", metadata: ["timestamp": "\(timestamp)", "message": "\(message)"])
}
}
diff --git a/ios/ConduitModule/Feedback.swift b/ios/ConduitModule/Feedback/Feedback.swift
similarity index 77%
rename from ios/ConduitModule/Feedback.swift
rename to ios/ConduitModule/Feedback/Feedback.swift
index 46c0cf4..52f0378 100644
--- a/ios/ConduitModule/Feedback.swift
+++ b/ios/ConduitModule/Feedback/Feedback.swift
@@ -19,40 +19,7 @@
import Foundation
-// MARK: - Feedback types
-
-public struct Metadata : Codable {
-
- /// Feedback version type.
- let version: Int = 1
-
- /// Feedback ID.
- let id: String
-
- /// Client platform.
- let platform: String
-
- /// App name.
- let appName: String
-
- @ISO1806MilliCodedDate var date: Date
-
- init(id: String, appName: String, platform: String, date: Date) {
- self.id = id
- self.appName = appName
- self.platform = platform
- self.date = date
- }
-
- enum CodingKeys : String, CodingKey {
- case version = "version"
- case id = "id"
- case platform = "platform"
- case appName = "appName"
- case date = "date!!timestamp"
- }
-
-}
+// MARK: - Common types
public struct PsiphonInfo : Codable {
@@ -70,68 +37,6 @@ public struct PsiphonInfo : Codable {
}
-public struct SystemInformation : Codable {
-
- let build: DeviceInfo
- let tunnelCoreBuildInfo: String
- let psiphonInfo: PsiphonInfo
- let isAppStoreBuild: Bool
- let isJailbroken: Bool
-
- /// BCP-47 identifier in a minimalist form. Script and region may be omitted. For example, "zh-TW", "en"
- let language: String
-
- let networkTypeName: String
-
- enum CodingKeys : String, CodingKey {
- case build = "Build"
- case tunnelCoreBuildInfo = "buildInfo"
- case psiphonInfo = "PsiphonInfo"
- case isAppStoreBuild = "isAppStoreBuild"
- case isJailbroken = "isJailbroken"
- case language = "language"
- case networkTypeName = "networkTypeName"
- }
-
-}
-
-public struct DiagnosticEntry : Codable {
-
- let message: String
- let data: GenericJSON
- @ISO1806MilliCodedDate var timestamp: Date
-
- enum CodingKeys : String, CodingKey {
- case message = "msg"
- case data = "data"
- case timestamp = "timestamp!!timestamp"
- }
-
- init(message: String, data: GenericJSON = .object([:]), timestamp: Date) {
- self.message = message
- self.data = data
- self.timestamp = timestamp
- }
-
-}
-
-public struct DiagnosticInfo : Codable {
-
- let systemInformation: SystemInformation
- let diagnosticHistory: [DiagnosticEntry]
-
- // TODO: This field is not used but is required to exist to maintain
- // compatibility with the feedback server, preventing formatting errors.
- private let statusHistory: [String] = []
-
- enum CodingKeys : String, CodingKey {
- case systemInformation = "SystemInformation"
- case statusHistory = "StatusHistory"
- case diagnosticHistory = "DiagnosticHistory"
- }
-
-}
-
public struct SurveyResponse : Codable {
let title: String
@@ -216,18 +121,7 @@ public struct DeviceInfo : Codable {
}
}
-public struct FeedbackDiagnosticReport : Codable {
-
- let metadata: Metadata
- let feedback: Feedback?
- let diagnosticInfo: DiagnosticInfo
-
- enum CodingKeys : String, CodingKey {
- case metadata = "Metadata"
- case feedback = "Feedback"
- case diagnosticInfo = "DiagnosticInfo"
- }
-}
+
public enum ClientPlatform {
@@ -262,6 +156,277 @@ func generateFeedbackId() throws -> String {
return feedbackID
}
+
+// MARK: - Version 1
+
+
+public struct SystemInformationV1 : Codable {
+
+ let build: DeviceInfo
+ let tunnelCoreBuildInfo: String
+ let psiphonInfo: PsiphonInfo
+ let isAppStoreBuild: Bool
+ let isJailbroken: Bool
+
+ /// BCP-47 identifier in a minimalist form. Script and region may be omitted. For example, "zh-TW", "en"
+ let language: String
+
+ let networkTypeName: String
+
+ enum CodingKeys : String, CodingKey {
+ case build = "Build"
+ case tunnelCoreBuildInfo = "buildInfo"
+ case psiphonInfo = "PsiphonInfo"
+ case isAppStoreBuild = "isAppStoreBuild"
+ case isJailbroken = "isJailbroken"
+ case language = "language"
+ case networkTypeName = "networkTypeName"
+ }
+
+}
+
+public struct DiagnosticEntry : Codable {
+
+ let message: String
+ let data: GenericJSON
+ @ISO1806MilliCodedDate var timestamp: Date
+
+ enum CodingKeys : String, CodingKey {
+ case message = "msg"
+ case data = "data"
+ case timestamp = "timestamp!!timestamp"
+ }
+
+ init(message: String, data: GenericJSON = .object([:]), timestamp: Date) {
+ self.message = message
+ self.data = data
+ self.timestamp = timestamp
+ }
+
+ public static func < (lhs: DiagnosticEntry, rhs: DiagnosticEntry) -> Bool {
+ return lhs.timestamp < rhs.timestamp
+ }
+
+}
+
+public extension DiagnosticEntry {
+
+ static func create(from tunnelCoreLog: TunnelCoreLog) throws -> DiagnosticEntry {
+ DiagnosticEntry(
+ message: "",
+ data: try GenericJSON(
+ ["noticeType": tunnelCoreLog.noticeType,
+ "data": tunnelCoreLog.data,
+ ]),
+ timestamp: tunnelCoreLog.timestamp
+ )
+ }
+
+}
+
+public struct DiagnosticInfo : Codable {
+
+ let systemInformation: SystemInformationV1
+ let diagnosticHistory: [DiagnosticEntry]
+ private let statusHistory: [String]
+
+ enum CodingKeys : String, CodingKey {
+ case systemInformation = "SystemInformation"
+ case statusHistory = "StatusHistory"
+ case diagnosticHistory = "DiagnosticHistory"
+ }
+
+}
+
+public struct MetadataV1 : Codable {
+
+ /// Feedback version type.
+ let version: Int = 1
+
+ /// Feedback ID.
+ let id: String
+
+ /// Client platform.
+ let platform: String
+
+ /// App name.
+ let appName: String
+
+ @ISO1806MilliCodedDate var date: Date
+
+ init(id: String, appName: String, platform: String, date: Date) {
+ self.id = id
+ self.appName = appName
+ self.platform = platform
+ self.date = date
+ }
+
+ enum CodingKeys : String, CodingKey {
+ case version = "version"
+ case id = "id"
+ case platform = "platform"
+ case appName = "appName"
+ case date = "date!!timestamp"
+ }
+
+}
+
+public struct FeedbackDiagnosticReportV1 : Codable {
+
+ let metadata: MetadataV1
+ let feedback: Feedback?
+ let diagnosticInfo: DiagnosticInfo
+
+ enum CodingKeys : String, CodingKey {
+ case metadata = "Metadata"
+ case feedback = "Feedback"
+ case diagnosticInfo = "DiagnosticInfo"
+ }
+}
+
+// MARK: - Version 2
+
+public struct ApplicationInfo : Codable {
+
+ let applicationId: String
+ let clientVersion: String
+
+ enum CodingKeys : String, CodingKey {
+ case applicationId = "applicationId"
+ case clientVersion = "clientVersion"
+ }
+
+}
+
+public struct SystemInformationV2 : Codable {
+
+ let build: DeviceInfo
+ let tunnelCoreBuildInfo: String
+ let isAppStoreBuild: Bool
+ let isJailbroken: Bool
+
+ /// BCP-47 identifier in a minimalist form. Script and region may be omitted. For example, "zh-TW", "en"
+ let language: String
+
+ let networkTypeName: String
+
+ enum CodingKeys : String, CodingKey {
+ case build = "Build"
+ case tunnelCoreBuildInfo = "buildInfo"
+ case isAppStoreBuild = "isAppStoreBuild"
+ case isJailbroken = "isJailbroken"
+ case language = "language"
+ case networkTypeName = "networkTypeName"
+ }
+
+}
+
+public enum FeedbackLogLevel : String, Codable {
+ case trace = "Trace"
+ case debug = "Debug"
+ case info = "Info"
+ case notice = "Notice"
+ case warning = "Warning"
+ case error = "Error"
+ case critical = "Critical"
+}
+
+public struct Log : Comparable, Codable {
+
+ @ISO1806MilliCodedDate var timestamp: Date
+ let level: FeedbackLogLevel?
+ let category: String
+ let message: String?
+ let data: GenericJSON?
+
+ enum CodingKeys : String, CodingKey {
+ case timestamp = "timestamp!!timestamp"
+ case level = "level"
+ case category = "category"
+ case message = "message"
+ case data = "data"
+ }
+
+ init(timestamp: Date, level: FeedbackLogLevel?, category: String, message: String?, data: GenericJSON? = nil) {
+ self.timestamp = timestamp
+ self.level = level
+ self.category = category
+ self.message = message
+ self.data = data
+ }
+
+ public static func < (lhs: Log, rhs: Log) -> Bool {
+ return lhs.timestamp < rhs.timestamp
+ }
+
+}
+
+public extension Log {
+
+ static func create(from tunnelCoreLog: TunnelCoreLog) throws -> Log {
+ Log(
+ timestamp: tunnelCoreLog.timestamp,
+ level: nil,
+ category: "tunnel-core",
+ message: nil,
+ data: try GenericJSON(
+ ["noticeType": tunnelCoreLog.noticeType,
+ "data": tunnelCoreLog.data,
+ ])
+ )
+ }
+
+}
+
+public struct MetadataV2 : Codable {
+
+ /// Feedback version type.
+ let version: Int = 2
+
+ /// Feedback ID.
+ let id: String
+
+ /// Client platform.
+ let platform: String
+
+ /// App name.
+ let appName: String
+
+
+ init(id: String, appName: String, platform: String) {
+ self.id = id
+ self.appName = appName
+ self.platform = platform
+ }
+
+ enum CodingKeys : String, CodingKey {
+ case version = "version"
+ case id = "id"
+ case platform = "platform"
+ case appName = "appName"
+ }
+
+}
+
+public struct FeedbackDiagnosticReportV2 : Codable {
+
+ let metadata: MetadataV2
+ let systemInformation: SystemInformationV2
+ let psiphonInfo: PsiphonInfo
+ let applicationInfo: ApplicationInfo
+ let logs: [Log]
+
+
+ enum CodingKeys : String, CodingKey {
+ case metadata = "Metadata"
+ case systemInformation = "SystemInformation"
+ case psiphonInfo = "PsiphonInfo"
+ case applicationInfo = "ApplicationInfo"
+ case logs = "Logs"
+ }
+
+}
+
// MARK: -
// The section below is too platform dependent,
@@ -292,21 +457,6 @@ public struct TunnelCoreLog : Codable {
@ISO1806MilliCodedDate var timestamp: Date
}
-public extension DiagnosticEntry {
-
- static func create(from tunnelCoreLog: TunnelCoreLog) throws -> DiagnosticEntry {
- DiagnosticEntry(
- message: "",
- data: try GenericJSON(
- ["noticeType": tunnelCoreLog.noticeType,
- "data": tunnelCoreLog.data,
- ]),
- timestamp: tunnelCoreLog.timestamp
- )
- }
-
-}
-
#if canImport(PsiphonTunnel)
import PsiphonTunnel
@@ -414,6 +564,32 @@ extension FeedbackUploadService : PsiphonTunnelLoggerDelegate, PsiphonTunnelFeed
#endif
+#if canImport(Puppy)
+import Puppy
+
+extension FeedbackLogLevel {
+
+ init(from puppyLogLevel: LogLevel) {
+ switch puppyLogLevel {
+ case .trace, .verbose, .debug:
+ self = .debug
+ case .info:
+ self = .info
+ case .notice:
+ self = .info
+ case .warning:
+ self = .warning
+ case .error:
+ self = .error
+ case .critical:
+ self = .critical
+ }
+ }
+
+}
+
+#endif
+
// MARK: - Basic type
@propertyWrapper
diff --git a/ios/ConduitModule/FeedbackLogReader.swift b/ios/ConduitModule/Feedback/FeedbackLogReader.swift
similarity index 72%
rename from ios/ConduitModule/FeedbackLogReader.swift
rename to ios/ConduitModule/Feedback/FeedbackLogReader.swift
index b0e0b3a..ea2bda0 100644
--- a/ios/ConduitModule/FeedbackLogReader.swift
+++ b/ios/ConduitModule/Feedback/FeedbackLogReader.swift
@@ -34,7 +34,7 @@ func parseJSONLines(
let decoder = JSONDecoder()
- for logLine in data.components(separatedBy: "\n") {
+ for logLine in data.components(separatedBy: .newlines) {
guard !logLine.isEmpty else {
continue
@@ -56,39 +56,32 @@ func parseJSONLines(
return (entries, parseErrors)
}
-/// Reads logs from `paths` of type `T` and converts to `DiagnosticEntry` using the `transform` function.
+/// Reads logs from `paths` of type `T` and converts to `LogType` using the `transform` function.
/// Paths that do not exist are ignored.
-/// The returned `DiagnosticEntry` array is sorted by timestamp in ascending order.
-func readDiagnosticLogFiles(
- _ type: T.Type,
+func readLogFiles(
+ withLogType type: T.Type,
paths: [URL],
- transform: (T) throws -> DiagnosticEntry
-) throws -> ([DiagnosticEntry], [ParseError]) {
+ transform: (T) throws -> LogType
+) throws -> ([LogType], [ParseError]) {
- var diagnosticEntries = [DiagnosticEntry]()
+ var logs = [LogType]()
var parseErrors = [ParseError]()
for path in paths {
// Ignore paths that don't exist.
if !FileManager.default.fileExists(atPath: try path.filePath()) {
- Logger.conduitModule.info("No diagnostic file at path: \(path, privacy: .private)")
+ parseErrors.append(ParseError(message: "No diagnostic file at path: [redacted]/\(path.lastPathComponent)"))
continue
}
let data = try Data(contentsOf: path)
let (entries, errs) = parseJSONLines(T.self, data: String(data: data, encoding: .utf8)!)
- diagnosticEntries.append(contentsOf: try entries.map(transform))
+ logs.append(contentsOf: try entries.map(transform))
parseErrors.append(contentsOf: errs)
-
- }
-
- // Sorts the entries by timestamp in ascending order.
- diagnosticEntries.sort {
- $0.timestamp < $1.timestamp
}
- return (diagnosticEntries, parseErrors)
+ return (logs, parseErrors)
}
diff --git a/ios/ConduitModule/Feedback/LogHandler.swift b/ios/ConduitModule/Feedback/LogHandler.swift
new file mode 100644
index 0000000..815d97d
--- /dev/null
+++ b/ios/ConduitModule/Feedback/LogHandler.swift
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2024, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2020-2023 Koichi Yokota
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ */
+
+
+import Foundation
+
+#if canImport(Logging)
+import Logging
+
+public struct OSLogger: LogHandler {
+
+ public var metadata: Logging.Logger.Metadata
+ public var logLevel: Logging.Logger.Level
+ public let label: String
+ public let queue: DispatchQueue
+ private let osLog: os.Logger
+
+ public init(subsystem: String, label: String, logLevel: Logging.Logger.Level = .trace) {
+ self.metadata = [:]
+ self.label = label
+ self.queue = DispatchQueue(label: label)
+ self.logLevel = logLevel
+ self.osLog = os.Logger(subsystem: subsystem, category: label)
+ }
+
+ public subscript(metadataKey key: String) -> Logging.Logger.Metadata.Value? {
+ get {
+ return metadata[key]
+ }
+ set(newValue) {
+ metadata[key] = newValue
+ }
+ }
+
+ public func log(
+ level: Logging.Logger.Level,
+ message: Logging.Logger.Message,
+ metadata: Logging.Logger.Metadata?,
+ source: String, file: String, function: String, line: UInt)
+ {
+ let osMsg: String
+ if let metadata = metadata {
+ osMsg = "\(message) \(String(describing: metadata))"
+ } else {
+ osMsg = "\(message)"
+ }
+
+ self.osLog.log(level: logType(level), "\(osMsg, privacy: .public)")
+ }
+
+ private func logType(_ level: Logging.Logger.Level) -> OSLogType {
+ switch level {
+ case .trace:
+ // `OSLog` doesn't have `trace`, so use `debug` instead.
+ return .debug
+ case .debug:
+ return .debug
+ case .info:
+ return .info
+ case .notice:
+ // `OSLog` doesn't have `notice`, so use `info` instead.
+ return .info
+ case .warning:
+ // `OSLog` doesn't have `warning`, so use `default` instead.
+ return .default
+ case .error:
+ return .error
+ case .critical:
+ // `OSLog` doesn't have `critical`, so use `.fault` instead.
+ return .fault
+ }
+ }
+}
+
+#endif // canImport(Logging)
+
+#if canImport(Logging) && canImport(Puppy)
+import Logging
+import Puppy
+
+/// Logs to both OSLog and Puppy.
+public struct PsiphonLogHandler: LogHandler {
+
+ public var logLevel: Logging.Logger.Level
+ public var metadata: Logging.Logger.Metadata
+
+ private let label: String
+ private let puppy: Puppy
+ private let metadataEncoder: JSONEncoder
+
+ public init(label: String, logLevel: Logging.Logger.Level, puppy: Puppy, metadata: Logging.Logger.Metadata = [:]) {
+ self.label = label
+ self.logLevel = logLevel
+ self.puppy = puppy
+ self.metadata = metadata
+ self.metadataEncoder = JSONEncoder()
+ }
+
+ public subscript(metadataKey key: String) -> Logging.Logger.Metadata.Value? {
+ get {
+ return metadata[key]
+ }
+ set(newValue) {
+ metadata[key] = newValue
+ }
+ }
+
+ public func log(
+ level: Logging.Logger.Level,
+ message: Logging.Logger.Message,
+ metadata: Logging.Logger.Metadata?,
+ source: String, file: String, function: String, line: UInt)
+ {
+
+ // Log with Puppy
+ do {
+ let metadata = mergedMetadata(metadata)
+ let encodedMetadata = try metadataEncoder.encode(metadata)
+ var swiftLogInfo = ["label": label, "source": source]
+
+ if let encodedMetadata = String(data: encodedMetadata, encoding: .utf8) {
+ swiftLogInfo["metadata"] = encodedMetadata
+ }
+ puppy.logMessage(level.toPuppy(), message: "\(message)", tag: "swiftlog", function: function, file: file, line: line, swiftLogInfo: swiftLogInfo)
+ } catch {
+ os_log(.fault, "failed to encode metadata")
+ }
+
+ }
+
+ private func mergedMetadata(_ metadata: Logging.Logger.Metadata?) -> Logging.Logger.Metadata {
+ var mergedMetadata: Logging.Logger.Metadata
+ if let metadata = metadata {
+ mergedMetadata = self.metadata.merging(metadata, uniquingKeysWith: { _, new in new })
+ } else {
+ mergedMetadata = self.metadata
+ }
+ return mergedMetadata
+ }
+
+}
+
+extension Logging.Logger.MetadataValue : Encodable {
+
+ public func encode(to encoder: any Encoder) throws {
+ var container = encoder.singleValueContainer()
+ switch self {
+ case .stringConvertible(let stringConvertible):
+ try container.encode(stringConvertible.description)
+ case .string(let string):
+ try container.encode(string)
+ case .array(let array):
+ try container.encode(array)
+ case .dictionary(let dict):
+ try container.encode(dict)
+ }
+ }
+
+}
+
+extension Logging.Logger.Level {
+
+ func toPuppy() -> LogLevel {
+ switch self {
+ case .trace:
+ return .trace
+ case .debug:
+ return .debug
+ case .info:
+ return .info
+ case .notice:
+ return .notice
+ case .warning:
+ return .warning
+ case .error:
+ return .error
+ case .critical:
+ return .critical
+ }
+ }
+
+}
+
+#endif // canImport(Logging) & canImport(Puppy)
+
diff --git a/ios/ConduitModule/Resources.swift b/ios/ConduitModule/Resources.swift
index f259a66..2f244f8 100644
--- a/ios/ConduitModule/Resources.swift
+++ b/ios/ConduitModule/Resources.swift
@@ -85,6 +85,11 @@ func getClientVersion() -> String {
Bundle.main.infoDictionary!["CFBundleVersion"] as! String
}
+/// Returns bundle identifier (`CFBundleIdentifier` defined in `Info.plist` file).
+func getApplicagtionId() -> String {
+ Bundle.main.bundleIdentifier!
+}
+
extension URL {
/// Returns valid file path if this URL points to a local file.
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 67cec83..e8b9ae9 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1010,7 +1010,7 @@ PODS:
- React-debug
- react-native-safe-area-context (4.10.5):
- React-Core
- - react-native-skia (1.3.13):
+ - react-native-skia (1.5.0):
- DoubleConversion
- glog
- hermes-engine
@@ -1623,7 +1623,7 @@ SPEC CHECKSUMS:
React-logger: 257858bd55f3a4e1bc0cf07ddc8fb9faba6f8c7c
React-Mapbuffer: 6c1cacdbf40b531f549eba249e531a7d0bfd8e7f
react-native-safe-area-context: a240ad4b683349e48b1d51fed1611138d1bdad97
- react-native-skia: dca6ed315f74bd4ae2b26368f7029dc8d17ba7e7
+ react-native-skia: dd503e4426d5dcd578a2d11c9261fb591501924b
React-nativeconfig: ba9a2e54e2f0882cf7882698825052793ed4c851
React-NativeModulesApple: 8d11ff8955181540585c944cf48e9e7236952697
React-perflogger: ed4e0c65781521e0424f2e5e40b40cc7879d737e
diff --git a/ios/conduit.xcodeproj/project.pbxproj b/ios/conduit.xcodeproj/project.pbxproj
index a034c13..ce67cb0 100644
--- a/ios/conduit.xcodeproj/project.pbxproj
+++ b/ios/conduit.xcodeproj/project.pbxproj
@@ -13,6 +13,9 @@
293BD6822CB6FA3C006B04D3 /* Feedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 293BD6812CB6FA3C006B04D3 /* Feedback.swift */; };
293BD69A2CB84246006B04D3 /* FeedbackLogReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 293BD6992CB84246006B04D3 /* FeedbackLogReader.swift */; };
294484BD2CACF05C00451B61 /* Resources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294484BC2CACF05C00451B61 /* Resources.swift */; };
+ 2974F3652CD95382000E8D3F /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 2974F3642CD95382000E8D3F /* Logging */; };
+ 2974F3682CDAA7F6000E8D3F /* LogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2974F3672CDAA7F6000E8D3F /* LogHandler.swift */; };
+ 2974F36A2CDAC4CB000E8D3F /* AppLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2974F3692CDAC4CB000E8D3F /* AppLogger.swift */; };
29A9C9FD2CA5FFDD0054431A /* ConduitModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A9C9FC2CA5FFDD0054431A /* ConduitModule.swift */; };
29A9C9FF2CA601D20054431A /* ConduitModule.mm in Sources */ = {isa = PBXBuildFile; fileRef = 29A9C9FE2CA600220054431A /* ConduitModule.mm */; };
29A9CA032CA715720054431A /* PsiphonTunnel.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29A9CA012CA715410054431A /* PsiphonTunnel.xcframework */; };
@@ -21,6 +24,7 @@
29EB55312CA726790042B1B4 /* ios_embedded_server_entries in Resources */ = {isa = PBXBuildFile; fileRef = 29EB552F2CA726790042B1B4 /* ios_embedded_server_entries */; };
29EB55322CA726790042B1B4 /* ios_psiphon_config in Resources */ = {isa = PBXBuildFile; fileRef = 29EB55302CA726790042B1B4 /* ios_psiphon_config */; };
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
+ 6773BCF42CD2C7B800CF4ADE /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = 6773BCF32CD2C7B800CF4ADE /* Puppy */; };
67A1F0232CC027D600AE7C39 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = 67A1F0222CC027D600AE7C39 /* Collections */; };
96905EF65AED1B983A6B3ABC /* libPods-conduit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-conduit.a */; };
A86CEE5837C5BBF9CD8BB436 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 23B8187366DE26611566EC73 /* PrivacyInfo.xcprivacy */; };
@@ -55,6 +59,8 @@
293BD6812CB6FA3C006B04D3 /* Feedback.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = Feedback.swift; sourceTree = ""; tabWidth = 4; };
293BD6992CB84246006B04D3 /* FeedbackLogReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackLogReader.swift; sourceTree = ""; };
294484BC2CACF05C00451B61 /* Resources.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = Resources.swift; sourceTree = ""; tabWidth = 4; };
+ 2974F3672CDAA7F6000E8D3F /* LogHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogHandler.swift; sourceTree = ""; };
+ 2974F3692CDAC4CB000E8D3F /* AppLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLogger.swift; sourceTree = ""; };
29A9C9FC2CA5FFDD0054431A /* ConduitModule.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = ConduitModule.swift; sourceTree = ""; tabWidth = 4; };
29A9C9FE2CA600220054431A /* ConduitModule.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ConduitModule.mm; sourceTree = ""; };
29A9CA002CA602340054431A /* ConduitModule-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ConduitModule-Bridging-Header.h"; sourceTree = ""; };
@@ -80,6 +86,8 @@
67A1F0232CC027D600AE7C39 /* Collections in Frameworks */,
96905EF65AED1B983A6B3ABC /* libPods-conduit.a in Frameworks */,
29A9CA032CA715720054431A /* PsiphonTunnel.xcframework in Frameworks */,
+ 6773BCF42CD2C7B800CF4ADE /* Puppy in Frameworks */,
+ 2974F3652CD95382000E8D3F /* Logging in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -107,16 +115,26 @@
name = conduit;
sourceTree = "";
};
- 29A9C9FB2CA5FF1C0054431A /* ConduitModule */ = {
+ 2974F3662CDAA7DE000E8D3F /* Feedback */ = {
isa = PBXGroup;
children = (
293BD6812CB6FA3C006B04D3 /* Feedback.swift */,
+ 293BD6992CB84246006B04D3 /* FeedbackLogReader.swift */,
+ 2974F3672CDAA7F6000E8D3F /* LogHandler.swift */,
+ );
+ path = Feedback;
+ sourceTree = "";
+ };
+ 29A9C9FB2CA5FF1C0054431A /* ConduitModule */ = {
+ isa = PBXGroup;
+ children = (
+ 2974F3662CDAA7DE000E8D3F /* Feedback */,
29A9C9FC2CA5FFDD0054431A /* ConduitModule.swift */,
+ 2974F3692CDAC4CB000E8D3F /* AppLogger.swift */,
29A9C9FE2CA600220054431A /* ConduitModule.mm */,
29A9CA002CA602340054431A /* ConduitModule-Bridging-Header.h */,
29A9CA062CA715DF0054431A /* ConduitManager.swift */,
294484BC2CACF05C00451B61 /* Resources.swift */,
- 293BD6992CB84246006B04D3 /* FeedbackLogReader.swift */,
);
path = ConduitModule;
sourceTree = "";
@@ -218,6 +236,8 @@
name = conduit;
packageProductDependencies = (
67A1F0222CC027D600AE7C39 /* Collections */,
+ 6773BCF32CD2C7B800CF4ADE /* Puppy */,
+ 2974F3642CD95382000E8D3F /* Logging */,
);
productName = conduit;
productReference = 13B07F961A680F5B00A75B9A /* conduit.app */;
@@ -247,6 +267,8 @@
mainGroup = 83CBB9F61A601CBA00E9B192;
packageReferences = (
673EB98A2CC01E25003A3E39 /* XCRemoteSwiftPackageReference "swift-collections" */,
+ 6773BCF22CD2C7B800CF4ADE /* XCRemoteSwiftPackageReference "Puppy" */,
+ 2974F3632CD95382000E8D3F /* XCRemoteSwiftPackageReference "swift-log" */,
);
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
@@ -388,7 +410,9 @@
buildActionMask = 2147483647;
files = (
29A9C9FF2CA601D20054431A /* ConduitModule.mm in Sources */,
+ 2974F3682CDAA7F6000E8D3F /* LogHandler.swift in Sources */,
293BD6822CB6FA3C006B04D3 /* Feedback.swift in Sources */,
+ 2974F36A2CDAC4CB000E8D3F /* AppLogger.swift in Sources */,
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */,
@@ -417,6 +441,7 @@
"$(inherited)",
"FB_SONARKIT_ENABLED=1",
);
+ GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS = "";
INFOPLIST_FILE = conduit/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -432,6 +457,7 @@
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = ca.psiphon.conduit;
PRODUCT_NAME = conduit;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OBJC_BRIDGING_HEADER = "conduit/conduit-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -449,6 +475,10 @@
CODE_SIGN_ENTITLEMENTS = conduit/conduit.entitlements;
CURRENT_PROJECT_VERSION = 11;
DEVELOPMENT_TEAM = Q6HLNEX92A;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COCOAPODS=1",
+ );
INFOPLIST_FILE = conduit/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -530,10 +560,7 @@
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
- OTHER_LDFLAGS = (
- "$(inherited)",
- " ",
- );
+ OTHER_LDFLAGS = "$(inherited) ";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
@@ -591,10 +618,7 @@
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = NO;
- OTHER_LDFLAGS = (
- "$(inherited)",
- " ",
- );
+ OTHER_LDFLAGS = "$(inherited) ";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
@@ -626,6 +650,14 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
+ 2974F3632CD95382000E8D3F /* XCRemoteSwiftPackageReference "swift-log" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/apple/swift-log/";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 1.5.2;
+ };
+ };
673EB98A2CC01E25003A3E39 /* XCRemoteSwiftPackageReference "swift-collections" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-collections.git";
@@ -634,9 +666,27 @@
minimumVersion = 1.1.4;
};
};
+ 6773BCF22CD2C7B800CF4ADE /* XCRemoteSwiftPackageReference "Puppy" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/sushichop/Puppy";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 0.7.0;
+ };
+ };
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
+ 2974F3642CD95382000E8D3F /* Logging */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 2974F3632CD95382000E8D3F /* XCRemoteSwiftPackageReference "swift-log" */;
+ productName = Logging;
+ };
+ 6773BCF32CD2C7B800CF4ADE /* Puppy */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 6773BCF22CD2C7B800CF4ADE /* XCRemoteSwiftPackageReference "Puppy" */;
+ productName = Puppy;
+ };
67A1F0222CC027D600AE7C39 /* Collections */ = {
isa = XCSwiftPackageProductDependency;
package = 673EB98A2CC01E25003A3E39 /* XCRemoteSwiftPackageReference "swift-collections" */;
diff --git a/ios/conduit.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/conduit.xcworkspace/xcshareddata/swiftpm/Package.resolved
index cdd6991..7338b39 100644
--- a/ios/conduit.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/ios/conduit.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -1,6 +1,15 @@
{
- "originHash" : "51f90653b2c9f9f7064c0d52159b40bf7d222e5f314be23e62fe28520fec03db",
+ "originHash" : "67cf71632c338117d70d4442c3c3b300089147fbe270082672adb70d1ab91346",
"pins" : [
+ {
+ "identity" : "puppy",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/sushichop/Puppy",
+ "state" : {
+ "revision" : "b5af02a72a5a1f92a68e6eceee19cac804067ad9",
+ "version" : "0.7.0"
+ }
+ },
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
@@ -9,6 +18,15 @@
"revision" : "671108c96644956dddcd89dd59c203dcdb36cec7",
"version" : "1.1.4"
}
+ },
+ {
+ "identity" : "swift-log",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-log.git",
+ "state" : {
+ "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5",
+ "version" : "1.5.4"
+ }
}
],
"version" : 3