Debug: add in-memory log buffer and AppInfoView debug section with copy/clear logs (DEBUG only)

This commit is contained in:
jack
2025-09-13 23:11:38 +02:00
parent de2b5ed142
commit b30a30ecce
2 changed files with 108 additions and 1 deletions
+37 -1
View File
@@ -86,6 +86,38 @@ final class SecureLogger {
return level.order >= minimumLevel.order
}
// MARK: - In-memory debug buffer (DEBUG builds only)
#if DEBUG
private static let logBufferQueue = DispatchQueue(label: "chat.bitchat.securelogger.buffer")
private static var logBuffer: [String] = []
private static let logBufferCap: Int = 2000
/// Append a sanitized line to the in-memory debug buffer
private static func record(_ line: String) {
logBufferQueue.async {
logBuffer.append(line)
if logBuffer.count > logBufferCap {
logBuffer.removeFirst(logBuffer.count - logBufferCap)
}
}
}
/// Get the current logs as an array (oldest first)
static func getLogs() -> [String] {
return logBufferQueue.sync { logBuffer }
}
/// Get the current logs as a single string
static func getLogText() -> String {
return getLogs().joined(separator: "\n")
}
/// Clear the in-memory debug buffer
static func clearLogs() {
logBufferQueue.async { logBuffer.removeAll(keepingCapacity: false) }
}
#endif
// MARK: - Security Event Types
enum SecurityEvent {
@@ -159,7 +191,9 @@ final class SecureLogger {
let errorDesc = sanitize(error.localizedDescription)
#if DEBUG
os_log("%{public}@ Error in %{public}@: %{public}@", log: category, type: .error, location, sanitized, errorDesc)
let line = "\(location) Error: \(sanitized)\(errorDesc)"
os_log("%{public}@", log: category, type: .error, line)
record(line)
#else
os_log("%{private}@ Error in %{private}@: %{private}@", log: category, type: .error, location, sanitized, errorDesc)
#endif
@@ -203,6 +237,7 @@ private extension SecureLogger {
#if DEBUG
os_log("%{public}@", log: category, type: level.osLogType, sanitized)
record(sanitized)
#else
// In release builds, only log non-debug messages
if level != .debug {
@@ -220,6 +255,7 @@ private extension SecureLogger {
#if DEBUG
os_log("%{public}@", log: .security, type: level.osLogType, message)
record(message)
#else
// In release, use private logging to prevent sensitive data exposure
os_log("%{private}@", log: .security, type: level.osLogType, message)
+71
View File
@@ -1,4 +1,9 @@
import SwiftUI
#if os(iOS)
import UIKit
#elseif os(macOS)
import AppKit
#endif
struct AppInfoView: View {
@Environment(\.dismiss) var dismiss
@@ -191,12 +196,78 @@ struct AppInfoView: View {
.background(Color.red.opacity(0.1))
.cornerRadius(8)
#if DEBUG
// Debug section (visible only in Debug builds)
DebugLogsSection(textColor: textColor, secondaryTextColor: secondaryTextColor)
#endif
.padding(.top)
}
.padding()
}
}
#if DEBUG
private struct DebugLogsSection: View {
let textColor: Color
let secondaryTextColor: Color
@State private var logsText: String = SecureLogger.getLogText()
private let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
var body: some View {
VStack(alignment: .leading, spacing: 12) {
SectionHeader("DEBUG")
HStack(spacing: 12) {
Button {
copyToPasteboard(logsText)
} label: {
Text("copy logs")
.font(.system(size: 14, design: .monospaced))
.foregroundColor(textColor)
}
.buttonStyle(.plain)
Button {
SecureLogger.clearLogs()
logsText = ""
} label: {
Text("clear logs")
.font(.system(size: 14, design: .monospaced))
.foregroundColor(textColor)
}
.buttonStyle(.plain)
}
ScrollView {
Text(logsText.isEmpty ? "(no logs)" : logsText)
.font(.system(size: 12, design: .monospaced))
.foregroundColor(secondaryTextColor)
.frame(maxWidth: .infinity, alignment: .leading)
.textSelection(.enabled)
.padding(8)
}
.frame(minHeight: 180)
.background(secondaryTextColor.opacity(0.08))
.cornerRadius(8)
}
.onReceive(timer) { _ in
logsText = SecureLogger.getLogText()
}
}
private func copyToPasteboard(_ text: String) {
#if os(iOS)
UIPasteboard.general.string = text
#elseif os(macOS)
let pb = NSPasteboard.general
pb.clearContents()
pb.setString(text, forType: .string)
#endif
}
}
#endif
struct SectionHeader: View {
let title: String
@Environment(\.colorScheme) var colorScheme