Logging: Add exportability to logs
Logs can be exported into their own files. I'm still debating on writing a continuous stream to a logfile that persists on app crashes. Also make the show error toasts toggle only apply to generic errors and not customized error toasts. Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
parent
7202a95bb2
commit
d2d7d7364f
4 changed files with 88 additions and 7 deletions
|
|
@ -993,7 +993,7 @@
|
|||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportsDocumentBrowser = NO;
|
||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
|
@ -1028,7 +1028,7 @@
|
|||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportsDocumentBrowser = NO;
|
||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
|
@ -1072,8 +1072,8 @@
|
|||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/SwiftUIX/SwiftUIX";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.1.3;
|
||||
branch = master;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
// A static DateFormatter is better than initializing new ones
|
||||
extension DateFormatter {
|
||||
static let historyDateFormatter: DateFormatter = {
|
||||
let df = DateFormatter()
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ import SwiftUI
|
|||
|
||||
@MainActor
|
||||
class LoggingManager: ObservableObject {
|
||||
let logFormatter = DateFormatter()
|
||||
|
||||
struct Log: Hashable {
|
||||
let level: LogLevel
|
||||
let message: String
|
||||
let timeStamp: Date = .init()
|
||||
var isExpanded: Bool = false
|
||||
|
||||
func toMessage() -> String {
|
||||
"[\(level.rawValue)]: \(message)"
|
||||
|
|
@ -30,6 +33,7 @@ class LoggingManager: ObservableObject {
|
|||
}
|
||||
|
||||
@Published var messageArray: [Log] = []
|
||||
@Published var showLogExportedAlert = false
|
||||
|
||||
// Toast variables
|
||||
@Published var toastDescription: String? = nil {
|
||||
|
|
@ -57,7 +61,13 @@ class LoggingManager: ObservableObject {
|
|||
@Published var indeterminateCancelAction: (() -> Void)? = nil
|
||||
@Published var showIndeterminateToast: Bool = false
|
||||
|
||||
init() {
|
||||
logFormatter.dateStyle = .short
|
||||
logFormatter.timeStyle = .long
|
||||
}
|
||||
|
||||
// MARK: - Logging functions
|
||||
// TODO: Maybe append to a constant logfile?
|
||||
|
||||
public func info(_ message: String,
|
||||
description: String? = nil)
|
||||
|
|
@ -105,8 +115,13 @@ class LoggingManager: ObservableObject {
|
|||
)
|
||||
|
||||
// If a task is run in parallel, don't show a toast on error
|
||||
if showToast && showErrorToasts {
|
||||
toastDescription = description.map { $0 } ?? "An error was logged"
|
||||
// Only gate generic error toasts behind the settings option
|
||||
if showToast {
|
||||
if let description {
|
||||
toastDescription = description
|
||||
} else if showErrorToasts {
|
||||
toastDescription = "An error was logged"
|
||||
}
|
||||
}
|
||||
|
||||
messageArray.append(log)
|
||||
|
|
@ -133,4 +148,33 @@ class LoggingManager: ObservableObject {
|
|||
indeterminateToastDescription = ""
|
||||
indeterminateCancelAction = nil
|
||||
}
|
||||
|
||||
public func exportLogs() {
|
||||
logFormatter.dateFormat = "yyyy-MM-dd-HHmmss"
|
||||
let logFileName = "ferrite_session_\(logFormatter.string(from: Date())).txt"
|
||||
let logFolderPath = FileManager.default.appDirectory.appendingPathComponent("Logs")
|
||||
let logPath = logFolderPath.appendingPathComponent(logFileName)
|
||||
|
||||
logFormatter.dateStyle = .short
|
||||
logFormatter.timeStyle = .long
|
||||
let joinedMessages = messageArray.map { "\(logFormatter.string(from: $0.timeStamp)): \($0.toMessage())" }.joined(separator: "\n")
|
||||
|
||||
do {
|
||||
if FileManager.default.fileExists(atPath: logPath.path) {
|
||||
try FileManager.default.removeItem(at: logPath)
|
||||
} else if !FileManager.default.fileExists(atPath: logFolderPath.path) {
|
||||
try FileManager.default.createDirectory(atPath: logFolderPath.path, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
|
||||
try joinedMessages.write(to: logPath, atomically: true, encoding: .utf8)
|
||||
|
||||
self.info("Log \(logFileName) was written to path \(logPath.description)")
|
||||
showLogExportedAlert.toggle()
|
||||
} catch {
|
||||
self.error(
|
||||
"Log export for file \(logFileName): \(error)",
|
||||
description: "Exporting your log file failed. Please check the logs page."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,15 +13,51 @@ struct SettingsLogView: View {
|
|||
var body: some View {
|
||||
NavView {
|
||||
List {
|
||||
ForEach(logManager.messageArray, id: \.self) { log in
|
||||
ForEach($logManager.messageArray, id: \.self) { $log in
|
||||
Text(log.toMessage())
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(log.isExpanded ? nil : 5)
|
||||
.onTapGesture {
|
||||
log.isExpanded.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.backport.alert(
|
||||
isPresented: $logManager.showLogExportedAlert,
|
||||
title: "Success",
|
||||
message: "Log successfully exported in Ferrite's logs folder"
|
||||
)
|
||||
.navigationTitle("Logs")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Menu {
|
||||
Button {
|
||||
logManager.exportLogs()
|
||||
} label: {
|
||||
Label("Export", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
|
||||
if #available(iOS 15, *) {
|
||||
Button(role: .destructive) {
|
||||
logManager.messageArray = []
|
||||
} label: {
|
||||
Label("Clear session logs", systemImage: "trash")
|
||||
}
|
||||
} else {
|
||||
Button {
|
||||
logManager.messageArray = []
|
||||
} label: {
|
||||
Label("Clear session logs", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue