소스 검색

Write logs to new post (#236)

pull/237/head
Angelo Stavrow 1 년 전
committed by GitHub
부모
커밋
1361ff7586
No known key found for this signature in database GPG 키 ID: 4AEE18F83AFDEB23
5개의 변경된 파일153개의 추가작업 그리고 25개의 파일을 삭제
  1. +2
    -0
      CHANGELOG.md
  2. +60
    -6
      Shared/Logging/Logging.swift
  3. +34
    -0
      Shared/WriteFreely_MultiPlatformApp.swift
  4. +4
    -4
      WriteFreely-MultiPlatform.xcodeproj/project.pbxproj
  5. +53
    -15
      iOS/Settings/SettingsView.swift

+ 2
- 0
CHANGELOG.md 파일 보기

@@ -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




+ 60
- 6
Shared/Logging/Logging.swift 파일 보기

@@ -1,9 +1,4 @@
//
// Logging.swift
// WriteFreely-MultiPlatform
//
// Created by Angelo Stavrow on 2022-06-25.
//
// Credit for much of this class: https://steipete.com/posts/logging-in-swift/


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

}

+ 34
- 0
Shared/WriteFreely_MultiPlatformApp.swift 파일 보기

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


+ 4
- 4
WriteFreely-MultiPlatform.xcodeproj/project.pbxproj 파일 보기

@@ -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;


+ 53
- 15
iOS/Settings/SettingsView.swift 파일 보기

@@ -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")) {
HStack {
Spacer()
Link("View the Guide", destination: model.howToURL)
Spacer()
}
HStack {
Spacer()
Link("Visit the Help Forum", destination: model.helpURL)
Spacer()
}
HStack {
Spacer()
Link("Write a Review on the App Store", destination: model.reviewURL)
Spacer()
Section(header: Text("Help and Support")) {
Link("View the Guide", destination: model.howToURL)
Link("Visit the Help Forum", destination: model.helpURL)
Link("Write a Review on the App Store", destination: model.reviewURL)
if #available(iOS 15.0, *) {
VStack(alignment: .leading, spacing: 8) {
Button(
action: didTapGenerateLogPostButton,
label: {
Text("Create Log Post")
}
)
Text("Generates a local post using recent logs. You can share this for troubleshooting.")
.font(.footnote)
.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 {


불러오는 중...
취소
저장