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:
kingbri 2023-03-10 15:01:00 -05:00
parent 7202a95bb2
commit d2d7d7364f
4 changed files with 88 additions and 7 deletions

View file

@ -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" */ = {

View file

@ -7,6 +7,7 @@
import Foundation
// A static DateFormatter is better than initializing new ones
extension DateFormatter {
static let historyDateFormatter: DateFormatter = {
let df = DateFormatter()

View file

@ -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."
)
}
}
}

View file

@ -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")
}
}
}
}
}
}