Last active
December 8, 2017 15:11
-
-
Save DevAndArtist/04dbb1c980b4484a64ecb5b1fce411d2 to your computer and use it in GitHub Desktop.
Logger (WIP)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Darwin | |
import UIKit | |
protocol _LoggerDelegate : AnyObject { | |
/// | |
func logger(_ logger: Logger, received newString: String) | |
} | |
final class Logger { | |
/// | |
private let pipe = Pipe() | |
/// | |
private var runLoopSource: CFRunLoopSource? | |
/// | |
private let fileDescriptor: FileDescriptor | |
/// | |
private var oldFileDescriptor: FileDescriptor? | |
/// | |
typealias Delegate = _LoggerDelegate | |
/// | |
weak var delegate: Delegate? | |
/// | |
var isLogRerouted = false { | |
didSet { if isLogRerouted != oldValue { reroute(isLogRerouted) } } | |
} | |
/// | |
var isLogForwarded = true | |
/// | |
init(source: Source) { | |
fileDescriptor = source.fileDescriptor | |
switch source { | |
case .standardOutput: | |
setbuf(stdout, nil) | |
case .standardError: | |
setbuf(stderr, nil) | |
default: | |
break | |
} | |
} | |
/// | |
deinit { isLogRerouted = false } | |
} | |
extension Logger { | |
/// | |
private func reroute(_ shouldReroute: Bool) { | |
if shouldReroute { | |
oldFileDescriptor = dup(fileDescriptor) | |
guard oldFileDescriptor != -1 else { return assertionFailure("Could not dup file descriptor.") } | |
guard dup2(pipe.fileHandleForWriting.fileDescriptor, fileDescriptor) != -1 else { | |
return assertionFailure("Could not dup2 file descriptor.") | |
} | |
monitor(true) | |
} else { | |
guard | |
let unwrappedOldFileDescriptor = oldFileDescriptor, | |
dup2(unwrappedOldFileDescriptor, fileDescriptor) != -1 | |
else { return assertionFailure("Could not dup2 back file descriptor.") } | |
monitor(false) | |
} | |
} | |
/// | |
private func monitor(_ shouldMonitor: Bool) { | |
if shouldMonitor { | |
var context = CFSocketContext(version: 0, | |
info: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()), | |
retain: nil, | |
release: nil, | |
copyDescription: nil) | |
/// | |
func receivedMessage(fromSocket socket: CFSocket?, | |
callbackType: CFSocketCallBackType, | |
cfData: CFData?, | |
dataPointer: UnsafeRawPointer?, | |
infoPointer: UnsafeMutableRawPointer?) { | |
guard let unwrappedDataPointer = dataPointer, let unwrappedInfoPointer = infoPointer else { return } | |
let realCFData = Unmanaged<CFData>.fromOpaque(unwrappedDataPointer).takeUnretainedValue() | |
let logger = Unmanaged<Logger>.fromOpaque(unwrappedInfoPointer).takeUnretainedValue() | |
let data = realCFData as NSData as Data | |
guard let string = String(data: data, encoding: .utf8) else { return } | |
logger.notifyDelegate(with: string) | |
guard | |
logger.isLogForwarded, | |
let oldFileDescriptor = logger.oldFileDescriptor, | |
data.withUnsafeBytes({ write(oldFileDescriptor, $0, data.count) }) != -1 | |
else { return NSLog("Couldn't write to old file descriptor.") } | |
} | |
let cfSocket = CFSocketCreateWithNative( | |
kCFAllocatorDefault, | |
pipe.fileHandleForReading.fileDescriptor, | |
CFSocketCallBackType.dataCallBack.rawValue, | |
receivedMessage(fromSocket:callbackType:cfData:dataPointer:infoPointer:), | |
&context | |
) | |
guard let unwrappedCFSocket = cfSocket else { return NSLog("Couldn't make cfsocket.") } | |
runLoopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, unwrappedCFSocket, 0) | |
guard let runLoopSource = runLoopSource else { | |
return NSLog("Couldn't create run loop source.") | |
} | |
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .defaultMode) | |
} else { | |
guard let runLoopSource = runLoopSource else { return } | |
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runLoopSource, .defaultMode) | |
self.runLoopSource = nil | |
} | |
} | |
/// | |
private func notifyDelegate(with string: String) { | |
delegate?.logger(self, received: string) | |
} | |
} | |
extension Logger { | |
/// | |
typealias FileDescriptor = Int32 | |
/// | |
enum Source { | |
case standardOutput | |
case standardError | |
case fileDescriptor(FileDescriptor) | |
/// | |
var fileDescriptor: FileDescriptor { | |
switch self { | |
case .standardOutput: | |
return STDOUT_FILENO | |
case .standardError: | |
return STDERR_FILENO | |
case .fileDescriptor(let fileDescriptor): | |
return fileDescriptor | |
} | |
} | |
/// | |
init(fileDescriptor: FileDescriptor) { | |
switch fileDescriptor { | |
case STDOUT_FILENO: | |
self = .standardOutput | |
case STDERR_FILENO: | |
self = .standardError | |
default: | |
self = .fileDescriptor(fileDescriptor) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment