Add app log in Diagnostics screen (#234)
This commit is contained in:
parent
fbc17877b1
commit
54c53707e0
|
@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
- IVPN provider.
|
- IVPN provider.
|
||||||
- OpenVPN: Support for `--route-nopull`. [#230](https://github.com/passepartoutvpn/passepartout-apple/pull/230)
|
- OpenVPN: Support for `--route-nopull`. [#230](https://github.com/passepartoutvpn/passepartout-apple/pull/230)
|
||||||
|
- App log in Diagnostics screen. [#234](https://github.com/passepartoutvpn/passepartout-apple/pull/234)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
@ -140,36 +140,40 @@ extension Constants {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Log {
|
enum Log {
|
||||||
|
enum App {
|
||||||
|
static let url = containerURL(filename: "App.log")
|
||||||
|
|
||||||
|
static let format = "$DHH:mm:ss.SSS$d $C$L$c $N.$F:$l - $M"
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Tunnel {
|
||||||
|
static let path = containerPath(filename: "Tunnel.log")
|
||||||
|
|
||||||
|
static let format = "$DHH:mm:ss$d - $M"
|
||||||
|
}
|
||||||
|
|
||||||
private static let parentPath = "Library/Caches"
|
private static let parentPath = "Library/Caches"
|
||||||
|
|
||||||
private static func containerLogURL(filename: String) -> URL {
|
static let level: SwiftyBeaver.Level = {
|
||||||
Files.containerURL
|
|
||||||
.appendingPathComponent(parentPath)
|
|
||||||
.appendingPathComponent(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func containerLogPath(filename: String) -> String {
|
|
||||||
"\(parentPath)/\(filename)"
|
|
||||||
}
|
|
||||||
|
|
||||||
static let appLogURL = containerLogURL(filename: "App.log")
|
|
||||||
|
|
||||||
static let tunnelLogPath = containerLogPath(filename: "Tunnel.log")
|
|
||||||
|
|
||||||
static let logLevel: SwiftyBeaver.Level = {
|
|
||||||
guard let levelString = ProcessInfo.processInfo.environment["LOG_LEVEL"], let levelNum = Int(levelString) else {
|
guard let levelString = ProcessInfo.processInfo.environment["LOG_LEVEL"], let levelNum = Int(levelString) else {
|
||||||
return .info
|
return .info
|
||||||
}
|
}
|
||||||
return .init(rawValue: levelNum) ?? .info
|
return .init(rawValue: levelNum) ?? .info
|
||||||
}()
|
}()
|
||||||
|
|
||||||
static let logFormat = "$DHH:mm:ss.SSS$d $C$L$c $N.$F:$l - $M"
|
static let maxBytes = 100000
|
||||||
|
|
||||||
static let tunnelLogFormat = "$DHH:mm:ss$d - $M"
|
static let refreshInterval: TimeInterval = 5.0
|
||||||
|
|
||||||
static let tunnelLogMaxBytes = 100000
|
private static func containerURL(filename: String) -> URL {
|
||||||
|
Files.containerURL
|
||||||
|
.appendingPathComponent(parentPath)
|
||||||
|
.appendingPathComponent(filename)
|
||||||
|
}
|
||||||
|
|
||||||
static let tunnelLogRefreshInterval: TimeInterval = 5.0
|
private static func containerPath(filename: String) -> String {
|
||||||
|
"\(parentPath)/\(filename)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum URLs {
|
enum URLs {
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import PassepartoutLibrary
|
||||||
|
|
||||||
extension AppContext {
|
extension AppContext {
|
||||||
static let shared = AppContext(coreContext: .shared)
|
static let shared = AppContext(coreContext: .shared)
|
||||||
|
@ -32,3 +33,7 @@ extension AppContext {
|
||||||
extension ProductManager {
|
extension ProductManager {
|
||||||
static let shared = AppContext.shared.productManager
|
static let shared = AppContext.shared.productManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension LogManager {
|
||||||
|
static let shared = AppContext.shared.logManager
|
||||||
|
}
|
||||||
|
|
|
@ -29,29 +29,29 @@ import PassepartoutLibrary
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
class AppContext {
|
class AppContext {
|
||||||
private let logManager: LogManager
|
let logManager: LogManager
|
||||||
|
|
||||||
private let reviewer: Reviewer
|
|
||||||
|
|
||||||
let productManager: ProductManager
|
let productManager: ProductManager
|
||||||
|
|
||||||
|
private let reviewer: Reviewer
|
||||||
|
|
||||||
private var cancellables: Set<AnyCancellable> = []
|
private var cancellables: Set<AnyCancellable> = []
|
||||||
|
|
||||||
init(coreContext: CoreContext) {
|
init(coreContext: CoreContext) {
|
||||||
logManager = LogManager(logFile: Constants.Log.appLogURL)
|
logManager = LogManager(logFile: Constants.Log.App.url)
|
||||||
logManager.logLevel = Constants.Log.logLevel
|
logManager.logLevel = Constants.Log.level
|
||||||
logManager.logFormat = Constants.Log.logFormat
|
logManager.logFormat = Constants.Log.App.format
|
||||||
logManager.configureLogging()
|
logManager.configureLogging()
|
||||||
pp_log.info("Logging to: \(logManager.logFile!)")
|
pp_log.info("Logging to: \(logManager.logFile!)")
|
||||||
|
|
||||||
reviewer = Reviewer()
|
|
||||||
reviewer.eventCountBeforeRating = Constants.Rating.eventCount
|
|
||||||
|
|
||||||
productManager = ProductManager(
|
productManager = ProductManager(
|
||||||
appType: Constants.InApp.appType,
|
appType: Constants.InApp.appType,
|
||||||
buildProducts: Constants.InApp.buildProducts
|
buildProducts: Constants.InApp.buildProducts
|
||||||
)
|
)
|
||||||
|
|
||||||
|
reviewer = Reviewer()
|
||||||
|
reviewer.eventCountBeforeRating = Constants.Rating.eventCount
|
||||||
|
|
||||||
// post
|
// post
|
||||||
|
|
||||||
configureObjects(coreContext: coreContext)
|
configureObjects(coreContext: coreContext)
|
||||||
|
|
|
@ -28,6 +28,8 @@ import Combine
|
||||||
import PassepartoutLibrary
|
import PassepartoutLibrary
|
||||||
|
|
||||||
struct DebugLogView: View {
|
struct DebugLogView: View {
|
||||||
|
private let title: String
|
||||||
|
|
||||||
private let url: URL
|
private let url: URL
|
||||||
|
|
||||||
private let timer: AnyPublisher<Date, Never>
|
private let timer: AnyPublisher<Date, Never>
|
||||||
|
@ -36,7 +38,7 @@ struct DebugLogView: View {
|
||||||
|
|
||||||
@State private var isSharing = false
|
@State private var isSharing = false
|
||||||
|
|
||||||
private let maxBytes = UInt64(Constants.Log.tunnelLogMaxBytes)
|
private let maxBytes = UInt64(Constants.Log.maxBytes)
|
||||||
|
|
||||||
private let appName = Constants.Global.appName
|
private let appName = Constants.Global.appName
|
||||||
|
|
||||||
|
@ -44,11 +46,17 @@ struct DebugLogView: View {
|
||||||
|
|
||||||
private let shareFilename = Unlocalized.Issues.Filenames.debugLog
|
private let shareFilename = Unlocalized.Issues.Filenames.debugLog
|
||||||
|
|
||||||
init(url: URL, updateInterval: TimeInterval) {
|
init(title: String, url: URL, refreshInterval: TimeInterval?) {
|
||||||
|
self.title = title
|
||||||
self.url = url
|
self.url = url
|
||||||
timer = Timer.TimerPublisher(interval: updateInterval, runLoop: .main, mode: .common)
|
if let refreshInterval = refreshInterval {
|
||||||
|
timer = Timer.TimerPublisher(interval: refreshInterval, runLoop: .main, mode: .common)
|
||||||
.autoconnect()
|
.autoconnect()
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
} else {
|
||||||
|
timer = Empty(outputType: Date.self, failureType: Never.self)
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -78,7 +86,7 @@ struct DebugLogView: View {
|
||||||
#endif
|
#endif
|
||||||
.edgesIgnoringSafeArea([.leading, .trailing])
|
.edgesIgnoringSafeArea([.leading, .trailing])
|
||||||
.onReceive(timer, perform: refreshLog)
|
.onReceive(timer, perform: refreshLog)
|
||||||
.navigationTitle(L10n.DebugLog.title)
|
.navigationTitle(title)
|
||||||
.themeDebugLogStyle()
|
.themeDebugLogStyle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,8 +57,6 @@ extension DiagnosticsView {
|
||||||
|
|
||||||
private let vpnProtocol: VPNProtocolType = .openVPN
|
private let vpnProtocol: VPNProtocolType = .openVPN
|
||||||
|
|
||||||
private let logUpdateInterval = Constants.Log.tunnelLogRefreshInterval
|
|
||||||
|
|
||||||
init(providerName: ProviderName?) {
|
init(providerName: ProviderName?) {
|
||||||
providerManager = .shared
|
providerManager = .shared
|
||||||
vpnManager = .shared
|
vpnManager = .shared
|
||||||
|
@ -108,16 +106,10 @@ extension DiagnosticsView {
|
||||||
|
|
||||||
private var debugLogSection: some View {
|
private var debugLogSection: some View {
|
||||||
Section {
|
Section {
|
||||||
let url = debugLogURL
|
DebugLogSection(appLogURL: appLogURL, tunnelLogURL: tunnelLogURL)
|
||||||
NavigationLink(L10n.DebugLog.title) {
|
|
||||||
url.map {
|
|
||||||
DebugLogView(
|
|
||||||
url: $0,
|
|
||||||
updateInterval: logUpdateInterval
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.disabled(url == nil)
|
|
||||||
Toggle(L10n.Diagnostics.Items.MasksPrivateData.caption, isOn: $vpnManager.masksPrivateData)
|
Toggle(L10n.Diagnostics.Items.MasksPrivateData.caption, isOn: $vpnManager.masksPrivateData)
|
||||||
|
} header: {
|
||||||
|
Text(L10n.DebugLog.title)
|
||||||
} footer: {
|
} footer: {
|
||||||
Text(L10n.Diagnostics.Sections.DebugLog.footer)
|
Text(L10n.Diagnostics.Sections.DebugLog.footer)
|
||||||
}
|
}
|
||||||
|
@ -161,7 +153,11 @@ extension DiagnosticsView.OpenVPNView {
|
||||||
return cfg.builder(withFallbacks: false)
|
return cfg.builder(withFallbacks: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var debugLogURL: URL? {
|
private var appLogURL: URL? {
|
||||||
|
LogManager.shared.logFile
|
||||||
|
}
|
||||||
|
|
||||||
|
private var tunnelLogURL: URL? {
|
||||||
vpnManager.debugLogURL(forProtocol: vpnProtocol)
|
vpnManager.debugLogURL(forProtocol: vpnProtocol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,6 @@ extension DiagnosticsView {
|
||||||
|
|
||||||
private let providerName: ProviderName?
|
private let providerName: ProviderName?
|
||||||
|
|
||||||
private let logUpdateInterval = Constants.Log.tunnelLogRefreshInterval
|
|
||||||
|
|
||||||
init(providerName: ProviderName?) {
|
init(providerName: ProviderName?) {
|
||||||
vpnManager = .shared
|
vpnManager = .shared
|
||||||
self.providerName = providerName
|
self.providerName = providerName
|
||||||
|
@ -43,21 +41,21 @@ extension DiagnosticsView {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
Section {
|
Section {
|
||||||
let url = debugLogURL
|
DebugLogSection(appLogURL: appLogURL, tunnelLogURL: tunnelLogURL)
|
||||||
NavigationLink(L10n.DebugLog.title) {
|
} header: {
|
||||||
url.map {
|
Text(L10n.DebugLog.title)
|
||||||
DebugLogView(
|
|
||||||
url: $0,
|
|
||||||
updateInterval: logUpdateInterval
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.disabled(url == nil)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private var debugLogURL: URL? {
|
}
|
||||||
vpnManager.debugLogURL(forProtocol: .wireGuard)
|
|
||||||
}
|
extension DiagnosticsView.WireGuardView {
|
||||||
|
private var appLogURL: URL? {
|
||||||
|
LogManager.shared.logFile
|
||||||
|
}
|
||||||
|
|
||||||
|
private var tunnelLogURL: URL? {
|
||||||
|
vpnManager.debugLogURL(forProtocol: .wireGuard)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,3 +47,46 @@ struct DiagnosticsView: View {
|
||||||
}.navigationTitle(L10n.Diagnostics.title)
|
}.navigationTitle(L10n.Diagnostics.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension DiagnosticsView {
|
||||||
|
struct DebugLogSection: View {
|
||||||
|
let appLogURL: URL?
|
||||||
|
|
||||||
|
let tunnelLogURL: URL?
|
||||||
|
|
||||||
|
private let refreshInterval = Constants.Log.refreshInterval
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
appLink
|
||||||
|
tunnelLink
|
||||||
|
}
|
||||||
|
|
||||||
|
private var appLink: some View {
|
||||||
|
navigationLink(
|
||||||
|
withTitle: L10n.Diagnostics.Items.AppLog.title,
|
||||||
|
url: appLogURL,
|
||||||
|
refreshInterval: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var tunnelLink: some View {
|
||||||
|
navigationLink(
|
||||||
|
withTitle: Unlocalized.VPN.vpn,
|
||||||
|
url: tunnelLogURL,
|
||||||
|
refreshInterval: refreshInterval
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func navigationLink(withTitle title: String, url: URL?, refreshInterval: TimeInterval?) -> some View {
|
||||||
|
NavigationLink(title) {
|
||||||
|
url.map {
|
||||||
|
DebugLogView(
|
||||||
|
title: title,
|
||||||
|
url: $0,
|
||||||
|
refreshInterval: refreshInterval
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.disabled(url == nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -210,6 +210,10 @@ internal enum L10n {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
internal enum Items {
|
internal enum Items {
|
||||||
|
internal enum AppLog {
|
||||||
|
/// App
|
||||||
|
internal static let title = L10n.tr("Localizable", "diagnostics.items.app_log.title", fallback: "App")
|
||||||
|
}
|
||||||
internal enum MasksPrivateData {
|
internal enum MasksPrivateData {
|
||||||
/// Mask network data
|
/// Mask network data
|
||||||
internal static let caption = L10n.tr("Localizable", "diagnostics.items.masks_private_data.caption", fallback: "Mask network data")
|
internal static let caption = L10n.tr("Localizable", "diagnostics.items.masks_private_data.caption", fallback: "Mask network data")
|
||||||
|
|
|
@ -114,8 +114,8 @@ class CoreContext {
|
||||||
|
|
||||||
private func configureObjects() {
|
private func configureObjects() {
|
||||||
providerManager.rateLimitMilliseconds = Constants.RateLimit.providerManager
|
providerManager.rateLimitMilliseconds = Constants.RateLimit.providerManager
|
||||||
vpnManager.tunnelLogPath = Constants.Log.tunnelLogPath
|
vpnManager.tunnelLogPath = Constants.Log.Tunnel.path
|
||||||
vpnManager.tunnelLogFormat = Constants.Log.tunnelLogFormat
|
vpnManager.tunnelLogFormat = Constants.Log.Tunnel.format
|
||||||
|
|
||||||
profileManager.observeUpdates()
|
profileManager.observeUpdates()
|
||||||
vpnManager.observeUpdates()
|
vpnManager.observeUpdates()
|
||||||
|
|
|
@ -255,6 +255,7 @@
|
||||||
"diagnostics.title" = "Diagnose";
|
"diagnostics.title" = "Diagnose";
|
||||||
"diagnostics.sections.debug_log.footer" = "Zensier-Status wird aktiv nach erneutem Verbinden. Netzwerk-Daten sind Hostnamen, IP-Adressen, Routingtabellen, SSID. Zugangsdaten und Private Keys werden nie gelogged.";
|
"diagnostics.sections.debug_log.footer" = "Zensier-Status wird aktiv nach erneutem Verbinden. Netzwerk-Daten sind Hostnamen, IP-Adressen, Routingtabellen, SSID. Zugangsdaten und Private Keys werden nie gelogged.";
|
||||||
"diagnostics.items.server_configuration.caption" = "Serverkonfiguration";
|
"diagnostics.items.server_configuration.caption" = "Serverkonfiguration";
|
||||||
|
"diagnostics.items.app_log.title" = "App";
|
||||||
"diagnostics.items.masks_private_data.caption" = "Netzwerkdaten zensieren";
|
"diagnostics.items.masks_private_data.caption" = "Netzwerkdaten zensieren";
|
||||||
"diagnostics.items.report_issue.caption" = "Verbindungsproblem melden";
|
"diagnostics.items.report_issue.caption" = "Verbindungsproblem melden";
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,7 @@
|
||||||
"diagnostics.title" = "Διαγνωστικά";
|
"diagnostics.title" = "Διαγνωστικά";
|
||||||
"diagnostics.sections.debug_log.footer" = "Η κατάσταση κάλυψης θα είναι αποτελεσματική μετά την επανασύνδεση. Τα δεδομένα δικτύου είναι του διακομιστή, διευθύνσεις IP, δρομολόγηση και SSID. Τα διαπιστευτήρια και τα ιδιωτικά κλειδιά δεν καταγράφονται ανεξάρτητα.";
|
"diagnostics.sections.debug_log.footer" = "Η κατάσταση κάλυψης θα είναι αποτελεσματική μετά την επανασύνδεση. Τα δεδομένα δικτύου είναι του διακομιστή, διευθύνσεις IP, δρομολόγηση και SSID. Τα διαπιστευτήρια και τα ιδιωτικά κλειδιά δεν καταγράφονται ανεξάρτητα.";
|
||||||
"diagnostics.items.server_configuration.caption" = "Ρυθμίσεις Διακομιστή";
|
"diagnostics.items.server_configuration.caption" = "Ρυθμίσεις Διακομιστή";
|
||||||
|
"diagnostics.items.app_log.title" = "Εφαρμογή";
|
||||||
"diagnostics.items.masks_private_data.caption" = "Μάσκα δεδομένα δικτύου";
|
"diagnostics.items.masks_private_data.caption" = "Μάσκα δεδομένα δικτύου";
|
||||||
"diagnostics.items.report_issue.caption" = "Αναφορά ζητήματος συνδεσιμότητας";
|
"diagnostics.items.report_issue.caption" = "Αναφορά ζητήματος συνδεσιμότητας";
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,7 @@
|
||||||
"diagnostics.title" = "Diagnostics";
|
"diagnostics.title" = "Diagnostics";
|
||||||
"diagnostics.sections.debug_log.footer" = "Masking status will be effective after reconnecting. Network data are hostnames, IP addresses, routing, SSID. Credentials and private keys are not logged regardless.";
|
"diagnostics.sections.debug_log.footer" = "Masking status will be effective after reconnecting. Network data are hostnames, IP addresses, routing, SSID. Credentials and private keys are not logged regardless.";
|
||||||
"diagnostics.items.server_configuration.caption" = "Server configuration";
|
"diagnostics.items.server_configuration.caption" = "Server configuration";
|
||||||
|
"diagnostics.items.app_log.title" = "App";
|
||||||
"diagnostics.items.masks_private_data.caption" = "Mask network data";
|
"diagnostics.items.masks_private_data.caption" = "Mask network data";
|
||||||
"diagnostics.items.report_issue.caption" = "Report connectivity issue";
|
"diagnostics.items.report_issue.caption" = "Report connectivity issue";
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,7 @@
|
||||||
"diagnostics.title" = "Diagnósticos";
|
"diagnostics.title" = "Diagnósticos";
|
||||||
"diagnostics.sections.debug_log.footer" = "El estado de ocultación será efectivo tras reconectar. Los datos de red son hostnames, direcciones IP, routing, SSID. Las credenciales y las claves privadas no son registrados a pesar.";
|
"diagnostics.sections.debug_log.footer" = "El estado de ocultación será efectivo tras reconectar. Los datos de red son hostnames, direcciones IP, routing, SSID. Las credenciales y las claves privadas no son registrados a pesar.";
|
||||||
"diagnostics.items.server_configuration.caption" = "Configuración del servidor";
|
"diagnostics.items.server_configuration.caption" = "Configuración del servidor";
|
||||||
|
"diagnostics.items.app_log.title" = "App";
|
||||||
"diagnostics.items.masks_private_data.caption" = "Ocultar datos de red";
|
"diagnostics.items.masks_private_data.caption" = "Ocultar datos de red";
|
||||||
"diagnostics.items.report_issue.caption" = "Reportar problema de conectividad";
|
"diagnostics.items.report_issue.caption" = "Reportar problema de conectividad";
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,7 @@
|
||||||
"diagnostics.title" = "Diagnostiques";
|
"diagnostics.title" = "Diagnostiques";
|
||||||
"diagnostics.sections.debug_log.footer" = "Camouflage du status sera effectif après la reconnection. Les données réseaux sont les noms d'hôtes, adresses IP, routage, SSID. Les identifiants et clés privés ne sont pas enregistrés.";
|
"diagnostics.sections.debug_log.footer" = "Camouflage du status sera effectif après la reconnection. Les données réseaux sont les noms d'hôtes, adresses IP, routage, SSID. Les identifiants et clés privés ne sont pas enregistrés.";
|
||||||
"diagnostics.items.server_configuration.caption" = "Configuration serveur";
|
"diagnostics.items.server_configuration.caption" = "Configuration serveur";
|
||||||
|
"diagnostics.items.app_log.title" = "Application";
|
||||||
"diagnostics.items.masks_private_data.caption" = "Masquer les données de réseau";
|
"diagnostics.items.masks_private_data.caption" = "Masquer les données de réseau";
|
||||||
"diagnostics.items.report_issue.caption" = "Rapporter un problème de connection";
|
"diagnostics.items.report_issue.caption" = "Rapporter un problème de connection";
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,7 @@
|
||||||
"diagnostics.title" = "Diagnostica";
|
"diagnostics.title" = "Diagnostica";
|
||||||
"diagnostics.sections.debug_log.footer" = "Il mascheramento sarà effettivo dopo una riconnessione. I dati di rete sono hostname, indirizzi IP, routing, SSID. Credenziali e chiavi private non sono registrati in ogni caso.";
|
"diagnostics.sections.debug_log.footer" = "Il mascheramento sarà effettivo dopo una riconnessione. I dati di rete sono hostname, indirizzi IP, routing, SSID. Credenziali e chiavi private non sono registrati in ogni caso.";
|
||||||
"diagnostics.items.server_configuration.caption" = "Configurazione del server";
|
"diagnostics.items.server_configuration.caption" = "Configurazione del server";
|
||||||
|
"diagnostics.items.app_log.title" = "App";
|
||||||
"diagnostics.items.masks_private_data.caption" = "Maschera dati rete";
|
"diagnostics.items.masks_private_data.caption" = "Maschera dati rete";
|
||||||
"diagnostics.items.report_issue.caption" = "Segnala problema connettività";
|
"diagnostics.items.report_issue.caption" = "Segnala problema connettività";
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,7 @@
|
||||||
"diagnostics.title" = "Diagnose";
|
"diagnostics.title" = "Diagnose";
|
||||||
"diagnostics.sections.debug_log.footer" = "De maskeerstatus is effectief na opnieuw verbinden. Netwerkgegevens zijn hostnamen, IP-adressen, routing, SSID's. Inloggegevens en privésleutels worden niet geregistreerd.";
|
"diagnostics.sections.debug_log.footer" = "De maskeerstatus is effectief na opnieuw verbinden. Netwerkgegevens zijn hostnamen, IP-adressen, routing, SSID's. Inloggegevens en privésleutels worden niet geregistreerd.";
|
||||||
"diagnostics.items.server_configuration.caption" = "Server configuratie";
|
"diagnostics.items.server_configuration.caption" = "Server configuratie";
|
||||||
|
"diagnostics.items.app_log.title" = "App";
|
||||||
"diagnostics.items.masks_private_data.caption" = "Netwerkgegevens maskeren";
|
"diagnostics.items.masks_private_data.caption" = "Netwerkgegevens maskeren";
|
||||||
"diagnostics.items.report_issue.caption" = "Probleem met connectiviteit melden";
|
"diagnostics.items.report_issue.caption" = "Probleem met connectiviteit melden";
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,7 @@
|
||||||
"diagnostics.title" = "Diagnostyka";
|
"diagnostics.title" = "Diagnostyka";
|
||||||
"diagnostics.sections.debug_log.footer" = "Status maskowania będzie widoczny po ponownym połączeniu. Dane połączenia to nazwy hostów, adresy IP, routing, SSID. Loginy i klucze prywatne nie są zapisywane.";
|
"diagnostics.sections.debug_log.footer" = "Status maskowania będzie widoczny po ponownym połączeniu. Dane połączenia to nazwy hostów, adresy IP, routing, SSID. Loginy i klucze prywatne nie są zapisywane.";
|
||||||
"diagnostics.items.server_configuration.caption" = "Konfiguracja serwera";
|
"diagnostics.items.server_configuration.caption" = "Konfiguracja serwera";
|
||||||
|
"diagnostics.items.app_log.title" = "Aplikacja";
|
||||||
"diagnostics.items.masks_private_data.caption" = "Maskuj dane sieci";
|
"diagnostics.items.masks_private_data.caption" = "Maskuj dane sieci";
|
||||||
"diagnostics.items.report_issue.caption" = "Zgłoś problemy z połączeniem";
|
"diagnostics.items.report_issue.caption" = "Zgłoś problemy z połączeniem";
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,7 @@
|
||||||
"diagnostics.title" = "Diagnóstico";
|
"diagnostics.title" = "Diagnóstico";
|
||||||
"diagnostics.sections.debug_log.footer" = "O status será escondido após reconectado. Os dados da rede são hostnames, endereços de IP, rotas, SSID. Credenciais e chaves privadas não será logadas em nenhum dos casos.";
|
"diagnostics.sections.debug_log.footer" = "O status será escondido após reconectado. Os dados da rede são hostnames, endereços de IP, rotas, SSID. Credenciais e chaves privadas não será logadas em nenhum dos casos.";
|
||||||
"diagnostics.items.server_configuration.caption" = "Configuração do servidor";
|
"diagnostics.items.server_configuration.caption" = "Configuração do servidor";
|
||||||
|
"diagnostics.items.app_log.title" = "Aplicativo";
|
||||||
"diagnostics.items.masks_private_data.caption" = "Esconder dados da rede";
|
"diagnostics.items.masks_private_data.caption" = "Esconder dados da rede";
|
||||||
"diagnostics.items.report_issue.caption" = "Reportar problemas de conexão";
|
"diagnostics.items.report_issue.caption" = "Reportar problemas de conexão";
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,7 @@
|
||||||
"diagnostics.title" = "Диагностика";
|
"diagnostics.title" = "Диагностика";
|
||||||
"diagnostics.sections.debug_log.footer" = "Маскировка включится после повторного подключения. Информация о сети - это названия хост профилей, IP адрес, маршрутизация и SSID. Данные для входа и приватные ключи не собираются.";
|
"diagnostics.sections.debug_log.footer" = "Маскировка включится после повторного подключения. Информация о сети - это названия хост профилей, IP адрес, маршрутизация и SSID. Данные для входа и приватные ключи не собираются.";
|
||||||
"diagnostics.items.server_configuration.caption" = "Конфигурация сервера";
|
"diagnostics.items.server_configuration.caption" = "Конфигурация сервера";
|
||||||
|
"diagnostics.items.app_log.title" = "Приложение";
|
||||||
"diagnostics.items.masks_private_data.caption" = "Маскировать информацию сети";
|
"diagnostics.items.masks_private_data.caption" = "Маскировать информацию сети";
|
||||||
"diagnostics.items.report_issue.caption" = "Сообщить о проблеме подкл.";
|
"diagnostics.items.report_issue.caption" = "Сообщить о проблеме подкл.";
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,7 @@
|
||||||
"diagnostics.title" = "Diagnostics";
|
"diagnostics.title" = "Diagnostics";
|
||||||
"diagnostics.sections.debug_log.footer" = "Masking status kommer att fungera efter återanslutning. Nätverksdata är värdnamn, IP-adresser, routing, SSID. Referenser och privata nycklar loggas inte oavsett.";
|
"diagnostics.sections.debug_log.footer" = "Masking status kommer att fungera efter återanslutning. Nätverksdata är värdnamn, IP-adresser, routing, SSID. Referenser och privata nycklar loggas inte oavsett.";
|
||||||
"diagnostics.items.server_configuration.caption" = "Server konfiguration";
|
"diagnostics.items.server_configuration.caption" = "Server konfiguration";
|
||||||
|
"diagnostics.items.app_log.title" = "App";
|
||||||
"diagnostics.items.masks_private_data.caption" = "Mask nätverksdata";
|
"diagnostics.items.masks_private_data.caption" = "Mask nätverksdata";
|
||||||
"diagnostics.items.report_issue.caption" = "Rapportera anslutningsproblem";
|
"diagnostics.items.report_issue.caption" = "Rapportera anslutningsproblem";
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,7 @@
|
||||||
"diagnostics.title" = "分析数据";
|
"diagnostics.title" = "分析数据";
|
||||||
"diagnostics.sections.debug_log.footer" = "在重连后状态隐藏才有效。网络数据包括主机名、IP地址、路由、SSID、认证方式,但私钥不会出现在日志中。";
|
"diagnostics.sections.debug_log.footer" = "在重连后状态隐藏才有效。网络数据包括主机名、IP地址、路由、SSID、认证方式,但私钥不会出现在日志中。";
|
||||||
"diagnostics.items.server_configuration.caption" = "服务端配置";
|
"diagnostics.items.server_configuration.caption" = "服务端配置";
|
||||||
|
"diagnostics.items.app_log.title" = "应用程序";
|
||||||
"diagnostics.items.masks_private_data.caption" = "隐藏网络数据";
|
"diagnostics.items.masks_private_data.caption" = "隐藏网络数据";
|
||||||
"diagnostics.items.report_issue.caption" = "报告连接问题";
|
"diagnostics.items.report_issue.caption" = "报告连接问题";
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftyBeaver
|
import SwiftyBeaver
|
||||||
|
|
||||||
|
@MainActor
|
||||||
public class LogManager {
|
public class LogManager {
|
||||||
public let logFile: URL?
|
public let logFile: URL?
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue