mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-15 20:10:30 +00:00
Merge pull request #31 from neoarz/main
Add logger view (please review and test before merging)
This commit is contained in:
commit
ab67f379e0
7 changed files with 489 additions and 4 deletions
|
|
@ -655,4 +655,4 @@
|
|||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = DC6F1D2F2D94EADD0071B2B6 /* Project object */;
|
||||
}
|
||||
}
|
||||
91
StikJIT/Utilities/LogManager.swift
Normal file
91
StikJIT/Utilities/LogManager.swift
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// LogManager.swift
|
||||
// StikJIT
|
||||
//
|
||||
// Created by neoarz on 3/29/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class LogManager: ObservableObject {
|
||||
static let shared = LogManager()
|
||||
|
||||
@Published var logs: [LogEntry] = []
|
||||
@Published var errorCount: Int = 0
|
||||
|
||||
struct LogEntry: Identifiable {
|
||||
let id = UUID()
|
||||
let timestamp: Date
|
||||
let type: LogType
|
||||
let message: String
|
||||
|
||||
enum LogType: String {
|
||||
case info = "INFO"
|
||||
case error = "ERROR"
|
||||
case debug = "DEBUG"
|
||||
case warning = "WARNING"
|
||||
}
|
||||
}
|
||||
|
||||
private init() {
|
||||
// Add initial system info logs
|
||||
addInfoLog("StikJIT starting up")
|
||||
addInfoLog("Initializing environment")
|
||||
}
|
||||
|
||||
func addLog(message: String, type: LogEntry.LogType) {
|
||||
//clean dumb stuff
|
||||
var cleanMessage = message
|
||||
|
||||
// Clean up common prefixes that match the log type
|
||||
let prefixesToRemove = [
|
||||
"Info: ", "INFO: ", "Information: ",
|
||||
"Error: ", "ERROR: ", "ERR: ",
|
||||
"Debug: ", "DEBUG: ", "DBG: ",
|
||||
"Warning: ", "WARN: ", "WARNING: "
|
||||
]
|
||||
|
||||
for prefix in prefixesToRemove {
|
||||
if cleanMessage.hasPrefix(prefix) {
|
||||
cleanMessage = String(cleanMessage.dropFirst(prefix.count))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.logs.append(LogEntry(timestamp: Date(), type: type, message: cleanMessage))
|
||||
|
||||
if type == .error {
|
||||
self.errorCount += 1
|
||||
}
|
||||
|
||||
// Keep log size manageable
|
||||
if self.logs.count > 1000 {
|
||||
self.logs.removeFirst(100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addInfoLog(_ message: String) {
|
||||
addLog(message: message, type: .info)
|
||||
}
|
||||
|
||||
func addErrorLog(_ message: String) {
|
||||
addLog(message: message, type: .error)
|
||||
}
|
||||
|
||||
func addDebugLog(_ message: String) {
|
||||
addLog(message: message, type: .debug)
|
||||
}
|
||||
|
||||
func addWarningLog(_ message: String) {
|
||||
addLog(message: message, type: .warning)
|
||||
}
|
||||
|
||||
func clearLogs() {
|
||||
DispatchQueue.main.async {
|
||||
self.logs.removeAll()
|
||||
self.errorCount = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
32
StikJIT/Utilities/LogManagerBridge.swift
Normal file
32
StikJIT/Utilities/LogManagerBridge.swift
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// LogManagerBridge.swift
|
||||
// StikJIT
|
||||
//
|
||||
// Created by neoarz on 3/29/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
//objc bridge for logs
|
||||
@objc class LogManagerBridge: NSObject {
|
||||
@objc static let shared = LogManagerBridge()
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
@objc func addInfoLog(_ message: String) {
|
||||
LogManager.shared.addInfoLog(message)
|
||||
}
|
||||
|
||||
@objc func addErrorLog(_ message: String) {
|
||||
LogManager.shared.addErrorLog(message)
|
||||
}
|
||||
|
||||
@objc func addDebugLog(_ message: String) {
|
||||
LogManager.shared.addDebugLog(message)
|
||||
}
|
||||
|
||||
@objc func addWarningLog(_ message: String) {
|
||||
LogManager.shared.addWarningLog(message)
|
||||
}
|
||||
}
|
||||
303
StikJIT/Views/ConsoleLogsView.swift
Normal file
303
StikJIT/Views/ConsoleLogsView.swift
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
//
|
||||
// ConsoleLogsView.swift
|
||||
// StikJIT
|
||||
//
|
||||
// Created by neoarz on 3/29/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
struct ConsoleLogsView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@StateObject private var logManager = LogManager.shared
|
||||
@State private var autoScroll = true
|
||||
@State private var scrollView: ScrollViewProxy? = nil
|
||||
|
||||
// Alert handlin
|
||||
@State private var showingExportAlert = false
|
||||
@State private var showingCopyAlert = false
|
||||
@State private var alertMessage = ""
|
||||
@State private var alertTitle = ""
|
||||
@State private var isError = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
Color.black
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
// Terminal logs area (made it look somewhat like feathers implementation)
|
||||
ScrollViewReader { proxy in
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
// Device Information (all thw way on top)
|
||||
ForEach(["Version: \(UIDevice.current.systemVersion)",
|
||||
"Name: \(UIDevice.current.name)",
|
||||
"Model: \(UIDevice.current.model)",
|
||||
"StikJIT Version: App Version: 1.0"], id: \.self) { info in
|
||||
Text("[\(timeString())] ℹ️ \(info)")
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.vertical, 2)
|
||||
.padding(.horizontal, 4)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
// Log entries
|
||||
ForEach(logManager.logs) { logEntry in
|
||||
Text(AttributedString(createLogAttributedString(logEntry)))
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.textSelection(.enabled)
|
||||
.lineLimit(nil)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.vertical, 1)
|
||||
.padding(.horizontal, 4)
|
||||
.id(logEntry.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
scrollView = proxy
|
||||
}
|
||||
.onChange(of: logManager.logs.count) {
|
||||
if autoScroll, let lastLog = logManager.logs.last {
|
||||
proxy.scrollTo(lastLog.id, anchor: .bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
|
||||
VStack(spacing: 16) {
|
||||
// Error count with red theme
|
||||
HStack {
|
||||
Text("\(logManager.errorCount) Critical Errors.")
|
||||
.font(.headline)
|
||||
.foregroundColor(.white)
|
||||
.padding(.vertical, 12)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.red)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
// Action buttons with dark background
|
||||
VStack(spacing: 1) {
|
||||
// Export button
|
||||
Button(action: {
|
||||
// Create logs content with device information
|
||||
var logsContent = "=== DEVICE INFORMATION ===\n"
|
||||
logsContent += "Version: \(UIDevice.current.systemVersion)\n"
|
||||
logsContent += "Name: \(UIDevice.current.name)\n"
|
||||
logsContent += "Model: \(UIDevice.current.model)\n"
|
||||
logsContent += "StikJIT Version: App Version: 1.0\n\n"
|
||||
logsContent += "=== LOG ENTRIES ===\n"
|
||||
|
||||
// Add all log entries with proper formatting
|
||||
logsContent += logManager.logs.map {
|
||||
"[\(formatTime(date: $0.timestamp))] [\($0.type.rawValue)] \($0.message)"
|
||||
}.joined(separator: "\n")
|
||||
|
||||
// Save to document directory (accessible in Files app)
|
||||
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd_HH-mm-ss"
|
||||
let timestamp = dateFormatter.string(from: Date())
|
||||
let fileURL = documentsDirectory.appendingPathComponent("StikJIT_Logs_\(timestamp).txt")
|
||||
|
||||
do {
|
||||
// Write the logs to the file
|
||||
try logsContent.write(to: fileURL, atomically: true, encoding: .utf8)
|
||||
|
||||
// Set alert variables and show the alert
|
||||
alertTitle = "Logs Exported"
|
||||
alertMessage = "Logs have been saved to Files app in StikJIT folder."
|
||||
isError = false
|
||||
showingExportAlert = true
|
||||
} catch {
|
||||
// Set error alert variables and show the alert
|
||||
alertTitle = "Export Failed"
|
||||
alertMessage = "Failed to save logs: \(error.localizedDescription)"
|
||||
isError = true
|
||||
showingExportAlert = true
|
||||
}
|
||||
}) {
|
||||
HStack {
|
||||
Text("Export Logs")
|
||||
.foregroundColor(.blue)
|
||||
Spacer()
|
||||
Image(systemName: "square.and.arrow.down")
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.padding(.vertical, 14)
|
||||
.padding(.horizontal, 20)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.background(Color(red: 0.1, green: 0.1, blue: 0.1))
|
||||
|
||||
Divider()
|
||||
.background(Color(red: 0.15, green: 0.15, blue: 0.15))
|
||||
|
||||
// Copy button
|
||||
Button(action: {
|
||||
// Create logs content with device information
|
||||
var logsContent = "=== DEVICE INFORMATION ===\n"
|
||||
logsContent += "Version: \(UIDevice.current.systemVersion)\n"
|
||||
logsContent += "Name: \(UIDevice.current.name)\n"
|
||||
logsContent += "Model: \(UIDevice.current.model)\n"
|
||||
logsContent += "StikJIT Version: App Version: 1.0\n\n"
|
||||
logsContent += "=== LOG ENTRIES ===\n"
|
||||
|
||||
// Add all log entries with proper formatting
|
||||
logsContent += logManager.logs.map {
|
||||
"[\(formatTime(date: $0.timestamp))] [\($0.type.rawValue)] \($0.message)"
|
||||
}.joined(separator: "\n")
|
||||
|
||||
// Copy to clipboard
|
||||
UIPasteboard.general.string = logsContent
|
||||
|
||||
// Show success alert using SwiftUI alert
|
||||
alertTitle = "Logs Copied"
|
||||
alertMessage = "Logs have been copied to clipboard."
|
||||
isError = false
|
||||
showingCopyAlert = true
|
||||
}) {
|
||||
HStack {
|
||||
Text("Copy Logs")
|
||||
.foregroundColor(.blue)
|
||||
Spacer()
|
||||
Image(systemName: "doc.on.doc")
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.padding(.vertical, 14)
|
||||
.padding(.horizontal, 20)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.background(Color(red: 0.1, green: 0.1, blue: 0.1))
|
||||
}
|
||||
.cornerRadius(10)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 20)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .principal) {
|
||||
Text("Console Logs")
|
||||
.font(.headline)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button(action: {
|
||||
dismiss()
|
||||
}) {
|
||||
HStack(spacing: 2) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 16, weight: .semibold))
|
||||
Text("Settings")
|
||||
.fontWeight(.regular)
|
||||
}
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(action: {
|
||||
logManager.clearLogs()
|
||||
}) {
|
||||
Text("Clear")
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.alert(alertTitle, isPresented: $showingExportAlert) {
|
||||
Button("OK", role: .cancel) { }
|
||||
} message: {
|
||||
Text(alertMessage)
|
||||
}
|
||||
.alert(alertTitle, isPresented: $showingCopyAlert) {
|
||||
Button("OK", role: .cancel) { }
|
||||
} message: {
|
||||
Text(alertMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// Creates an NSAttributedString that combines timestamp, type, and message
|
||||
// with proper styling for each component
|
||||
private func createLogAttributedString(_ logEntry: LogManager.LogEntry) -> NSAttributedString {
|
||||
let fullString = NSMutableAttributedString()
|
||||
|
||||
// Timestamp part
|
||||
let timestampString = "[\(formatTime(date: logEntry.timestamp))]"
|
||||
let timestampAttr = NSAttributedString(
|
||||
string: timestampString,
|
||||
attributes: [.foregroundColor: UIColor.gray]
|
||||
)
|
||||
fullString.append(timestampAttr)
|
||||
fullString.append(NSAttributedString(string: " "))
|
||||
|
||||
// Log type part
|
||||
let typeString = "[\(logEntry.type.rawValue)]"
|
||||
let typeColor = UIColor(colorForLogType(logEntry.type))
|
||||
let typeAttr = NSAttributedString(
|
||||
string: typeString,
|
||||
attributes: [.foregroundColor: typeColor]
|
||||
)
|
||||
fullString.append(typeAttr)
|
||||
fullString.append(NSAttributedString(string: " "))
|
||||
|
||||
// Message part
|
||||
let messageAttr = NSAttributedString(
|
||||
string: logEntry.message,
|
||||
attributes: [.foregroundColor: UIColor.white]
|
||||
)
|
||||
fullString.append(messageAttr)
|
||||
|
||||
return fullString
|
||||
}
|
||||
|
||||
// Helper to display current time
|
||||
private func timeString() -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "HH:mm:ss"
|
||||
return formatter.string(from: Date())
|
||||
}
|
||||
|
||||
// Helper to format Date objects to time strings
|
||||
private func formatTime(date: Date) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "HH:mm:ss"
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
|
||||
// Return color based on log type
|
||||
private func colorForLogType(_ type: LogManager.LogEntry.LogType) -> Color {
|
||||
switch type {
|
||||
case .info:
|
||||
return .green
|
||||
case .error:
|
||||
return .red
|
||||
case .debug:
|
||||
return .blue
|
||||
case .warning:
|
||||
return .orange
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ConsoleLogsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ConsoleLogsView()
|
||||
}
|
||||
}
|
||||
|
|
@ -259,11 +259,20 @@ struct HomeView: View {
|
|||
|
||||
private func startJITInBackground(with bundleID: String) {
|
||||
isProcessing = true
|
||||
|
||||
// Add log message
|
||||
LogManager.shared.addInfoLog("Starting JIT for \(bundleID)")
|
||||
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
|
||||
JITEnableContext.shared().debugApp(withBundleID: bundleID, logger: nil)
|
||||
JITEnableContext.shared().debugApp(withBundleID: bundleID, logger: { message in
|
||||
if let message = message {
|
||||
// Log messages from the JIT process
|
||||
LogManager.shared.addInfoLog(message)
|
||||
}
|
||||
})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
LogManager.shared.addInfoLog("JIT process completed for \(bundleID)")
|
||||
isProcessing = false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ struct SettingsView: View {
|
|||
|
||||
@State private var mounted = false
|
||||
|
||||
@State private var showingConsoleLogsView = false
|
||||
|
||||
// Developer profile image URLs
|
||||
private let developerProfiles: [String: String] = [
|
||||
"Blu": "https://github.com/0-Blu.png",
|
||||
|
|
@ -361,8 +363,32 @@ struct SettingsView: View {
|
|||
.padding(.vertical, 20)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
.padding(.bottom, 16)
|
||||
|
||||
// Version text - now outside of any card, as standalone text at the bottom
|
||||
// Move System Logs section here (right after About card, before version)
|
||||
SettingsCard {
|
||||
Button(action: {
|
||||
showingConsoleLogsView = true
|
||||
}) {
|
||||
HStack {
|
||||
Text("System Logs")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.padding(.vertical, 16)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
.padding(.bottom, 16)
|
||||
|
||||
// Version info should now come after System Logs
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("Version 1.0 • iOS \(UIDevice.current.systemVersion)")
|
||||
|
|
@ -376,6 +402,11 @@ struct SettingsView: View {
|
|||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 20)
|
||||
}
|
||||
|
||||
// Add this sheet at the end of the ZStack, before the final closing bracket
|
||||
.sheet(isPresented: $showingConsoleLogsView) {
|
||||
ConsoleLogsView()
|
||||
}
|
||||
}
|
||||
.fileImporter(
|
||||
isPresented: $isShowingPairingFilePicker,
|
||||
|
|
@ -655,3 +686,10 @@ struct CollaboratorRow: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define these in a separate file if they conflict
|
||||
struct ConsoleLogsView_Preview: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ConsoleLogsView()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "applist.h"
|
||||
|
||||
#include "JITEnableContext.h"
|
||||
#import "StikJIT-Swift.h" // This imports the Swift files into Objective-C
|
||||
|
||||
JITEnableContext* sharedJITContext = nil;
|
||||
|
||||
|
|
@ -41,6 +42,17 @@ JITEnableContext* sharedJITContext = nil;
|
|||
NSString *message = [[NSString alloc] initWithFormat:formatStr arguments:args];
|
||||
NSLog(@"%@", message);
|
||||
|
||||
// Add to log manager
|
||||
if ([message containsString:@"ERROR"] || [message containsString:@"Error"]) {
|
||||
[[LogManagerBridge shared] addErrorLog:message];
|
||||
} else if ([message containsString:@"WARNING"] || [message containsString:@"Warning"]) {
|
||||
[[LogManagerBridge shared] addWarningLog:message];
|
||||
} else if ([message containsString:@"DEBUG"]) {
|
||||
[[LogManagerBridge shared] addDebugLog:message];
|
||||
} else {
|
||||
[[LogManagerBridge shared] addInfoLog:message];
|
||||
}
|
||||
|
||||
if(logger) {
|
||||
logger(message);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue