Last active
November 7, 2023 22:25
-
-
Save steipete/459a065f905a41f8f577fb02ef34206e to your computer and use it in GitHub Desktop.
Access streaming OSLogStore at runtime with SPI. (FB8519418) https://steipete.com/posts/logging-in-swift/
This file contains 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
// | |
// OSLogStream.swift | |
// LoggingTest | |
// | |
// Created by Peter Steinberger on 24.08.20. | |
// | |
// Requires importing https://github.com/apple/llvm-project/blob/apple/master/lldb/tools/debugserver/source/MacOSX/DarwinLog/ActivityStreamSPI.h via bridging header | |
import Foundation | |
class OSLogStream { | |
private var stream: os_activity_stream_t! | |
private let filterPid = ProcessInfo.processInfo.processIdentifier; | |
private let logHandler: (LogMessage) -> Void | |
private let OSActivityStreamForPID: os_activity_stream_for_pid_t | |
private let OSActivityStreamResume: os_activity_stream_resume_t | |
private let OSActivityStreamCancel: os_activity_stream_cancel_t | |
private let OSLogCopyFormattedMessage: os_log_copy_formatted_message_t | |
struct LogMessage { | |
let msg: String | |
let date: Date | |
} | |
init?(logHandler: @escaping (LogMessage) -> Void) { | |
self.logHandler = logHandler | |
guard let handle = dlopen("/System/Library/PrivateFrameworks/LoggingSupport.framework/LoggingSupport", RTLD_NOW) else { return nil } | |
OSActivityStreamForPID = unsafeBitCast(dlsym(handle, "os_activity_stream_for_pid"), to: os_activity_stream_for_pid_t.self) | |
OSActivityStreamResume = unsafeBitCast(dlsym(handle, "os_activity_stream_resume"), to: os_activity_stream_resume_t.self) | |
OSActivityStreamCancel = unsafeBitCast(dlsym(handle, "os_activity_stream_cancel"), to: os_activity_stream_cancel_t.self) | |
OSLogCopyFormattedMessage = unsafeBitCast(dlsym(handle, "os_log_copy_formatted_message"), to: os_log_copy_formatted_message_t.self) | |
let activity_stream_flags = os_activity_stream_flag_t(OS_ACTIVITY_STREAM_HISTORICAL | OS_ACTIVITY_STREAM_PROCESS_ONLY) // NSLog, ASL | |
stream = OSActivityStreamForPID(filterPid, activity_stream_flags, { entryPointer, error in | |
guard error == 0, let entry = entryPointer?.pointee else { return false } | |
return self.handleStreamEntry(entry) | |
}) | |
guard stream != nil else { return nil } | |
OSActivityStreamResume(stream) | |
} | |
deinit { | |
if let stream = stream { | |
OSActivityStreamCancel(stream) | |
} | |
} | |
private func handleStreamEntry(_ entry: os_activity_stream_entry_s) -> Bool { | |
guard entry.type == OS_ACTIVITY_STREAM_TYPE_LOG_MESSAGE || entry.type == OS_ACTIVITY_STREAM_TYPE_LEGACY_LOG_MESSAGE else { return true } | |
var osLogMessage = entry.log_message | |
guard let messageTextC = OSLogCopyFormattedMessage(&osLogMessage) else { return false } | |
let message = String(utf8String: messageTextC) | |
let date = Date(timeIntervalSince1970: TimeInterval(osLogMessage.tv_gmt.tv_sec)) | |
let logMessage = LogMessage(msg: message ?? "", date: date) | |
DispatchQueue.main.async { self.logHandler(logMessage) } | |
return true | |
} | |
} |
Also don't forget to free(messageTextC);
There is also os_log_get_type
which gives you the log level.
uint8_t logLevel=m_os_log_get_type(log_message);
const char * level=NULL;
switch (logLevel){
case 0x00:
level=" <Notice>";
break;
case 0x01:
level=" <Info>";
break;
case 0x2:
level=" <Debug>";
break;
case 0x10:
level=" <Error>";
break;
case 0x11:
break;
level=" <Fault>";
default:
level=" <Unknown>";
break;
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
One good thing about finding "S"PI in Apple's open sources is that this promises quite the long API stability with those. So that's a great find!