Add debug log preview

This commit is contained in:
Elaine 2026-05-27 16:22:31 -04:00 committed by GitHub
parent 025a9ff9be
commit ab454da687
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 299 additions and 176 deletions

View File

@ -1250,14 +1250,20 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
switch action {
case .submitDebugLogsAndCrash:
addSubmitDebugLogsAction {
DebugLogs.submitLogs(supportTag: supportTag, dumper: logDumper) {
DebugLogs(dumper: logDumper).promptToSubmitLogs(
from: viewController,
supportTag: supportTag,
) {
owsFail("Exiting after submitting debug logs")
}
}
case .submitDebugLogsAndLaunchApp(let window, let launchContext):
addSubmitDebugLogsAction { [unowned window] in
DebugLogs.submitLogs(supportTag: supportTag, dumper: logDumper) {
DebugLogs(dumper: logDumper).promptToSubmitLogs(
from: viewController,
supportTag: supportTag,
) {
ignoreErrorAndLaunchApp(in: window, launchContext: launchContext)
}
}

View File

@ -192,13 +192,16 @@ class CallQualitySurveyManager {
return proto
}
func submit(rating: CallQualitySurvey.Rating, shouldSubmitDebugLogs: Bool) {
func submit(
rating: CallQualitySurvey.Rating,
logsToSubmit logs: DebugLogs?,
) {
var proto = buildProto(rating: rating)
Task {
if shouldSubmitDebugLogs {
if let logs {
do {
let debugLogURL = try await DebugLogs.uploadLogs(dumper: .fromGlobals())
let debugLogURL = try await logs.uploadLogs()
proto.debugLogURL = debugLogURL.absoluteString
} catch {
logger.error("Failed to submit debug logs: \(error)")

View File

@ -16,10 +16,12 @@ final class SurveyDebugLogViewController: CallQualitySurveySheetViewController {
private let tableViewController = OWSTableViewController2()
private var shouldSubmitDebugLogs = false
private var logs: DebugLogs
private let rating: CallQualitySurvey.Rating
init(rating: CallQualitySurvey.Rating) {
self.logs = DebugLogs(dumper: .fromGlobals())
self.rating = rating
super.init(nibName: nil, bundle: nil)
}
@ -129,7 +131,8 @@ final class SurveyDebugLogViewController: CallQualitySurveySheetViewController {
let container = UIView()
let textView = LinkingTextView { [weak self] in
self?.showDebugLogPreview()
guard let self else { return }
self.logs.showPreview(from: self)
}
textView.attributedText = .composed(of: [
OWSLocalizedString(
@ -193,12 +196,6 @@ final class SurveyDebugLogViewController: CallQualitySurveySheetViewController {
present(nav, animated: true)
}
private func showDebugLogPreview() {
let vc = DebugLogPreviewViewController()
let nav = OWSNavigationController(rootViewController: vc)
present(nav, animated: true)
}
override func customSheetHeight() -> CGFloat? {
let headerHeight = headerContainer.height
let collectionViewHeight = tableViewController.tableView.contentSize.height + tableViewController.tableView.contentInset.totalHeight
@ -209,7 +206,7 @@ final class SurveyDebugLogViewController: CallQualitySurveySheetViewController {
private func submit() {
sheetNav?.submit(
rating: self.rating,
shouldSubmitDebugLogs: self.shouldSubmitDebugLogs,
logsToSubmit: shouldSubmitDebugLogs ? logs : nil,
)
}
}

View File

@ -82,10 +82,13 @@ final class CallQualitySurveyNavigationController: UINavigationController {
pushViewController(vc, animated: false)
}
func submit(rating: CallQualitySurvey.Rating, shouldSubmitDebugLogs: Bool) {
func submit(
rating: CallQualitySurvey.Rating,
logsToSubmit: DebugLogs?,
) {
callQualitySurveyManager.submit(
rating: rating,
shouldSubmitDebugLogs: shouldSubmitDebugLogs,
logsToSubmit: logsToSubmit,
)
let host = presentingViewController
dismiss(animated: true) {

View File

@ -47,11 +47,12 @@ enum ContactSupportActionSheet {
let submitWithLogAction = ActionSheetAction(title: submitWithLogTitle, style: .default) { [weak fromViewController] _ in
guard let fromViewController else { return }
let logs = DebugLogs(dumper: logDumper)
let emailRequest = SupportEmailModel(
userDescription: nil,
emojiMood: nil,
supportFilter: emailFilter.asString,
debugLogPolicy: .requireUpload(logDumper),
debugLogPolicy: .requireUpload(logs),
hasRecentChallenge: logDumper.challengeReceivedRecently(),
)

View File

@ -8,7 +8,7 @@ import SignalServiceKit
import SignalUI
import zlib
public struct DebugLogDumper {
struct DebugLogDumper {
fileprivate var accountManager: (any TSAccountManager)?
fileprivate var appVersion: any AppVersion
fileprivate var db: (any DB)?
@ -25,7 +25,7 @@ public struct DebugLogDumper {
)
}
public func challengeReceivedRecently() -> Bool {
func challengeReceivedRecently() -> Bool {
guard let db else {
return false
}
@ -57,34 +57,134 @@ public struct DebugLogDumper {
}
}
enum DebugLogs {
final class DebugLogs {
private let dumper: DebugLogDumper
private var logsDirPath: String?
init(dumper: DebugLogDumper) {
self.dumper = dumper
self.logsDirPath = DebugLogs.collectAndFlushLogs(dumper: dumper)
}
deinit {
if let logsDirPath {
OWSFileSystem.deleteFile(logsDirPath)
}
}
func showPreview(
from viewController: UIViewController,
onSubmit: (() -> Void)? = nil,
onCancel: (() -> Void)? = nil,
) {
guard let logsDirPath else {
Logger.error("No logs path found for preview")
handleError(error: .noLogs, viewController: viewController)
onCancel?()
return
}
let logFilePaths = ((try? FileManager.default.contentsOfDirectory(atPath: logsDirPath)) ?? []).map {
URL(fileURLWithPath: logsDirPath).appendingPathComponent($0).path
}
let previewVC = DebugLogPreviewViewController(logFilePaths: logFilePaths, onSubmit: onSubmit, onCancel: onCancel)
let nav = OWSNavigationController(rootViewController: previewVC)
viewController.present(nav, animated: true)
}
/// Presents a log preview with an option to submit. Completion is only
/// called if the user submits, after the submission is completed.
@MainActor
static func submitLogs(supportTag: String? = nil, dumper: DebugLogDumper, completion: (() -> Void)? = nil) {
let submitLogsCompletion = {
if let completion {
// Wait a moment. If the user opens a URL, it needs a moment to complete.
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
func promptToSubmitLogs(
from viewController: UIViewController,
supportTag: String? = nil,
completion: (() -> Void)? = nil,
) {
showPreview(from: viewController, onSubmit: {
Task {
await viewController.awaitableDismiss(animated: true)
await self.submitLogs(supportTag: supportTag)
if let completion {
try? await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC)
completion()
}
}
}
})
}
@MainActor
func promptToSubmitLogs(
from viewController: UIViewController,
supportTag: String? = nil,
) async {
let didSubmit = await withCheckedContinuation { continuation in
showPreview(
from: viewController,
onSubmit: {
continuation.resume(returning: true)
},
onCancel: {
continuation.resume(returning: false)
},
)
}
if didSubmit {
await viewController.awaitableDismiss(animated: true)
await submitLogs(supportTag: supportTag)
}
}
enum DebugLogsError: LocalizedError {
case noLogs
case couldNotPackageLogs
case uploadError(zipFilePath: String)
var errorDescription: String? { localizedErrorMessage }
var localizedErrorMessage: String {
switch self {
case .noLogs:
OWSLocalizedString(
"DEBUG_LOG_ALERT_NO_LOGS",
comment: "Error indicating that no debug logs could be found.",
)
case .couldNotPackageLogs:
OWSLocalizedString(
"DEBUG_LOG_ALERT_COULD_NOT_PACKAGE_LOGS",
comment: "Error indicating that the debug logs could not be packaged.",
)
case .uploadError:
OWSLocalizedString(
"DEBUG_LOG_ALERT_ERROR_UPLOADING_LOG",
comment: "Error indicating that a debug log could not be uploaded.",
)
}
}
}
@MainActor
private func submitLogs(supportTag: String?) async {
var supportFilter = "Signal - iOS Debug Log"
if let supportTag {
supportFilter += " - \(supportTag)"
}
guard let frontmostViewController = UIApplication.shared.frontmostViewControllerIgnoringAlerts else {
submitLogsCompletion()
return
}
uploadLogsUsingViewController(frontmostViewController, dumper: dumper) { url in
guard let presentingViewController = UIApplication.shared.frontmostViewControllerIgnoringAlerts else {
submitLogsCompletion()
return
}
let url: URL?
do {
url = try await uploadLogsWithUI(from: frontmostViewController)
} catch {
self.handleError(error: error, viewController: frontmostViewController)
return
}
guard let url else { return }
guard let presentingViewController = UIApplication.shared.frontmostViewControllerIgnoringAlerts else {
return
}
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
let alert = ActionSheetController(
title: NSLocalizedString("DEBUG_LOG_ALERT_TITLE", comment: "Title of the debug log alert."),
message: NSLocalizedString("DEBUG_LOG_ALERT_MESSAGE", comment: "Message of the debug log alert."),
@ -102,10 +202,10 @@ enum DebugLogs {
await ComposeSupportEmailOperation.sendEmailWithDefaultErrorHandling(
supportFilter: supportFilter,
logUrl: url,
hasRecentChallenge: dumper.challengeReceivedRecently(),
hasRecentChallenge: self.dumper.challengeReceivedRecently(),
)
}
submitLogsCompletion()
continuation.resume()
},
))
}
@ -118,7 +218,7 @@ enum DebugLogs {
handler: { _ in
UIPasteboard.general.string = url.absoluteString
presentingViewController.presentToast(text: CommonStrings.copiedToClipboardToast, image: .copy)
submitLogsCompletion()
continuation.resume()
},
))
alert.addAction(ActionSheetAction(
@ -131,67 +231,39 @@ enum DebugLogs {
AttachmentSharing.showShareUI(
for: url.absoluteString,
sender: nil,
completion: submitLogsCompletion,
completion: { continuation.resume() },
)
},
))
alert.addAction(ActionSheetAction(
title: CommonStrings.cancelButton,
style: .cancel,
handler: { _ in submitLogsCompletion() },
handler: { _ in continuation.resume() },
))
presentingViewController.presentActionSheet(alert)
}
}
@MainActor
private static func uploadLogsUsingViewController(_ viewController: UIViewController, dumper: DebugLogDumper, completion: @escaping (URL) -> Void) {
AssertIsOnMainThread()
ModalActivityIndicatorViewController.present(
fromViewController: viewController,
private func uploadLogsWithUI(from viewController: UIViewController) async throws(DebugLogsError) -> URL? {
return try await ModalActivityIndicatorViewController.presentAndPropagateResult(
from: viewController,
canCancel: true,
asyncBlock: { await _uploadLogs(dumper: dumper, modalActivityIndicator: $0, completion: completion) },
)
}
@MainActor
private static func _uploadLogs(dumper: DebugLogDumper, modalActivityIndicator: ModalActivityIndicatorViewController, completion: @escaping (URL) -> Void) async {
do {
let url = try await uploadLogs(dumper: dumper)
guard !modalActivityIndicator.wasCancelled else { return }
modalActivityIndicator.dismiss {
completion(url)
}
} catch {
guard !modalActivityIndicator.wasCancelled else {
if let logArchiveOrDirectoryPath = error.logArchiveOrDirectoryPath {
OWSFileSystem.deleteFile(logArchiveOrDirectoryPath)
) { () throws(DebugLogsError) -> URL? in
do throws(DebugLogsError) {
return try await self.uploadLogs()
} catch {
if Task.isCancelled {
return nil
}
return
}
modalActivityIndicator.dismiss {
DebugLogs.showFailureAlert(
with: error.localizedErrorMessage,
logArchiveOrDirectoryPath: error.logArchiveOrDirectoryPath,
)
throw error
}
}
}
// MARK: - Collecting & uploading
private struct NoLogsError: Error {
var errorString: String {
OWSLocalizedString(
"DEBUG_LOG_ALERT_NO_LOGS",
comment: "Error indicating that no debug logs could be found.",
)
}
}
private static func collectLogs() -> Result<String, NoLogsError> {
private static func collectLogs() -> String? {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy.MM.dd hh.mm.ss"
let dateString = dateFormatter.string(from: Date())
@ -203,7 +275,7 @@ enum DebugLogs {
let logFilePaths = DebugLogger.shared.allLogFilePaths
if logFilePaths.isEmpty {
return .failure(NoLogsError())
return nil
}
for logFilePath in logFilePaths {
@ -219,50 +291,44 @@ enum DebugLogs {
OWSFileSystem.protectFileOrFolder(atPath: copyFilePath)
}
return .success(zipDirPath)
return zipDirPath
}
static func exportLogs() {
func exportLogs(viewController: UIViewController) {
AssertIsOnMainThread()
switch collectLogs() {
case let .success(logsDirPath):
AttachmentSharing.showShareUI(for: URL(fileURLWithPath: logsDirPath), sender: nil) {
OWSFileSystem.deleteFile(logsDirPath)
}
case let .failure(error):
Self.showFailureAlert(with: error.errorString, logArchiveOrDirectoryPath: nil)
return
guard let logsDirPath else {
return handleError(
error: .noLogs,
viewController: viewController,
)
}
AttachmentSharing.showShareUI(for: URL(fileURLWithPath: logsDirPath), sender: nil) {
OWSFileSystem.deleteFile(logsDirPath)
}
}
struct UploadDebugLogError: Error {
var localizedErrorMessage: String
var logArchiveOrDirectoryPath: String?
}
/// - Note: Various dependencies might not be initialized yet when this
/// method is called from the database recovery flow. Notably, the database
/// isn't available in that flow.
static func uploadLogs(dumper: DebugLogDumper) async throws(UploadDebugLogError) -> URL {
// Phase 1: Dump any additional details that are relevant.
private static func collectAndFlushLogs(
dumper: DebugLogDumper,
) -> String? {
// Dump any additional details that are relevant.
dumper.dump()
Logger.info("About to zip debug logs")
// Phase 2: Flush pending logs to disk.
// Flush pending logs to disk.
Logger.flush()
// Phase 3: Make a local copy of all of the log files.
let zipDirPath: String
switch collectLogs() {
case let .success(logsDirPath):
zipDirPath = logsDirPath
case let .failure(error):
throw UploadDebugLogError(localizedErrorMessage: error.errorString)
// Make a local copy of all of the log files.
return collectLogs()
}
func uploadLogs() async throws(DebugLogsError) -> URL {
guard let logsDirPath else {
throw DebugLogsError.noLogs
}
// Phase 4: Zip up the log files.
let zipDirUrl = URL(fileURLWithPath: zipDirPath)
let zipFileUrl = URL(fileURLWithPath: (zipDirPath as NSString).appendingPathExtension("zip")!)
// Zip up the log files.
let zipDirUrl = URL(fileURLWithPath: logsDirPath)
let zipFileUrl = URL(fileURLWithPath: (logsDirPath as NSString).appendingPathExtension("zip")!)
let fileCoordinator = NSFileCoordinator()
var zipError: NSError?
fileCoordinator.coordinate(readingItemAt: zipDirUrl, options: [.forUploading], error: &zipError) { temporaryFileUrl in
@ -273,38 +339,44 @@ enum DebugLogs {
}
}
if zipError != nil || !OWSFileSystem.fileOrFolderExists(url: zipFileUrl) {
let errorMessage = OWSLocalizedString(
"DEBUG_LOG_ALERT_COULD_NOT_PACKAGE_LOGS",
comment: "Error indicating that the debug logs could not be packaged.",
)
throw UploadDebugLogError(localizedErrorMessage: errorMessage, logArchiveOrDirectoryPath: zipDirPath)
throw DebugLogsError.couldNotPackageLogs
}
OWSFileSystem.protectFileOrFolder(atPath: zipFileUrl.path)
OWSFileSystem.deleteFile(zipDirPath)
// Phase 5: Upload the log files.
// Upload the log files.
do {
let url = try await DebugLogUploader.uploadFile(fileUrl: zipFileUrl, mimeType: MimeType.applicationZip.rawValue)
try OWSFileSystem.deleteFile(url: zipFileUrl)
return url
} catch {
let errorMessage = OWSLocalizedString(
"DEBUG_LOG_ALERT_ERROR_UPLOADING_LOG",
comment: "Error indicating that a debug log could not be uploaded.",
)
throw UploadDebugLogError(localizedErrorMessage: errorMessage, logArchiveOrDirectoryPath: zipFileUrl.path)
throw DebugLogsError.uploadError(zipFilePath: zipFileUrl.path)
}
}
private static func showFailureAlert(with message: String, logArchiveOrDirectoryPath: String?) {
let deleteArchive: (String) -> Void = { filePath in
OWSFileSystem.deleteFile(filePath)
private func handleError(
error: DebugLogsError,
viewController: UIViewController,
) {
let logsPath: String?
let completion: (() -> Void)?
switch error {
case .noLogs:
logsPath = nil
completion = nil
case .couldNotPackageLogs:
logsPath = self.logsDirPath
completion = nil
case .uploadError(let zipFilePath):
logsPath = zipFilePath
completion = {
OWSFileSystem.deleteFile(zipFilePath)
}
}
let alert = ActionSheetController(title: nil, message: message)
let alert = ActionSheetController(message: error.localizedErrorMessage)
if let logArchiveOrDirectoryPath {
if let logsPath {
alert.addAction(.init(
title: OWSLocalizedString(
"DEBUG_LOG_ALERT_OPTION_EXPORT_LOG_ARCHIVE",
@ -312,23 +384,18 @@ enum DebugLogs {
),
) { _ in
AttachmentSharing.showShareUI(
for: URL(fileURLWithPath: logArchiveOrDirectoryPath),
for: URL(fileURLWithPath: logsPath),
sender: nil,
completion: {
deleteArchive(logArchiveOrDirectoryPath)
},
completion: completion,
)
})
}
alert.addAction(.init(title: CommonStrings.okButton) { _ in
if let logArchiveOrDirectoryPath {
deleteArchive(logArchiveOrDirectoryPath)
}
completion?()
})
let presentingViewController = UIApplication.shared.frontmostViewControllerIgnoringAlerts
presentingViewController?.presentActionSheet(alert)
viewController.presentActionSheet(alert)
}
}

View File

@ -366,11 +366,13 @@ public class NotificationActionHandler {
@MainActor
private class func submitDebugLogs(supportTag: String?) async {
await withCheckedContinuation { continuation in
DebugLogs.submitLogs(supportTag: supportTag, dumper: .fromGlobals()) {
continuation.resume()
}
guard let viewController = CurrentAppContext().frontmostViewController() else {
return
}
await DebugLogs(dumper: .fromGlobals()).promptToSubmitLogs(
from: viewController,
supportTag: supportTag,
)
}
@MainActor

View File

@ -148,7 +148,11 @@ class ProvisioningController: NSObject {
@objc
@MainActor
private func submitLogs() {
DebugLogs.submitLogs(supportTag: "Onboarding", dumper: .fromGlobals())
guard let viewController = CurrentAppContext().frontmostViewController() else {
return
}
let logs = DebugLogs(dumper: .fromGlobals())
logs.promptToSubmitLogs(from: viewController, supportTag: "Onboarding")
}
// MARK: - Transitions

View File

@ -500,7 +500,8 @@ public class RegistrationNavigationController: OWSNavigationController {
))
self.present(navVc, animated: true)
} else {
DebugLogs.submitLogs(supportTag: "Registration", dumper: .fromGlobals())
let logs = DebugLogs(dumper: .fromGlobals())
logs.promptToSubmitLogs(from: self, supportTag: "Registration")
}
}
}

View File

@ -12,10 +12,10 @@ struct SupportEmailModel {
enum LogPolicy {
/// Attempt to upload the logs and include the resulting URL in the email body
/// If the upload fails for one reason or another, continue anyway
case attemptUpload(DebugLogDumper)
case attemptUpload(DebugLogs)
/// Upload the logs. If they fail to upload, fail the operation
case requireUpload(DebugLogDumper)
case requireUpload(DebugLogs)
/// Don't upload new logs, instead use the provided link
case link(URL)
@ -157,15 +157,15 @@ final class ComposeSupportEmailOperation: NSObject {
debugUrlString = nil
case .link(let url):
debugUrlString = url.absoluteString
case .attemptUpload(let dumper):
case .attemptUpload(let logs):
do {
debugUrlString = try await uploadDebugLogWithTimeout(dumper: dumper).absoluteString
debugUrlString = try await uploadDebugLogWithTimeout(logs: logs).absoluteString
} catch {
debugUrlString = "[Support note: Log upload failed — \(error.userErrorDescription)]"
}
case .requireUpload(let dumper):
case .requireUpload(let logs):
do {
debugUrlString = try await uploadDebugLogWithTimeout(dumper: dumper).absoluteString
debugUrlString = try await uploadDebugLogWithTimeout(logs: logs).absoluteString
} catch {
throw EmailError.logUploadFailure(underlyingError: (error as? LocalizedError))
}
@ -187,17 +187,16 @@ final class ComposeSupportEmailOperation: NSObject {
}
}
private func uploadDebugLogWithTimeout(dumper: DebugLogDumper) async throws -> URL {
private func uploadDebugLogWithTimeout(logs: DebugLogs) async throws -> URL {
do {
return try await withCooperativeTimeout(seconds: 60) {
do throws(DebugLogs.UploadDebugLogError) {
return try await DebugLogs.uploadLogs(dumper: dumper)
do throws(DebugLogs.DebugLogsError) {
return try await logs.uploadLogs()
} catch {
// FIXME: Should we do something with the local log file?
if let logArchiveOrDirectoryPath = error.logArchiveOrDirectoryPath {
_ = OWSFileSystem.deleteFile(logArchiveOrDirectoryPath)
if case .uploadError(zipFilePath: let zipPath) = error {
OWSFileSystem.deleteFile(zipPath)
}
throw DebugLogsUploadError(localizedDescription: error.localizedErrorMessage)
throw error
}
}
} catch is CooperativeTimeoutError {
@ -286,11 +285,3 @@ final class ComposeSupportEmailOperation: NSObject {
.joined(separator: "\r\n")
}
}
struct DebugLogsUploadError: Error, LocalizedError, UserErrorDescriptionProvider {
let localizedDescription: String
var errorDescription: String? {
localizedDescription
}
}

View File

@ -214,11 +214,15 @@ final class ContactSupportViewController: OWSTableViewController2, TextViewWithP
private func didTapNext() {
let logDumper = DebugLogDumper.fromGlobals()
var logPolicy: SupportEmailModel.LogPolicy?
if debugSwitch.isOn {
logPolicy = .attemptUpload(DebugLogs(dumper: logDumper))
}
let emailRequest = SupportEmailModel(
userDescription: descriptionField.text,
emojiMood: emojiPicker.selectedMood,
supportFilter: selectedFilter.map { "iOS \($0.emailFilterString)" },
debugLogPolicy: debugSwitch.isOn ? .attemptUpload(logDumper) : nil,
debugLogPolicy: logPolicy,
hasRecentChallenge: logDumper.challengeReceivedRecently(),
)

View File

@ -9,6 +9,16 @@ import SignalUI
final class DebugLogPreviewViewController: OWSViewController {
private let textView = UITextView()
private let logFilePaths: [String]
private let onSubmit: (() -> Void)?
private let onCancel: (() -> Void)?
init(logFilePaths: [String], onSubmit: (() -> Void)? = nil, onCancel: (() -> Void)? = nil) {
self.logFilePaths = logFilePaths
self.onSubmit = onSubmit
self.onCancel = onCancel
super.init()
}
override func viewDidLoad() {
super.viewDidLoad()
@ -22,7 +32,7 @@ final class DebugLogPreviewViewController: OWSViewController {
view.backgroundColor = .Signal.groupedBackground
navigationItem.rightBarButtonItem = .cancelButton(dismissingFrom: self)
navigationItem.rightBarButtonItem = .cancelButton(dismissingFrom: self, completion: onCancel)
// UITableView does not have the text-rendering optimizations that
// UITextView with scrolling enabled has, and the app freezes when
@ -64,12 +74,31 @@ final class DebugLogPreviewViewController: OWSViewController {
textView.textContainerInset = .init(margin: 20)
textView.textContainer.lineFragmentPadding = 0
textView.verticalScrollIndicatorInsets = .init(hMargin: 0, vMargin: OWSTableViewController2.cellRounding / 2)
if let onSubmit {
cellContainer.setContentHuggingPriority(.defaultLow, for: .vertical)
let submitButton = UIButton(
configuration: .largePrimary(title: OWSLocalizedString(
"DEBUG_LOG_PREVIEW_SUBMIT_BUTTON",
comment: "Button below the debug log preview which continues the submission flow",
)),
primaryAction: UIAction { _ in
onSubmit()
},
)
stackView.addArrangedSubview(submitButton)
submitButton.autoPinWidthToSuperviewMargins()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.presentationController?.delegate = self
}
private func loadLogs() {
Logger.flush()
self.textView.text = DebugLogger.shared.allLogFilePaths.reduce(
self.textView.text = self.logFilePaths.reduce(
into: "",
) { partialResult, logFilePath in
do {
@ -89,3 +118,11 @@ final class DebugLogPreviewViewController: OWSViewController {
}
}
// MARK: - UIAdaptivePresentationControllerDelegate
extension DebugLogPreviewViewController: UIAdaptivePresentationControllerDelegate {
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
onCancel?()
}
}

View File

@ -69,8 +69,9 @@ final class HelpViewController: OWSTableViewController2 {
loggingSection.add(.item(
name: OWSLocalizedString("SETTINGS_ADVANCED_SUBMIT_DEBUGLOG", comment: ""),
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "submit_debug_log"),
actionBlock: {
DebugLogs.submitLogs(dumper: .fromGlobals())
actionBlock: { [weak self] in
guard let self else { return }
DebugLogs(dumper: .fromGlobals()).promptToSubmitLogs(from: self)
},
))
contents.add(loggingSection)

View File

@ -88,8 +88,10 @@ class InternalSettingsViewController: OWSTableViewController2 {
))
if mode == .registration {
debugSection.add(.actionItem(withText: "Submit debug logs") {
DebugLogs.submitLogs(supportTag: "Registration", dumper: .fromGlobals())
debugSection.add(.actionItem(withText: "Submit debug logs") { [weak self] in
guard let self else { return }
let logs = DebugLogs(dumper: .fromGlobals())
logs.promptToSubmitLogs(from: self, supportTag: "Registration")
})
}

View File

@ -210,12 +210,10 @@ class DatabaseRecoveryViewController<SetupResult>: OWSViewController {
@objc
private func didRequestToSubmitDebugLogs() {
self.dismiss(animated: true) {
DebugLogs.submitLogs(
supportTag: "LaunchFailure_DatabaseRecoveryFailed",
dumper: .preLaunch(),
)
}
DebugLogs(dumper: .preLaunch()).promptToSubmitLogs(
from: self,
supportTag: "LaunchFailure_DatabaseRecoveryFailed",
)
}
private func attemptRecovery() {

View File

@ -676,7 +676,10 @@ private final class BackupArchiveErrorHeroSheet: HeroSheetViewController {
title: "Submit debug log",
action: { sheet in
sheet.dismiss(animated: true) {
DebugLogs.submitLogs(supportTag: "BackupArchive", dumper: .fromGlobals())
DebugLogs(dumper: .fromGlobals()).promptToSubmitLogs(
from: fromViewController,
supportTag: "BackupArchive",
)
}
},
),

View File

@ -2749,6 +2749,9 @@
/* Header text displayed above the debug log preview */
"DEBUG_LOG_PREVIEW_HEADER" = "When submitted, your log will be posted online for 30 days at a unique, unpublished URL.";
/* Button below the debug log preview which continues the submission flow */
"DEBUG_LOG_PREVIEW_SUBMIT_BUTTON" = "Submit";
/* Title for the debug log preview screen */
"DEBUG_LOG_PREVIEW_TITLE" = "Debug Log";