Skip to content

Instantly share code, notes, and snippets.

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)
// OSLogStream.swift
// LoggingTest
// Created by Peter Steinberger on 24.08.20.
// Requires importing 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 }
deinit {
if let stream = 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
Copy link

Very interesting! This works for you on a device as well?

Might be helpful for my project, regardless of "S"PI status. WebKit is full of those. 😂

Copy link

This works on macOS and iOS, Simulator and device. Test project is here:

I only tested this with iOS 14/Big Sur but in theory this should work all the way down to iOS 10.

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!

Copy link

Also don't forget to free(messageTextC);

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>";
	case 0x01:
		level=" <Info>";
	case 0x2:
		level=" <Debug>";
	case 0x10:
		level=" <Error>";
	case 0x11:
		level=" <Fault>";
		level=" <Unknown>";


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