Skip to content

Commit

Permalink
Error Log (#1566)
Browse files Browse the repository at this point in the history
Co-authored-by: Sjmarf <[email protected]>
  • Loading branch information
EricBAndrews and Sjmarf authored Dec 30, 2024
1 parent 64aa346 commit 04ac212
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 4 deletions.
8 changes: 8 additions & 0 deletions Mlem.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,8 @@
CDE4AC3F2CA2083200981010 /* VideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE4AC3E2CA2082F00981010 /* VideoView.swift */; };
CDE4AC452CA370E000981010 /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = CDE4AC442CA370E000981010 /* SDWebImageSwiftUI */; };
CDE4AC472CA372B600981010 /* SDWebImageWebPCoder in Frameworks */ = {isa = PBXBuildFile; productRef = CDE4AC462CA372B600981010 /* SDWebImageWebPCoder */; };
CDEE15522D22190600EB9D7B /* ErrorsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDEE15512D22190000EB9D7B /* ErrorsTracker.swift */; };
CDEE15542D22364B00EB9D7B /* ErrorLogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDEE15532D22364500EB9D7B /* ErrorLogView.swift */; };
CDF9EF332AB2845C003F885B /* Icons.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF9EF322AB2845C003F885B /* Icons.swift */; };
CDFB8C692C7796020070845F /* View+DynamicBlur.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDFB8C682C7796020070845F /* View+DynamicBlur.swift */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -884,6 +886,8 @@
CDE1F19B2C63E2EB008AF042 /* SettingPropertyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingPropertyWrapper.swift; sourceTree = "<group>"; };
CDE1F19D2C63E306008AF042 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
CDE4AC3E2CA2082F00981010 /* VideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoView.swift; sourceTree = "<group>"; };
CDEE15512D22190000EB9D7B /* ErrorsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorsTracker.swift; sourceTree = "<group>"; };
CDEE15532D22364500EB9D7B /* ErrorLogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLogView.swift; sourceTree = "<group>"; };
CDF9EF322AB2845C003F885B /* Icons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icons.swift; sourceTree = "<group>"; };
CDFB8C682C7796020070845F /* View+DynamicBlur.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+DynamicBlur.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -959,6 +963,7 @@
03134A492BEACF46002662CC /* Settings */ = {
isa = PBXGroup;
children = (
CDEE15532D22364500EB9D7B /* ErrorLogView.swift */,
CDC44A332D1CBBCC0030F01C /* AccessibilitySettingsView.swift */,
CD45CB0C2D1880E3008BC729 /* FiltersSettingsView.swift */,
0397D4982C6EA68A002C6CDC /* InteractionBarEditor */,
Expand Down Expand Up @@ -1759,6 +1764,7 @@
CD4D59212B87BD0800B82964 /* Definitions */ = {
isa = PBXGroup;
children = (
CDEE15512D22190000EB9D7B /* ErrorsTracker.swift */,
CD7BF9312D18F4EB0020F2C5 /* FiltersTracker.swift */,
CD4D58B22B86BFD400B82964 /* AccountsTracker.swift */,
036CC3AE2B8145C30098B6A1 /* AppState.swift */,
Expand Down Expand Up @@ -2371,6 +2377,7 @@
CD1B2E212C7F84160075C7EA /* View+MarkReadOnScroll.swift in Sources */,
033F84AD2C298466002E3EDF /* SectionIndexTitles.swift in Sources */,
CDCA44B62C176C5000C092B3 /* View+EdgeBorders.swift in Sources */,
CDEE15542D22364B00EB9D7B /* ErrorLogView.swift in Sources */,
03FA318F2C6FEF2000D47FA3 /* InteractionBarActionLabelView.swift in Sources */,
CDE1F1982C63DFC9008AF042 /* PadConstants.swift in Sources */,
03B25B312CC4403500EB6DF5 /* Fediseer.swift in Sources */,
Expand Down Expand Up @@ -2476,6 +2483,7 @@
033F84BB2C2ACB96002E3EDF /* CommentView.swift in Sources */,
03AFD0E52C3C14D50054B8AD /* InstanceStubProviding+Extensions.swift in Sources */,
CD4D58CB2B86DD3D00B82964 /* AssociatedIcon.swift in Sources */,
CDEE15522D22190600EB9D7B /* ErrorsTracker.swift in Sources */,
03B62B792CE2A2C00077E9C8 /* RulesPickerView.swift in Sources */,
0389DDD52C39F1290005B808 /* CommunityListRow.swift in Sources */,
CD4BAD352B4B2C0B00A1E726 /* FeedsView.swift in Sources */,
Expand Down
31 changes: 31 additions & 0 deletions Mlem/App/Globals/Definitions/ErrorsTracker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// ErrorsTracker.swift
// Mlem
//
// Created by Eric Andrews on 2024-12-29.
//

import Observation

@Observable
class ErrorsTracker {
private(set) var errors: [ErrorDetails] = .init()

@MainActor
func addError(_ error: Error) {
errors.prepend(.init(error: error))
}

static var main: ErrorsTracker = .init()

func createErrorLog() -> String {
var ret: String = ""

errors.forEach { details in
let description = details.error?.localizedDescription ?? "No Description"
ret += "\(details.when.formatted(.iso8601))\t\(details.title ?? "Error")\t\(description)\n"
}

return ret
}
}
4 changes: 4 additions & 0 deletions Mlem/App/Logic/HandleError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ private func _handleError(
print("📂 -> \(file) | \(function) | line: \(line)")
#endif

Task {
await ErrorsTracker.main.addError(error)
}

switch error {
// TODO: Modify MlemMiddleware to attach the ApiClient throwing the error to ApiClientError.invalidSession, so that we can access the relevant UserStub in a multi-account context
case ApiClientError.invalidSession:
Expand Down
14 changes: 14 additions & 0 deletions Mlem/App/Logic/ImageFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,17 @@ func downloadImageToFileSystem(url: URL) async -> URL? {
return nil
}
}

func downloadTextToFileSystem(fileName: String, text: String) async -> URL? {
do {
let fileUrl = FileManager.default.temporaryDirectory.appending(path: fileName)
if FileManager.default.fileExists(atPath: fileUrl.absoluteString) {
try FileManager.default.removeItem(at: fileUrl)
}
try text.write(to: fileUrl, atomically: true, encoding: String.Encoding.utf8)
return fileUrl
} catch {
handleError(error)
return nil
}
}
3 changes: 3 additions & 0 deletions Mlem/App/Models/ErrorDetails.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct ErrorDetails: Hashable {
var buttonText: String?
var refresh: (() async -> Bool)?
var autoRefresh: Bool = false
var when: Date

init(
title: String? = nil,
Expand All @@ -35,6 +36,8 @@ struct ErrorDetails: Hashable {
self.buttonText = buttonText
self.refresh = refresh
self.autoRefresh = autoRefresh
self.when = Date.now

if let error {
switch error {
case ApiClientError.imageTooLarge:
Expand Down
2 changes: 2 additions & 0 deletions Mlem/App/Views/Root/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ struct ContentView: View {
var tabReselectTracker: TabReselectTracker { .main }
var navigationModel: NavigationModel { .main }
var filtersTracker: FiltersTracker { .main }
var errorsTracker: ErrorsTracker { .main }

@State var avatarImage: UIImage?
@State var selectedAvatarImage: UIImage?
Expand Down Expand Up @@ -66,6 +67,7 @@ struct ContentView: View {
.environment(tabReselectTracker)
.environment(appState)
.environment(filtersTracker)
.environment(errorsTracker)
.task {
do {
try await MlemStats.main.loadInstances()
Expand Down
21 changes: 18 additions & 3 deletions Mlem/App/Views/Root/Tabs/Settings/DeveloperSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import SwiftUI
import MlemMiddleware

// Strings in this view are intentionally left unlocalized; we shouldn't
// be burdening translators with these when they'll never be used
Expand All @@ -16,10 +17,24 @@ struct DeveloperSettingsView: View {

var body: some View {
Form {
Toggle(String("Developer Mode"), isOn: $developerMode)
Button(String("Reset Feed Welcome Prompt")) {
showFeedWelcomePrompt = true
Section {
Toggle(String("Developer Mode"), isOn: $developerMode)
NavigationLink(String("Error Log"), destination: .settings(.errorLog))
}

#if DEBUG
Section {
Button(String("Reset Feed Welcome Prompt")) {
showFeedWelcomePrompt = true
}

Button(String("Create Error")) {
handleError(ApiClientError.insufficientPermissions)
}
} header: {
Text(verbatim: "Debug Tools")
}
#endif
}
.navigationTitle("Developer")
}
Expand Down
83 changes: 83 additions & 0 deletions Mlem/App/Views/Root/Tabs/Settings/ErrorLogView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// ErrorLogView.swift
// Mlem
//
// Created by Eric Andrews on 2024-12-29.
//

import SwiftUI

struct ErrorLogView: View {
@Environment(ErrorsTracker.self) var errorsTracker
@Environment(Palette.self) var palette
@Environment(NavigationLayer.self) var navigation

var body: some View {
FancyScrollView {
LazyVStack(spacing: Constants.main.standardSpacing) {
if errorsTracker.errors.isEmpty {
Text(verbatim: "No errors")
.foregroundStyle(palette.secondary)
}
ForEach(Array(errorsTracker.errors.enumerated()), id: \.offset) { _, errorDetails in
errorView(errorDetails)
}
.padding(.horizontal, Constants.main.standardSpacing)
}
}
.background(palette.groupedBackground)
.navigationTitle(String("Error Log"))
.toolbar {
if !errorsTracker.errors.isEmpty {
ToolbarItem(placement: .topBarTrailing) {
Button {
Task {
if let url = await downloadTextToFileSystem(
fileName: "mlem_error_log.txt",
text: errorsTracker.createErrorLog()
) {
navigation.shareInfo = .init(url: url)
} else {
ToastModel.main.add(.failure(String("Failed to share error log")))
}
}
} label: {
Image(systemName: Icons.share)
}
}
}
}
}

@ViewBuilder
func errorView(_ details: ErrorDetails) -> some View {
VStack(alignment: .leading, spacing: Constants.main.standardSpacing) {
HStack {
Text(details.title ?? "Error")
.fontWeight(.semibold)

Spacer()

Button {
if let text = details.error?.localizedDescription {
UIPasteboard.general.string = text
ToastModel.main.add(.success(String("Copied")))
}
} label: {
Text(Image(systemName: Icons.copy))
.font(.subheadline)
.foregroundStyle(palette.accent)
}
}

Text(details.errorText)

Text(details.when.formatted(date: .abbreviated, time: .standard))
.font(.caption)
.foregroundStyle(palette.secondary)
}
.padding(Constants.main.standardSpacing)
.background(palette.secondaryGroupedBackground)
.clipShape(.rect(cornerRadius: Constants.main.mediumItemCornerRadius))
}
}
4 changes: 3 additions & 1 deletion Mlem/App/Views/Shared/Navigation/SettingsPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ enum SettingsPage: Hashable {
case importExportSettings
case theme, icon
case post, comment, inbox, subscriptionList
case about, advanced, developer
case about, advanced, developer, errorLog
case postInteractionBar, commentInteractionBar, replyInteractionBar
case moderation
case licences, document(Document)
Expand Down Expand Up @@ -57,6 +57,8 @@ enum SettingsPage: Hashable {
AdvancedSettingsView()
case .developer:
DeveloperSettingsView()
case .errorLog:
ErrorLogView()
case .about:
AboutMlemView()
case .theme:
Expand Down
12 changes: 12 additions & 0 deletions Mlem/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,9 @@
},
"Debug" : {

},
"Debug Tools" : {

},
"Default" : {

Expand Down Expand Up @@ -696,6 +699,9 @@
},
"Enter your instance's domain name below." : {

},
"Error Log" : {

},
"EULA" : {

Expand Down Expand Up @@ -732,6 +738,9 @@
},
"Failed to Save Image" : {

},
"Failed to share error log" : {

},
"Failed to unlock post" : {

Expand Down Expand Up @@ -1130,6 +1139,9 @@
},
"No comments found" : {

},
"No errors" : {

},
"No reason given" : {

Expand Down

0 comments on commit 04ac212

Please sign in to comment.