Skip to content

Instantly share code, notes, and snippets.

@steipete
Last active November 7, 2023 22:25
Show Gist options
  • Save steipete/459a065f905a41f8f577fb02ef34206e to your computer and use it in GitHub Desktop.
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/
//
// 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
}
}
@LeoNatan
Copy link

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!

@LeoNatan
Copy link

Also don't forget to free(messageTextC);

@LeoNatan
Copy link

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;
}

From https://github.com/limneos/oslog/blob/master/main.mm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment