Skip to content

Commit

Permalink
Create log viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
vishnuravi committed Nov 8, 2024
1 parent e4fd6db commit 8bb0834
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 1 deletion.
20 changes: 20 additions & 0 deletions LifeSpace.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
6347EB742BBBF442008E0C4A /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6347EB732BBBF442008E0C4A /* Constants.swift */; };
63497B702BBF6ECE001F8419 /* LocationDataPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63497B6F2BBF6ECE001F8419 /* LocationDataPoint.swift */; };
63497B732BBF855E001F8419 /* OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63497B722BBF855E001F8419 /* OptionsPanel.swift */; };
634E38422CDE6B4000B16E20 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 634E38412CDE6B3B00B16E20 /* LogManager.swift */; };
634E38442CDE6E2B00B16E20 /* LogViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 634E38432CDE6E2900B16E20 /* LogViewer.swift */; };
634E38482CDE7A7400B16E20 /* LogType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 634E38472CDE7A7100B16E20 /* LogType.swift */; };
634FFF672C169F40005E8217 /* LifeSpaceConsent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 634FFF662C169F40005E8217 /* LifeSpaceConsent.swift */; };
634FFF6D2C16B81A005E8217 /* HIPAAAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 634FFF6C2C16B81A005E8217 /* HIPAAAuthorization.swift */; };
635198792CD53FF40087B1F3 /* FirebaseConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 635198782CD53FF10087B1F3 /* FirebaseConfiguration.swift */; };
Expand Down Expand Up @@ -137,6 +140,9 @@
6347EB732BBBF442008E0C4A /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
63497B6F2BBF6ECE001F8419 /* LocationDataPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDataPoint.swift; sourceTree = "<group>"; };
63497B722BBF855E001F8419 /* OptionsPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionsPanel.swift; sourceTree = "<group>"; };
634E38412CDE6B3B00B16E20 /* LogManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogManager.swift; sourceTree = "<group>"; };
634E38432CDE6E2900B16E20 /* LogViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewer.swift; sourceTree = "<group>"; };
634E38472CDE7A7100B16E20 /* LogType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogType.swift; sourceTree = "<group>"; };
634FFF662C169F40005E8217 /* LifeSpaceConsent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LifeSpaceConsent.swift; sourceTree = "<group>"; };
634FFF6C2C16B81A005E8217 /* HIPAAAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HIPAAAuthorization.swift; sourceTree = "<group>"; };
635198782CD53FF10087B1F3 /* FirebaseConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseConfiguration.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -302,6 +308,16 @@
path = Map;
sourceTree = "<group>";
};
634E38462CDE790800B16E20 /* Debug */ = {
isa = PBXGroup;
children = (
634E38472CDE7A7100B16E20 /* LogType.swift */,
634E38412CDE6B3B00B16E20 /* LogManager.swift */,
634E38432CDE6E2900B16E20 /* LogViewer.swift */,
);
path = Debug;
sourceTree = "<group>";
};
635198772CD53FE30087B1F3 /* Firestore */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -361,6 +377,7 @@
653A254F283387FE005D4D48 /* LifeSpace */ = {
isa = PBXGroup;
children = (
634E38462CDE790800B16E20 /* Debug */,
635198772CD53FE30087B1F3 /* Firestore */,
A9720E412ABB68B300872D23 /* Account */,
637AA5CF2BBDA686007BD7A3 /* Location */,
Expand Down Expand Up @@ -634,6 +651,7 @@
2FE5DCB129EE6107004B9AB4 /* AccountOnboarding.swift in Sources */,
2FE5DC3A29EDD7CA004B9AB4 /* Welcome.swift in Sources */,
634FFF672C169F40005E8217 /* LifeSpaceConsent.swift in Sources */,
634E38482CDE7A7400B16E20 /* LogType.swift in Sources */,
2FE5DC3829EDD7CA004B9AB4 /* InterestingModules.swift in Sources */,
2FE5DC3529EDD7CA004B9AB4 /* Consent.swift in Sources */,
630D3B572C616E9D006066E5 /* WithdrawView.swift in Sources */,
Expand Down Expand Up @@ -664,8 +682,10 @@
2F4E23832989D51F0013F3D9 /* LifeSpaceTestingSetup.swift in Sources */,
63F4C39B2BBCCCF80033D985 /* LocationModule.swift in Sources */,
6347EB742BBBF442008E0C4A /* Constants.swift in Sources */,
634E38442CDE6E2B00B16E20 /* LogViewer.swift in Sources */,
63F4C39D2BBCCD200033D985 /* LocationUtils.swift in Sources */,
2FE5DC5329EDD7FA004B9AB4 /* Bundle+Questionnaire.swift in Sources */,
634E38422CDE6B4000B16E20 /* LogManager.swift in Sources */,
634FFF6D2C16B81A005E8217 /* HIPAAAuthorization.swift in Sources */,
2FE5DC5129EDD7FA004B9AB4 /* LifeSpaceTaskContext.swift in Sources */,
63EA5F7B2BC04F8400A48590 /* DailySurveyTask.swift in Sources */,
Expand Down
11 changes: 11 additions & 0 deletions LifeSpace/Account/AccountSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ struct AccountSheet: View {
locationTrackingToggle
withdrawButton
}
Section(header: Text("DEBUG_SECTION")) {
logExportButton
}
}
}

Expand Down Expand Up @@ -170,6 +173,14 @@ struct AccountSheet: View {
}
}

private var logExportButton: some View {
NavigationLink(destination: {
LogViewer()
}) {
Text("VIEW_LOGS")
}
}


private func getDocumentURL(for fileName: String) -> URL? {
guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
Expand Down
61 changes: 61 additions & 0 deletions LifeSpace/Debug/LogManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// LogStore.swift
// LifeSpace
//
// Created by Vishnu Ravi on 11/8/24.
//

import Foundation
import OSLog
import Spezi
import SwiftUI

actor LogManager: Module, DefaultInitializable, EnvironmentAccessible {
@Application(\.logger) private var logger

func query(
startDate: Date? = nil,
endDate: Date? = nil,
logType: OSLogEntryLog.Level? = nil
) -> String {
do {
let store = try OSLogStore(scope: .currentProcessIdentifier)
let position: OSLogPosition
if let startDate = startDate {
position = store.position(date: startDate)
} else {
position = store.position(timeIntervalSinceLatestBoot: 1)
}

let logs = try store.getEntries(at: position).compactMap { $0 as? OSLogEntryLog }

return logs
.filter { logEntry in
/// Filter by subsystem
guard logEntry.subsystem == Bundle.main.bundleIdentifier else {
return false
}

/// Filter by log type if specified
if let logType = logType, logEntry.level != logType {
return false
}

/// Filter by date range if specified
if let startDate = startDate, logEntry.date < startDate {
return false
}
if let endDate = endDate, logEntry.date > endDate {
return false
}

return true
}
.map { "[\($0.date.formatted())] [\($0.category)] \($0.composedMessage)" }
.joined(separator: "\n")
} catch {
logger.warning("\(error.localizedDescription, privacy: .public)")
return ""
}
}
}
34 changes: 34 additions & 0 deletions LifeSpace/Debug/LogType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// LogType.swift
// LifeSpace
//
// Created by Vishnu Ravi on 11/8/24.
//

import OSLog


enum LogType: String, CaseIterable, Identifiable {
case all = "All"
case info = "Info"
case debug = "Debug"
case error = "Error"
case fault = "Fault"

var id: String { self.rawValue }

var osLogLevel: OSLogEntryLog.Level? {
switch self {
case .all:
return nil
case .info:
return .info
case .debug:
return .debug
case .error:
return .error
case .fault:
return .fault
}
}
}
95 changes: 95 additions & 0 deletions LifeSpace/Debug/LogViewer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// LogShareView.swift
// LifeSpace
//
// Created by Vishnu Ravi on 11/8/24.
//

import OSLog
import Spezi
import SwiftUI


struct LogViewer: View {
@Environment(LogManager.self) var manager

@State private var startDate: Date = Calendar.current.date(byAdding: .day, value: -1, to: Date()) ?? Date()
@State private var endDate = Date()
@State private var selectedLogType: LogType = .all
@State private var logs = ""
@State private var isLoading = false

var body: some View {
VStack {
/// Date range selection
HStack {
DatePicker("FROM", selection: $startDate, displayedComponents: .date)
Spacer()
DatePicker("TO", selection: $endDate, displayedComponents: .date)
}
.padding()

/// Log type selection
Picker("LOG_TYPE", selection: $selectedLogType) {
ForEach(LogType.allCases) { type in
Text(type.rawValue).tag(type)
}
}
.pickerStyle(SegmentedPickerStyle())
.padding()

ScrollView {
if isLoading {
ProgressView("LOADING_LOGS")
.padding()
} else {
Text(logs)
.padding()
}
}
}
.navigationTitle("LOG_VIEWER")
.onAppear {
Task {
await queryLogs()
}
}
.onChange(of: startDate) {
Task {
await queryLogs()
}
}
.onChange(of: endDate) {
Task {
await queryLogs()
}
}
.onChange(of: selectedLogType) {
Task {
await queryLogs()
}
}
.toolbar {
if !logs.isEmpty {
ShareLink(
item: logs,
preview: SharePreview("LOGS", image: Image(systemName: "doc.text"))

Check failure on line 76 in LifeSpace/Debug/LogViewer.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Accessibility Label for Image Violation: Images that provide context should have an accessibility label or should be explicitly hidden from accessibility (accessibility_label_for_image)
) {
Image(systemName: "square.and.arrow.up")

Check failure on line 78 in LifeSpace/Debug/LogViewer.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Accessibility Label for Image Violation: Images that provide context should have an accessibility label or should be explicitly hidden from accessibility (accessibility_label_for_image)
}
}
}
}

@MainActor
private func queryLogs() async {
isLoading = true

/// This is very slow, so run as a detached task with high priority
logs = await Task.detached(priority: .userInitiated) { [manager, startDate, endDate, selectedLogType] in
await manager.query(startDate: startDate, endDate: endDate, logType: selectedLogType.osLogLevel)
}.value

isLoading = false
}
}
Loading

0 comments on commit 8bb0834

Please sign in to comment.