Browse Source

Write logs to new post (#236)

pull/237/head
Angelo Stavrow 1 year ago
committed by GitHub
parent
commit
1361ff7586
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 153 additions and 25 deletions
  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 View File

@@ -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] 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 methods to fetch device logs.
- [iOS] Added a settings option to generate a log file as a new local draft (iOS 15+).

### Changed



+ 60
- 6
Shared/Logging/Logging.swift View File

@@ -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 os
@@ -14,6 +9,11 @@ protocol LogWriter {
func logCrashAndSetFlag(error: Error)
}

@available(iOS 15, *)
protocol LogReader {
func fetchLogs() -> [String]
}

final class Logging {

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 View File

@@ -24,6 +24,8 @@ struct CheckForDebugModifier {
struct WriteFreely_MultiPlatformApp: App {
@StateObject private var model = WriteFreelyModel.shared

private let logger = Logging(for: String(describing: WriteFreely_MultiPlatformApp.self))

#if os(macOS)
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@StateObject var updaterViewModel = MacUpdatesViewModel()
@@ -68,6 +70,11 @@ struct WriteFreely_MultiPlatformApp: App {
)
)
}
.onAppear {
if #available(iOS 15, *) {
if didCrash { generateCrashLogPost() }
}
}
.withErrorHandling()
.environmentObject(model)
.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() {
UserDefaults.shared.set(false, forKey: WFDefaults.didHaveFatalError)
UserDefaults.shared.removeObject(forKey: WFDefaults.fatalErrorDescription)


+ 4
- 4
WriteFreely-MultiPlatform.xcodeproj/project.pbxproj View File

@@ -1050,7 +1050,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 690;
CURRENT_PROJECT_VERSION = 691;
DEVELOPMENT_TEAM = TPPAB4YBA6;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
@@ -1081,7 +1081,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 690;
CURRENT_PROJECT_VERSION = 691;
DEVELOPMENT_TEAM = TPPAB4YBA6;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
@@ -1224,7 +1224,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 690;
CURRENT_PROJECT_VERSION = 691;
DEVELOPMENT_TEAM = TPPAB4YBA6;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = iOS/Info.plist;
@@ -1250,7 +1250,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 690;
CURRENT_PROJECT_VERSION = 691;
DEVELOPMENT_TEAM = TPPAB4YBA6;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = iOS/Info.plist;


+ 53
- 15
iOS/Settings/SettingsView.swift View File

@@ -1,7 +1,11 @@
import SwiftUI

struct SettingsView: View {

@EnvironmentObject var model: WriteFreelyModel
@State private var isShowingAlert = false

private let logger = Logging(for: String(describing: SettingsView.self))

var body: some View {
VStack {
@@ -14,21 +18,22 @@ struct SettingsView: View {
Section(header: Text("Appearance")) {
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")) {
@@ -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.
}

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


Loading…
Cancel
Save