ReadKeep/readeck/UI/Settings/LoggingConfigurationView.swift
Ilyas Hallak 7338db5fab Improve debug logging system
- Redesign logging configuration UI with cleaner list-based navigation
- Replace segmented controls with detailed selection screens for better UX
- Add in-app debug log viewer with filtering and search capabilities
- Implement opt-in logging toggle to reduce device performance impact
- Add log storage system with 1000 entry limit
- Enable log export via share sheet
- Show warning banner when logging is disabled
2025-11-01 13:54:40 +01:00

233 lines
7.8 KiB
Swift

//
// LoggingConfigurationView.swift
// readeck
//
// Created by Ilyas Hallak on 16.08.25.
//
import SwiftUI
import os
struct LoggingConfigurationView: View {
@StateObject private var logConfig = LogConfiguration.shared
private let logger = Logger.ui
var body: some View {
List {
Section {
Toggle("Enable Logging", isOn: $logConfig.isLoggingEnabled)
.tint(.green)
} header: {
Text("Logging Status")
} footer: {
Text("Enable logging to capture debug messages. When disabled, no logs are recorded to reduce device performance impact.")
}
if logConfig.isLoggingEnabled {
Section {
NavigationLink {
GlobalLogLevelView(logConfig: logConfig)
} label: {
HStack {
Label("Global Log Level", systemImage: "slider.horizontal.3")
Spacer()
Text(levelName(for: logConfig.globalMinLevel))
.foregroundColor(.secondary)
}
}
Toggle("Show Performance Logs", isOn: $logConfig.showPerformanceLogs)
Toggle("Show Timestamps", isOn: $logConfig.showTimestamps)
Toggle("Include Source Location", isOn: $logConfig.includeSourceLocation)
} header: {
Text("Global Settings")
} footer: {
Text("Logs below the global level will be filtered out globally")
}
}
if logConfig.isLoggingEnabled {
Section {
ForEach(LogCategory.allCases, id: \.self) { category in
NavigationLink {
CategoryLogLevelView(category: category, logConfig: logConfig)
} label: {
HStack {
Text(category.rawValue)
Spacer()
Text(levelName(for: logConfig.getLevel(for: category)))
.foregroundColor(.secondary)
}
}
}
} header: {
Text("Category-specific Levels")
} footer: {
Text("Configure log levels for each category individually")
}
}
Section {
Button(role: .destructive) {
resetToDefaults()
} label: {
Label("Reset to Defaults", systemImage: "arrow.counterclockwise")
}
}
}
.navigationTitle("Logging Configuration")
.navigationBarTitleDisplayMode(.inline)
.onAppear {
logger.debug("Opened logging configuration view")
}
}
private func levelName(for level: LogLevel) -> String {
switch level.rawValue {
case 0: return "Debug"
case 1: return "Info"
case 2: return "Notice"
case 3: return "Warning"
case 4: return "Error"
case 5: return "Critical"
default: return "Unknown"
}
}
private func resetToDefaults() {
logger.info("Resetting logging configuration to defaults")
for category in LogCategory.allCases {
logConfig.setLevel(.debug, for: category)
}
logConfig.globalMinLevel = .debug
logConfig.showPerformanceLogs = true
logConfig.showTimestamps = true
logConfig.includeSourceLocation = true
logger.info("Logging configuration reset to defaults")
}
}
// MARK: - Global Log Level View
struct GlobalLogLevelView: View {
@ObservedObject var logConfig: LogConfiguration
var body: some View {
List {
ForEach(LogLevel.allCases, id: \.self) { level in
Button {
logConfig.globalMinLevel = level
} label: {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text(levelName(for: level))
.foregroundColor(.primary)
Text(levelDescription(for: level))
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
if logConfig.globalMinLevel == level {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
}
}
}
}
}
.navigationTitle("Global Log Level")
.navigationBarTitleDisplayMode(.inline)
}
private func levelName(for level: LogLevel) -> String {
switch level.rawValue {
case 0: return "Debug"
case 1: return "Info"
case 2: return "Notice"
case 3: return "Warning"
case 4: return "Error"
case 5: return "Critical"
default: return "Unknown"
}
}
private func levelDescription(for level: LogLevel) -> String {
switch level.rawValue {
case 0: return "Show all logs including debug information"
case 1: return "Show informational messages and above"
case 2: return "Show notable events and above"
case 3: return "Show warnings and errors only"
case 4: return "Show errors and critical issues only"
case 5: return "Show only critical issues"
default: return ""
}
}
}
// MARK: - Category Log Level View
struct CategoryLogLevelView: View {
let category: LogCategory
@ObservedObject var logConfig: LogConfiguration
var body: some View {
List {
ForEach(LogLevel.allCases, id: \.self) { level in
Button {
logConfig.setLevel(level, for: category)
} label: {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text(levelName(for: level))
.foregroundColor(.primary)
Text(levelDescription(for: level))
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
if logConfig.getLevel(for: category) == level {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
}
}
}
}
}
.navigationTitle("\(category.rawValue) Logs")
.navigationBarTitleDisplayMode(.inline)
}
private func levelName(for level: LogLevel) -> String {
switch level.rawValue {
case 0: return "Debug"
case 1: return "Info"
case 2: return "Notice"
case 3: return "Warning"
case 4: return "Error"
case 5: return "Critical"
default: return "Unknown"
}
}
private func levelDescription(for level: LogLevel) -> String {
switch level.rawValue {
case 0: return "Show all logs including debug information"
case 1: return "Show informational messages and above"
case 2: return "Show notable events and above"
case 3: return "Show warnings and errors only"
case 4: return "Show errors and critical issues only"
case 5: return "Show only critical issues"
default: return ""
}
}
}
#Preview {
NavigationStack {
LoggingConfigurationView()
}
}