diff --git a/nym-vpn-android/core/src/main/java/net/nymtech/vpn/backend/NymBackend.kt b/nym-vpn-android/core/src/main/java/net/nymtech/vpn/backend/NymBackend.kt index ecbc1e20c0..c2e47ea792 100644 --- a/nym-vpn-android/core/src/main/java/net/nymtech/vpn/backend/NymBackend.kt +++ b/nym-vpn-android/core/src/main/java/net/nymtech/vpn/backend/NymBackend.kt @@ -130,7 +130,10 @@ class NymBackend private constructor(private val context: Context) : Backend, Tu ) { runCatching { startNetworkMonitorJob() - initLogger(null, LOG_LEVEL, false) + + initLogger(storagePath, LOG_LEVEL, config.sentryMonitoringEnabled) + ?: Timber.e("Failed to initialize backend logger") + initEnvironment(environment) configureLib(config, userAgent) initialized.complete(Unit) diff --git a/nym-vpn-apple/NymMixnetTunnel/PacketTunneProvider+Messaging.swift b/nym-vpn-apple/NymMixnetTunnel/PacketTunneProvider+Messaging.swift index 502904d3a0..17979563ae 100644 --- a/nym-vpn-apple/NymMixnetTunnel/PacketTunneProvider+Messaging.swift +++ b/nym-vpn-apple/NymMixnetTunnel/PacketTunneProvider+Messaging.swift @@ -55,6 +55,9 @@ extension PacketTunnelProvider { tunnelConnectingState: tunnelConnectingState, connectionInfoData: connectionInfoData ) + + // Should we clear tunnelActor.lastError here? + return try JSONEncoder().encode(statusResponse) } catch { logger.error("AppMessage: \(error.localizedDescription)") diff --git a/nym-vpn-apple/NymMixnetTunnel/PacketTunnelProvider.swift b/nym-vpn-apple/NymMixnetTunnel/PacketTunnelProvider.swift index 3ed9fc35d5..6187e5d56e 100644 --- a/nym-vpn-apple/NymMixnetTunnel/PacketTunnelProvider.swift +++ b/nym-vpn-apple/NymMixnetTunnel/PacketTunnelProvider.swift @@ -12,9 +12,15 @@ class PacketTunnelProvider: NEPacketTunnelProvider { let tunnelActor: TunnelActor lazy var logger = Logger(label: "MixnetTunnel") + var logInitFailure: String? override init() { - Self.configureLogger() + tunnelActor = TunnelActor() + + super.init() + + self.configureLogger() + LoggingSystem.bootstrap { label in let fileLogHandler = FileLogHandler(label: label, logFileManager: LogFileManager(logFileType: .tunnel)) #if DEBUG @@ -27,8 +33,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider { return fileLogHandler #endif } - - tunnelActor = TunnelActor() } override func startTunnel(options: [String: NSObject]? = nil) async throws { @@ -125,16 +129,20 @@ extension PacketTunnelProvider { } } - static func configureLogger() { + func configureLogger() { let logPath = LogFileManager.logFileURL(logFileType: .library)?.path() Task { let logLevel = await MainActor.run { ConfigurationManager.shared.debugLevel } - await initLogger( - path: logPath, - debugLevel: logLevel, - sentryMonitoring: true - ) + do { + try await initLogger( + path: logPath, + debugLevel: logLevel, + sentryMonitoring: true + ) + } catch { + self.logInitFailure = error.localizedDescription + } } } } diff --git a/nym-vpn-apple/NymMixnetTunnel/TunnelActor.swift b/nym-vpn-apple/NymMixnetTunnel/TunnelActor.swift index aabab83430..df0a5445a9 100644 --- a/nym-vpn-apple/NymMixnetTunnel/TunnelActor.swift +++ b/nym-vpn-apple/NymMixnetTunnel/TunnelActor.swift @@ -41,6 +41,12 @@ actor TunnelActor { func setTunnelProvider(_ tunnelProvider: NEPacketTunnelProvider?) { self.tunnelProvider = tunnelProvider + + if let provider = tunnelProvider as? PacketTunnelProvider, + let failure = provider.logInitFailure + { + lastError = .createLogFailed(failure) + } } private func setCurrentState(_ state: TunnelState) async { diff --git a/nym-vpn-apple/Services/Sources/Services/Tunnels/Errors/PacketTunnelProviderError.swift b/nym-vpn-apple/Services/Sources/Services/Tunnels/Errors/PacketTunnelProviderError.swift index 16cb10c54c..a39cd0cc9f 100644 --- a/nym-vpn-apple/Services/Sources/Services/Tunnels/Errors/PacketTunnelProviderError.swift +++ b/nym-vpn-apple/Services/Sources/Services/Tunnels/Errors/PacketTunnelProviderError.swift @@ -1,6 +1,7 @@ import Foundation public enum PacketTunnelProviderError: String, Error { + case failedToInitLogging case invalidSavedConfiguration case backendStartFailure case noCredentialDataDir diff --git a/nym-vpn-apple/ServicesIOS/Sources/ErrorHandler/VPNErrorReason.swift b/nym-vpn-apple/ServicesIOS/Sources/ErrorHandler/VPNErrorReason.swift index 396c207057..164a077804 100644 --- a/nym-vpn-apple/ServicesIOS/Sources/ErrorHandler/VPNErrorReason.swift +++ b/nym-vpn-apple/ServicesIOS/Sources/ErrorHandler/VPNErrorReason.swift @@ -5,6 +5,7 @@ import Theme public enum VPNErrorReason: LocalizedError { case initialization(details: String) + case createLogFile(details: String) case internalError(details: String) case storage(details: String) case networkConnectionError(details: String) @@ -44,6 +45,8 @@ public enum VPNErrorReason: LocalizedError { switch vpnError { case let .Initialization(details: details): self = .initialization(details: details) + case let .CreateLogFile(details: details): + self = .createLogFile(details: details) case let .InternalError(details: details): self = .internalError(details: details) case let .Storage(details: details): @@ -231,6 +234,8 @@ public enum VPNErrorReason: LocalizedError { switch errorReason { case .initialization: self = .initialization(details: nsError.userInfo["details"] as? String ?? Self.somethingWentWrong) + case .createLogFile: + self = .createLogFile(details: nsError.userInfo["details"] as? String ?? Self.somethingWentWrong) case .internalError: self = .internalError(details: nsError.userInfo["details"] as? String ?? Self.somethingWentWrong) case .storage: @@ -317,6 +322,8 @@ extension VPNErrorReason { switch self { case let .internalError(details): details + case let .createLogFile(details): + details case let .storage(details): details case let .networkConnectionError(details): @@ -384,6 +391,7 @@ extension VPNErrorReason: Equatable { /// The VPNErrorReasonCode mirrors the error codes as raw integers and can be constructed from a VPNErrorReason. enum VPNErrorReasonCode: Int, RawRepresentable { case initialization + case createLogFile case internalError case storage case networkConnectionError @@ -416,6 +424,8 @@ enum VPNErrorReasonCode: Int, RawRepresentable { switch vpnErrorReason { case .initialization: self = .initialization + case .createLogFile: + self = .createLogFile case .internalError: self = .internalError case .storage: diff --git a/nym-vpn-apple/ServicesMutual/Sources/ErrorReason/ErrorReason.swift b/nym-vpn-apple/ServicesMutual/Sources/ErrorReason/ErrorReason.swift index fd0fe51ac4..259de2fb13 100644 --- a/nym-vpn-apple/ServicesMutual/Sources/ErrorReason/ErrorReason.swift +++ b/nym-vpn-apple/ServicesMutual/Sources/ErrorReason/ErrorReason.swift @@ -12,6 +12,8 @@ public enum ErrorReason: LocalizedError, Codable { case offline case noAccountStored case noDeviceStored + // PacketTunnelProvider + case createLogFailed(String) // Tunnel case setFirewallPolicy case setRouting @@ -114,6 +116,8 @@ public enum ErrorReason: LocalizedError, Codable { self = .noAccountStored case .noDeviceStored: self = .noDeviceStored + case .createLogFailed: + self = .createLogFailed("Unknown") case .setFirewallPolicy: self = .setFirewallPolicy case .setRouting: @@ -201,6 +205,8 @@ extension ErrorReason { private extension ErrorReason { var description: String { switch self { + case .createLogFailed(let message): + "errorReason.createLogFailed".localizedString + ": " + message case .setFirewallPolicy: "errorReason.firewall".localizedString case .setRouting: @@ -285,6 +291,7 @@ enum ErrorReasonCode: Int, RawRepresentable { case offline case noAccountStored case noDeviceStored + case createLogFailed case setFirewallPolicy case setRouting case setDns @@ -323,6 +330,8 @@ enum ErrorReasonCode: Int, RawRepresentable { self = .noAccountStored case .noDeviceStored: self = .noDeviceStored + case .createLogFailed: + self = .createLogFailed case .internalUnknown: self = .internalUnknown case .sameEntryAndExitGateway: diff --git a/nym-vpn-core/crates/nym-firewall/src/net.rs b/nym-vpn-core/crates/nym-firewall/src/net.rs index 72a5471ffd..cdad3421b6 100644 --- a/nym-vpn-core/crates/nym-firewall/src/net.rs +++ b/nym-vpn-core/crates/nym-firewall/src/net.rs @@ -25,7 +25,10 @@ pub static ALLOWED_LAN_NETS: LazyLock<[IpNetwork; 6]> = LazyLock::new(|| { ] }); /// When "allow local network" is enabled the app will allow traffic to these networks. -#[cfg_attr(target_os = "windows", allow(unused))] +#[cfg_attr( + any(target_os = "windows", target_os = "android", target_os = "ios"), + allow(unused) +)] pub static ALLOWED_LAN_MULTICAST_NETS: LazyLock<[IpNetwork; 8]> = LazyLock::new(|| { [ // Local network broadcast. Not routable diff --git a/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/android.rs b/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/android.rs index 2b3d1441e3..ee3ed0833a 100644 --- a/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/android.rs +++ b/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/android.rs @@ -3,7 +3,9 @@ use log::LevelFilter; -pub(crate) fn init_logs(level: String) { +use crate::error::VpnError; + +pub(crate) fn init_logs(level: String) -> Result<(), VpnError> { use android_logger::{Config, FilterBuilder}; let levels = level + ",tungstenite=warn,mio=warn,tokio_tungstenite=warn"; @@ -16,5 +18,6 @@ pub(crate) fn init_logs(level: String) { .with_tag("libnymvpn") .with_filter(FilterBuilder::new().parse(levels.as_str()).build()), ); - tracing::debug!("Logger initialized"); + + Ok(()) } diff --git a/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/error.rs b/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/error.rs index 08d98dd758..2805246bd1 100644 --- a/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/error.rs +++ b/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/error.rs @@ -13,6 +13,9 @@ pub enum VpnError { #[error("initialization error: {details}")] Initialization { details: String }, + #[error("failed to create log file: {details}")] + CreateLogFile { details: String }, + #[error("storage error: {details}")] Storage { details: String }, diff --git a/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/swift.rs b/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/ios.rs similarity index 77% rename from nym-vpn-core/crates/nym-vpn-lib-uniffi/src/swift.rs rename to nym-vpn-core/crates/nym-vpn-lib-uniffi/src/ios.rs index f928f0d8c7..5aa0555ac7 100644 --- a/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/swift.rs +++ b/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/ios.rs @@ -11,7 +11,9 @@ use tracing_subscriber::{ util::SubscriberInitExt, }; -pub fn init_logs(level: String, path: Option, sentry: bool) { +use crate::error::VpnError; + +pub fn init_logs(level: String, path: Option, sentry: bool) -> Result<(), VpnError> { let oslogger_layer = OsLogger::new("net.nymtech.vpn.agent", "default"); let filter = tracing_subscriber::EnvFilter::builder() @@ -41,36 +43,38 @@ pub fn init_logs(level: String, path: Option, sentry: bool) { let registry = Registry::default().with(oslogger_layer); let mut layers = Vec::new(); - let file_layer = path.as_ref().and_then(|path| { + + if let Some(path) = &path { // Ensure log directory exists if let Some(parent) = path.parent() - && !parent.exists() && let Err(e) = std::fs::create_dir_all(parent) { - eprintln!("Failed to create log directory {}: {e}", parent.display()); + return Err(VpnError::CreateLogFile { + details: format!("Failed to create log directory {}: {e}", parent.display()), + }); } // Attempting to get the tracing_appending solution to work was not successful. // Falling back to a more basic solution that does not support log rotation, for now. // Attempt to open the log file for writing - OpenOptions::new() + let file = OpenOptions::new() .write(true) .create(true) .truncate(true) .open(path) - .ok() - .map(|file| { - fmtLayer::default() - .with_writer(file) - .with_ansi(false) - .compact() - }) - }); + .map_err(|e| VpnError::CreateLogFile { + details: format!("Failed to open log file {}: {e}", path.display()), + })?; + + let file_layer = fmtLayer::default() + .with_writer(file) + .with_ansi(false) + .compact(); - if let Some(file_layer) = file_layer { layers.push(file_layer.boxed()); - }; + } + if sentry { let layer = sentry_tracing::layer().event_filter(|md| match md.level() { &Level::ERROR | &Level::WARN => sentry_tracing::EventFilter::Event, @@ -80,11 +84,11 @@ pub fn init_logs(level: String, path: Option, sentry: bool) { layers.push(layer.boxed()); } - let result = registry.with(layers).with(filter).try_init(); - - if let Err(err) = result { - eprintln!("Failed to initialize logger: {err}"); - } else { - tracing::debug!("Logger initialized level: {level}, path?:{path:?}"); - } + registry + .with(layers) + .with(filter) + .try_init() + .map_err(|err| VpnError::CreateLogFile { + details: format!("Failed to initialize logger: {err}"), + }) } diff --git a/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/lib.rs b/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/lib.rs index 85a7ff421b..6e3b2ba3f4 100644 --- a/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/lib.rs +++ b/nym-vpn-core/crates/nym-vpn-lib-uniffi/src/lib.rs @@ -46,10 +46,11 @@ uniffi::setup_scaffolding!(); #[cfg(target_os = "android")] pub mod android; +#[cfg(target_os = "ios")] +pub mod ios; + pub(crate) mod error; pub mod helpers; -#[cfg(any(target_os = "ios", target_os = "macos"))] -pub mod swift; mod account; mod environment; @@ -204,7 +205,11 @@ async fn configure_lib_for_main_process(user_agent: UserAgent) -> Result<(), Vpn Ok(()) } -async fn init_logger(path: Option, debug_level: Option, sentry_monitoring: bool) { +async fn init_logger( + path: Option, + debug_level: Option, + sentry_monitoring: bool, +) -> Result<(), VpnError> { let default_log_level = env::var("RUST_LOG").unwrap_or("info".to_string()); let log_level = debug_level.unwrap_or(default_log_level); tracing::info!("Setting log level: {log_level}, path?: {path:?}"); @@ -214,10 +219,19 @@ async fn init_logger(path: Option, debug_level: Option, sentry_ let mut guard = SENTRY_CLIENT.lock().await; *guard = sentry_monitoring::init(); } + #[cfg(target_os = "ios")] - swift::init_logs(log_level, path, sentry_monitoring); + { + ios::init_logs(log_level, path, sentry_monitoring) + } #[cfg(target_os = "android")] - android::init_logs(log_level); + { + android::init_logs(log_level) + } + #[cfg(not(any(target_os = "ios", target_os = "android")))] + { + Ok(()) + } } /// Additional extra function for when only want to set the logger without initializing the @@ -228,8 +242,8 @@ pub async fn initLogger( path: Option, debug_level: Option, sentry_monitoring: bool, -) { - init_logger(path, debug_level, sentry_monitoring).await; +) -> Result<(), VpnError> { + init_logger(path, debug_level, sentry_monitoring).await } /// Returns the system messages for the current network environment