mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-12 00:03:27 +01:00
[Console-Log]: Added raw console logging in ErrorLog section (ladybug icon)
This commit is contained in:
12
SideStore/Utils/common/AbstractClassError.swift
Normal file
12
SideStore/Utils/common/AbstractClassError.swift
Normal file
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// OutputStream.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 28/12/24.
|
||||
// Copyright © 2024 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
public enum AbstractClassError: Error {
|
||||
case abstractInitializerInvoked
|
||||
case abstractMethodInvoked
|
||||
}
|
||||
29
SideStore/Utils/common/FileOutputStream.swift
Normal file
29
SideStore/Utils/common/FileOutputStream.swift
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// FileOutputStream.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 28/12/24.
|
||||
// Copyright © 2024 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class FileOutputStream: OutputStream {
|
||||
private let fileHandle: FileHandle
|
||||
|
||||
init(_ fileHandle: FileHandle) {
|
||||
self.fileHandle = fileHandle
|
||||
}
|
||||
|
||||
public func write(_ data: Data) {
|
||||
fileHandle.write(data)
|
||||
}
|
||||
|
||||
public func flush() {
|
||||
fileHandle.synchronizeFile()
|
||||
}
|
||||
|
||||
public func close() {
|
||||
fileHandle.closeFile()
|
||||
}
|
||||
}
|
||||
15
SideStore/Utils/common/OutputStream.swift
Normal file
15
SideStore/Utils/common/OutputStream.swift
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// OutputStream.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 28/12/24.
|
||||
// Copyright © 2024 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol OutputStream {
|
||||
func write(_ data: Data)
|
||||
func flush()
|
||||
func close()
|
||||
}
|
||||
73
SideStore/Utils/iostreams/ConsoleLog.swift
Normal file
73
SideStore/Utils/iostreams/ConsoleLog.swift
Normal file
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// ConsoleLog.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 25/11/24.
|
||||
// Copyright © 2024 SideStore. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class ConsoleLog {
|
||||
private static let CONSOLE_LOGS_DIRECTORY = "ConsoleLogs"
|
||||
private static let CONSOLE_LOG_NAME_PREFIX = "console"
|
||||
private static let CONSOLE_LOG_EXTN = ".log"
|
||||
|
||||
private lazy var consoleLogger: ConsoleLogger = {
|
||||
let logFileHandle = createLogFileHandle()
|
||||
let fileOutputStream = FileOutputStream(logFileHandle)
|
||||
|
||||
return UnBufferedConsoleLogger(stream: fileOutputStream)
|
||||
}()
|
||||
|
||||
private lazy var consoleLogsDir: URL = {
|
||||
// create a directory for console logs
|
||||
let docsDir = FileManager.default.documentsDirectory
|
||||
let consoleLogsDir = docsDir.appendingPathComponent(ConsoleLog.CONSOLE_LOGS_DIRECTORY)
|
||||
if !FileManager.default.fileExists(atPath: consoleLogsDir.path) {
|
||||
try! FileManager.default.createDirectory(at: consoleLogsDir, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
return consoleLogsDir
|
||||
}()
|
||||
|
||||
public lazy var logName: String = {
|
||||
logFileURL.lastPathComponent
|
||||
}()
|
||||
|
||||
public lazy var logFileURL: URL = {
|
||||
// get current timestamp
|
||||
let currentTime = Date()
|
||||
let dateTimeStamp = ConsoleLog.getDateInTimeStamp(date: currentTime)
|
||||
|
||||
// create a log file with the current timestamp
|
||||
let logName = "\(ConsoleLog.CONSOLE_LOG_NAME_PREFIX)-\(dateTimeStamp)\(ConsoleLog.CONSOLE_LOG_EXTN)"
|
||||
let logFileURL = consoleLogsDir.appendingPathComponent(logName)
|
||||
return logFileURL
|
||||
}()
|
||||
|
||||
|
||||
private func createLogFileHandle() -> FileHandle {
|
||||
if !FileManager.default.fileExists(atPath: logFileURL.path) {
|
||||
FileManager.default.createFile(atPath: logFileURL.path, contents: nil, attributes: nil)
|
||||
}
|
||||
|
||||
// return the file handle
|
||||
return try! FileHandle(forWritingTo: logFileURL)
|
||||
}
|
||||
|
||||
private static func getDateInTimeStamp(date: Date) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyyMMdd_HHmmss" // Format: 20241228_142345
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
|
||||
func startCapturing() {
|
||||
consoleLogger.startCapturing()
|
||||
}
|
||||
|
||||
func stopCapturing() {
|
||||
consoleLogger.stopCapturing()
|
||||
}
|
||||
}
|
||||
|
||||
166
SideStore/Utils/iostreams/ConsoleLogger.swift
Normal file
166
SideStore/Utils/iostreams/ConsoleLogger.swift
Normal file
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// ConsoleCapture.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 25/11/24.
|
||||
// Copyright © 2024 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol ConsoleLogger{
|
||||
func startCapturing()
|
||||
func stopCapturing()
|
||||
}
|
||||
|
||||
public class AbstractConsoleLogger<T: OutputStream>: ConsoleLogger{
|
||||
var outPipe: Pipe?
|
||||
var errPipe: Pipe?
|
||||
|
||||
var outputHandle: FileHandle?
|
||||
var errorHandle: FileHandle?
|
||||
|
||||
var originalStdout: Int32?
|
||||
var originalStderr: Int32?
|
||||
|
||||
let ostream: T
|
||||
|
||||
let writeQueue = DispatchQueue(label: "async-write-queue")
|
||||
|
||||
public init(stream: T) throws {
|
||||
// Since swift doesn't support compile time abstract classes Instantiation checking,
|
||||
// we are using runtime check to prevent direct instantiation :(
|
||||
if Self.self === AbstractConsoleLogger.self {
|
||||
throw AbstractClassError.abstractInitializerInvoked
|
||||
}
|
||||
|
||||
self.ostream = stream
|
||||
}
|
||||
|
||||
deinit {
|
||||
stopCapturing()
|
||||
}
|
||||
|
||||
public func startCapturing() { // made it public coz, let client ask for capturing
|
||||
|
||||
// if already initialized within current instance, bail out
|
||||
guard outPipe == nil, errPipe == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
// Create new pipes for stdout and stderr
|
||||
self.outPipe = Pipe()
|
||||
self.errPipe = Pipe()
|
||||
|
||||
outputHandle = self.outPipe?.fileHandleForReading
|
||||
errorHandle = self.errPipe?.fileHandleForReading
|
||||
|
||||
// Store original file descriptors
|
||||
originalStdout = dup(STDOUT_FILENO)
|
||||
originalStderr = dup(STDERR_FILENO)
|
||||
|
||||
// Redirect stdout and stderr to our pipes
|
||||
dup2(self.outPipe?.fileHandleForWriting.fileDescriptor ?? -1, STDOUT_FILENO)
|
||||
dup2(self.errPipe?.fileHandleForWriting.fileDescriptor ?? -1, STDERR_FILENO)
|
||||
|
||||
// Setup readability handlers for raw data
|
||||
setupReadabilityHandler(for: outputHandle, isError: false)
|
||||
setupReadabilityHandler(for: errorHandle, isError: true)
|
||||
}
|
||||
|
||||
private func setupReadabilityHandler(for handle: FileHandle?, isError: Bool) {
|
||||
handle?.readabilityHandler = { [weak self] handle in
|
||||
let data = handle.availableData
|
||||
if !data.isEmpty {
|
||||
self?.writeQueue.async {
|
||||
try? self?.writeData(data)
|
||||
}
|
||||
|
||||
// Forward to original std stream
|
||||
if let originalFD = isError ? self?.originalStderr : self?.originalStdout {
|
||||
data.withUnsafeBytes { (bufferPointer) -> Void in
|
||||
if let baseAddress = bufferPointer.baseAddress, bufferPointer.count > 0 {
|
||||
write(originalFD, baseAddress, bufferPointer.count)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeData(_ data: Data) throws {
|
||||
throw AbstractClassError.abstractMethodInvoked
|
||||
}
|
||||
|
||||
func stopCapturing() {
|
||||
ostream.close()
|
||||
|
||||
// Restore original stdout and stderr
|
||||
if let stdout = originalStdout {
|
||||
dup2(stdout, STDOUT_FILENO)
|
||||
close(stdout)
|
||||
}
|
||||
if let stderr = originalStderr {
|
||||
dup2(stderr, STDERR_FILENO)
|
||||
close(stderr)
|
||||
}
|
||||
|
||||
// Clean up
|
||||
outPipe?.fileHandleForReading.readabilityHandler = nil
|
||||
errPipe?.fileHandleForReading.readabilityHandler = nil
|
||||
outPipe = nil
|
||||
errPipe = nil
|
||||
outputHandle = nil
|
||||
errorHandle = nil
|
||||
originalStdout = nil
|
||||
originalStderr = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class UnBufferedConsoleLogger<T: OutputStream>: AbstractConsoleLogger<T> {
|
||||
|
||||
required override init(stream: T) {
|
||||
// cannot throw abstractInitializerInvoked, so need to override else client needs to handle it unnecessarily
|
||||
try! super.init(stream: stream)
|
||||
}
|
||||
|
||||
override func writeData(_ data: Data) throws {
|
||||
// directly write data to the stream without buffering
|
||||
ostream.write(data)
|
||||
}
|
||||
}
|
||||
|
||||
public class BufferedConsoleLogger<T: OutputStream>: AbstractConsoleLogger<T> {
|
||||
|
||||
// Buffer size (bytes) and storage
|
||||
private let maxBufferSize: Int
|
||||
private var bufferedData = Data()
|
||||
|
||||
required init(stream: T, bufferSize: Int = 1024) {
|
||||
self.maxBufferSize = bufferSize
|
||||
try! super.init(stream: stream)
|
||||
}
|
||||
|
||||
override func writeData(_ data: Data) throws {
|
||||
// Append data to buffer
|
||||
self.bufferedData.append(data)
|
||||
|
||||
// Check if the buffer is full and flush
|
||||
if self.bufferedData.count >= self.maxBufferSize {
|
||||
self.flushBuffer()
|
||||
}
|
||||
}
|
||||
|
||||
private func flushBuffer() {
|
||||
// Write all buffered data to the stream
|
||||
ostream.write(bufferedData)
|
||||
bufferedData.removeAll()
|
||||
}
|
||||
|
||||
override func stopCapturing() {
|
||||
// Flush buffer and close the file handles first
|
||||
flushBuffer()
|
||||
super.stopCapturing()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user