mirror of
https://github.com/writeas/writefreely-swiftui-multiplatform.git
synced 2024-11-15 01:11:02 +00:00
Write logs to new post (#236)
This commit is contained in:
parent
fcd7c167c7
commit
1361ff7586
@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- [Mac] Added a menu item for toggling the toolbar.
|
- [Mac] Added a menu item for toggling the toolbar.
|
||||||
- [Mac] In a post with unpublished changes (i.e., with "local" or "edited" status), the post is autosaved after a one-second pause in typing.
|
- [Mac] In a post with unpublished changes (i.e., with "local" or "edited" status), the post is autosaved after a one-second pause in typing.
|
||||||
- [iOS/Mac] Added a context-menu item to delete local posts from the post list.
|
- [iOS/Mac] Added a context-menu item to delete local posts from the post list.
|
||||||
|
- [iOS/Mac] Added methods to fetch device logs.
|
||||||
|
- [iOS] Added a settings option to generate a log file as a new local draft (iOS 15+).
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
//
|
// Credit for much of this class: https://steipete.com/posts/logging-in-swift/
|
||||||
// Logging.swift
|
|
||||||
// WriteFreely-MultiPlatform
|
|
||||||
//
|
|
||||||
// Created by Angelo Stavrow on 2022-06-25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import os
|
import os
|
||||||
@ -14,6 +9,11 @@ protocol LogWriter {
|
|||||||
func logCrashAndSetFlag(error: Error)
|
func logCrashAndSetFlag(error: Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOS 15, *)
|
||||||
|
protocol LogReader {
|
||||||
|
func fetchLogs() -> [String]
|
||||||
|
}
|
||||||
|
|
||||||
final class Logging {
|
final class Logging {
|
||||||
|
|
||||||
private let logger: Logger
|
private let logger: Logger
|
||||||
@ -48,3 +48,57 @@ extension Logging: LogWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Logging: LogReader {
|
||||||
|
|
||||||
|
@available(iOS 15, *)
|
||||||
|
func fetchLogs() -> [String] {
|
||||||
|
var logs: [String] = []
|
||||||
|
|
||||||
|
do {
|
||||||
|
let osLog = try getLogEntries()
|
||||||
|
for logEntry in osLog {
|
||||||
|
let formattedEntry = formatEntry(logEntry)
|
||||||
|
logs.append(formattedEntry)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
logs.append("Could not fetch logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
return logs
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 15, *)
|
||||||
|
private func getLogEntries() throws -> [OSLogEntryLog] {
|
||||||
|
let logStore = try OSLogStore(scope: .currentProcessIdentifier)
|
||||||
|
let oneHourAgo = logStore.position(date: Date().addingTimeInterval(-3600))
|
||||||
|
let allEntries = try Array(logStore.__entriesEnumerator(position: oneHourAgo, predicate: nil))
|
||||||
|
return allEntries
|
||||||
|
.compactMap { $0 as? OSLogEntryLog }
|
||||||
|
.filter { $0.subsystem == subsystem }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 15, *)
|
||||||
|
private func formatEntry(_ logEntry: OSLogEntryLog) -> String {
|
||||||
|
/// The desired format is:
|
||||||
|
/// `date [process/category] LEVEL: composedMessage (threadIdentifier)`
|
||||||
|
var level: String = ""
|
||||||
|
switch logEntry.level {
|
||||||
|
case .debug:
|
||||||
|
level = "DEBUG"
|
||||||
|
case .info:
|
||||||
|
level = "INFO"
|
||||||
|
case .notice:
|
||||||
|
level = "NOTICE"
|
||||||
|
case .error:
|
||||||
|
level = "ERROR"
|
||||||
|
case .fault:
|
||||||
|
level = "FAULT"
|
||||||
|
default:
|
||||||
|
level = "UNDEFINED"
|
||||||
|
}
|
||||||
|
// swiftlint:disable:next line_length
|
||||||
|
return "\(logEntry.date) [\(logEntry.process)/\(logEntry.category)] \(level): \(logEntry.composedMessage) (\(logEntry.threadIdentifier))"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -24,6 +24,8 @@ struct CheckForDebugModifier {
|
|||||||
struct WriteFreely_MultiPlatformApp: App {
|
struct WriteFreely_MultiPlatformApp: App {
|
||||||
@StateObject private var model = WriteFreelyModel.shared
|
@StateObject private var model = WriteFreelyModel.shared
|
||||||
|
|
||||||
|
private let logger = Logging(for: String(describing: WriteFreely_MultiPlatformApp.self))
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||||
@StateObject var updaterViewModel = MacUpdatesViewModel()
|
@StateObject var updaterViewModel = MacUpdatesViewModel()
|
||||||
@ -68,6 +70,11 @@ struct WriteFreely_MultiPlatformApp: App {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
if #available(iOS 15, *) {
|
||||||
|
if didCrash { generateCrashLogPost() }
|
||||||
|
}
|
||||||
|
}
|
||||||
.withErrorHandling()
|
.withErrorHandling()
|
||||||
.environmentObject(model)
|
.environmentObject(model)
|
||||||
.environment(\.managedObjectContext, LocalStorageManager.standard.container.viewContext)
|
.environment(\.managedObjectContext, LocalStorageManager.standard.container.viewContext)
|
||||||
@ -167,6 +174,33 @@ struct WriteFreely_MultiPlatformApp: App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOS 15, *)
|
||||||
|
private func generateCrashLogPost() {
|
||||||
|
logger.log("Generating local log post...")
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now()) {
|
||||||
|
// Unset selected post and collection and navigate to local drafts.
|
||||||
|
self.model.selectedPost = nil
|
||||||
|
self.model.selectedCollection = nil
|
||||||
|
self.model.showAllPosts = false
|
||||||
|
|
||||||
|
// Create the new log post.
|
||||||
|
let newLogPost = model.editor.generateNewLocalPost(withFont: 2)
|
||||||
|
newLogPost.title = "Logs For Support"
|
||||||
|
var postBody: [String] = [
|
||||||
|
"WriteFreely-Multiplatform v\(Bundle.main.appMarketingVersion) (\(Bundle.main.appBuildVersion))",
|
||||||
|
"Generated \(Date())",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
postBody.append(contentsOf: logger.fetchLogs())
|
||||||
|
newLogPost.body = postBody.joined(separator: "\n")
|
||||||
|
|
||||||
|
self.model.selectedPost = newLogPost
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log("Generated local log post.")
|
||||||
|
}
|
||||||
|
|
||||||
private func resetCrashFlags() {
|
private func resetCrashFlags() {
|
||||||
UserDefaults.shared.set(false, forKey: WFDefaults.didHaveFatalError)
|
UserDefaults.shared.set(false, forKey: WFDefaults.didHaveFatalError)
|
||||||
UserDefaults.shared.removeObject(forKey: WFDefaults.fatalErrorDescription)
|
UserDefaults.shared.removeObject(forKey: WFDefaults.fatalErrorDescription)
|
||||||
|
@ -1050,7 +1050,7 @@
|
|||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||||
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 690;
|
CURRENT_PROJECT_VERSION = 691;
|
||||||
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
|
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
|
||||||
@ -1081,7 +1081,7 @@
|
|||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||||
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 690;
|
CURRENT_PROJECT_VERSION = 691;
|
||||||
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
|
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
|
||||||
@ -1224,7 +1224,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 690;
|
CURRENT_PROJECT_VERSION = 691;
|
||||||
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = iOS/Info.plist;
|
INFOPLIST_FILE = iOS/Info.plist;
|
||||||
@ -1250,7 +1250,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 690;
|
CURRENT_PROJECT_VERSION = 691;
|
||||||
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = iOS/Info.plist;
|
INFOPLIST_FILE = iOS/Info.plist;
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
|
|
||||||
@EnvironmentObject var model: WriteFreelyModel
|
@EnvironmentObject var model: WriteFreelyModel
|
||||||
|
@State private var isShowingAlert = false
|
||||||
|
|
||||||
|
private let logger = Logging(for: String(describing: SettingsView.self))
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
@ -14,21 +18,22 @@ struct SettingsView: View {
|
|||||||
Section(header: Text("Appearance")) {
|
Section(header: Text("Appearance")) {
|
||||||
PreferencesView(preferences: model.preferences)
|
PreferencesView(preferences: model.preferences)
|
||||||
}
|
}
|
||||||
Section(header: Text("External Links")) {
|
Section(header: Text("Help and Support")) {
|
||||||
HStack {
|
Link("View the Guide", destination: model.howToURL)
|
||||||
Spacer()
|
Link("Visit the Help Forum", destination: model.helpURL)
|
||||||
Link("View the Guide", destination: model.howToURL)
|
Link("Write a Review on the App Store", destination: model.reviewURL)
|
||||||
Spacer()
|
if #available(iOS 15.0, *) {
|
||||||
}
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
HStack {
|
Button(
|
||||||
Spacer()
|
action: didTapGenerateLogPostButton,
|
||||||
Link("Visit the Help Forum", destination: model.helpURL)
|
label: {
|
||||||
Spacer()
|
Text("Create Log Post")
|
||||||
}
|
}
|
||||||
HStack {
|
)
|
||||||
Spacer()
|
Text("Generates a local post using recent logs. You can share this for troubleshooting.")
|
||||||
Link("Write a Review on the App Store", destination: model.reviewURL)
|
.font(.footnote)
|
||||||
Spacer()
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Section(header: Text("Acknowledgements")) {
|
Section(header: Text("Acknowledgements")) {
|
||||||
@ -55,8 +60,41 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.alert(isPresented: $isShowingAlert) {
|
||||||
|
Alert(
|
||||||
|
title: Text("Log Post Created"),
|
||||||
|
message: Text("Check your local drafts for app logs from the past 24 hours.")
|
||||||
|
)
|
||||||
|
}
|
||||||
// .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info.
|
// .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOS 15, *)
|
||||||
|
private func didTapGenerateLogPostButton() {
|
||||||
|
logger.log("Generating local log post...")
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now()) {
|
||||||
|
// Unset selected post and collection and navigate to local drafts.
|
||||||
|
self.model.selectedPost = nil
|
||||||
|
self.model.selectedCollection = nil
|
||||||
|
self.model.showAllPosts = false
|
||||||
|
|
||||||
|
// Create the new log post.
|
||||||
|
let newLogPost = model.editor.generateNewLocalPost(withFont: 2)
|
||||||
|
newLogPost.title = "Logs For Support"
|
||||||
|
var postBody: [String] = [
|
||||||
|
"WriteFreely-Multiplatform v\(Bundle.main.appMarketingVersion) (\(Bundle.main.appBuildVersion))",
|
||||||
|
"Generated \(Date())",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
postBody.append(contentsOf: logger.fetchLogs())
|
||||||
|
newLogPost.body = postBody.joined(separator: "\n")
|
||||||
|
|
||||||
|
self.isShowingAlert = true
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log("Generated local log post.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SettingsView_Previews: PreviewProvider {
|
struct SettingsView_Previews: PreviewProvider {
|
||||||
|
Loading…
Reference in New Issue
Block a user