Browse Source

Fix #140, system log messages work

more progress
Tanner Bennett 5 years ago
parent
commit
d7d40e6d27

+ 1 - 0
Classes/Editing/ArgumentInputViews/FLEXArgumentInputJSONObjectView.h

@@ -8,6 +8,7 @@
 
 #import "FLEXArgumentInputTextView.h"
 
+#warning TODO This is never supported
 @interface FLEXArgumentInputJSONObjectView : FLEXArgumentInputTextView
 
 @end

+ 13 - 16
Classes/Editing/ArgumentInputViews/FLEXArgumentInputViewFactory.m

@@ -39,26 +39,23 @@
 + (Class)argumentInputViewSubclassForTypeEncoding:(const char *)typeEncoding currentValue:(id)currentValue
 {
     Class argumentInputViewSubclass = nil;
+    NSArray<Class> *inputViewClasses = @[[FLEXArgumentInputColorView class],
+                                         [FLEXArgumentInputFontView class],
+                                         [FLEXArgumentInputStringView class],
+                                         [FLEXArgumentInputStructView class],
+                                         [FLEXArgumentInputSwitchView class],
+                                         [FLEXArgumentInputDateView class],
+                                         [FLEXArgumentInputNumberView class],
+                                         [FLEXArgumentInputJSONObjectView class]];
     
     // Note that order is important here since multiple subclasses may support the same type.
     // An example is the number subclass and the bool subclass for the type @encode(BOOL).
     // Both work, but we'd prefer to use the bool subclass.
-    if ([FLEXArgumentInputColorView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
-        argumentInputViewSubclass = [FLEXArgumentInputColorView class];
-    } else if ([FLEXArgumentInputFontView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
-        argumentInputViewSubclass = [FLEXArgumentInputFontView class];
-    } else if ([FLEXArgumentInputStringView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
-        argumentInputViewSubclass = [FLEXArgumentInputStringView class];
-    } else if ([FLEXArgumentInputStructView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
-        argumentInputViewSubclass = [FLEXArgumentInputStructView class];
-    } else if ([FLEXArgumentInputSwitchView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
-        argumentInputViewSubclass = [FLEXArgumentInputSwitchView class];
-    } else if ([FLEXArgumentInputDateView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
-        argumentInputViewSubclass = [FLEXArgumentInputDateView class];
-    } else if ([FLEXArgumentInputNumberView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
-        argumentInputViewSubclass = [FLEXArgumentInputNumberView class];
-    } else if ([FLEXArgumentInputJSONObjectView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
-        argumentInputViewSubclass = [FLEXArgumentInputJSONObjectView class];
+    for (Class inputView in inputViewClasses) {
+        if ([inputView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
+            argumentInputViewSubclass = inputView;
+            break;
+        }
     }
     
     return argumentInputViewSubclass;

+ 205 - 0
Classes/GlobalStateExplorers/SystemLog/ActivityStreamAPI.h

@@ -0,0 +1,205 @@
+//
+// Taken from https://github.com/llvm-mirror/lldb/blob/master/tools/debugserver/source/MacOSX/DarwinLog/ActivityStreamSPI.h
+// by Tanner Bennett on 03/03/2019 with minimal modifications.
+//
+
+//===-- ActivityStreamAPI.h -------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef ActivityStreamSPI_h
+#define ActivityStreamSPI_h
+
+#include <sys/time.h>
+// #include <xpc/xpc.h>
+
+/* By default, XPC objects are declared as Objective-C types when building with
+ * an Objective-C compiler. This allows them to participate in ARC, in RR
+ * management by the Blocks runtime and in leaks checking by the static
+ * analyzer, and enables them to be added to Cocoa collections.
+ *
+ * See <os/object.h> for details.
+ */
+#if OS_OBJECT_USE_OBJC
+OS_OBJECT_DECL(xpc_object);
+#else
+typedef void * xpc_object_t;
+#endif 
+
+#define OS_ACTIVITY_MAX_CALLSTACK 32
+
+// Enums
+
+typedef NS_ENUM(uint32_t, os_activity_stream_flag_t) {
+    OS_ACTIVITY_STREAM_PROCESS_ONLY = 0x00000001,
+    OS_ACTIVITY_STREAM_SKIP_DECODE = 0x00000002,
+    OS_ACTIVITY_STREAM_PAYLOAD = 0x00000004,
+    OS_ACTIVITY_STREAM_HISTORICAL = 0x00000008,
+    OS_ACTIVITY_STREAM_CALLSTACK = 0x00000010,
+    OS_ACTIVITY_STREAM_DEBUG = 0x00000020,
+    OS_ACTIVITY_STREAM_BUFFERED = 0x00000040,
+    OS_ACTIVITY_STREAM_NO_SENSITIVE = 0x00000080,
+    OS_ACTIVITY_STREAM_INFO = 0x00000100,
+    OS_ACTIVITY_STREAM_PROMISCUOUS = 0x00000200,
+    OS_ACTIVITY_STREAM_PRECISE_TIMESTAMPS = 0x00000200
+};
+
+typedef NS_ENUM(uint32_t, os_activity_stream_type_t) {
+    OS_ACTIVITY_STREAM_TYPE_ACTIVITY_CREATE = 0x0201,
+    OS_ACTIVITY_STREAM_TYPE_ACTIVITY_TRANSITION = 0x0202,
+    OS_ACTIVITY_STREAM_TYPE_ACTIVITY_USERACTION = 0x0203,
+    
+    OS_ACTIVITY_STREAM_TYPE_TRACE_MESSAGE = 0x0300,
+    
+    OS_ACTIVITY_STREAM_TYPE_LOG_MESSAGE = 0x0400,
+    OS_ACTIVITY_STREAM_TYPE_LEGACY_LOG_MESSAGE = 0x0480,
+    
+    OS_ACTIVITY_STREAM_TYPE_SIGNPOST_BEGIN = 0x0601,
+    OS_ACTIVITY_STREAM_TYPE_SIGNPOST_END = 0x0602,
+    OS_ACTIVITY_STREAM_TYPE_SIGNPOST_EVENT = 0x0603,
+    
+    OS_ACTIVITY_STREAM_TYPE_STATEDUMP_EVENT = 0x0A00,
+};
+
+typedef NS_ENUM(uint32_t, os_activity_stream_event_t) {
+    OS_ACTIVITY_STREAM_EVENT_STARTED = 1,
+    OS_ACTIVITY_STREAM_EVENT_STOPPED = 2,
+    OS_ACTIVITY_STREAM_EVENT_FAILED = 3,
+    OS_ACTIVITY_STREAM_EVENT_CHUNK_STARTED = 4,
+    OS_ACTIVITY_STREAM_EVENT_CHUNK_FINISHED = 5,
+};
+
+// Types
+
+typedef uint64_t os_activity_id_t;
+typedef struct os_activity_stream_s *os_activity_stream_t;
+typedef struct os_activity_stream_entry_s *os_activity_stream_entry_t;
+
+#define OS_ACTIVITY_STREAM_COMMON()                                            \
+uint64_t trace_id;                                                           \
+uint64_t timestamp;                                                          \
+uint64_t thread;                                                             \
+const uint8_t *image_uuid;                                                   \
+const char *image_path;                                                      \
+struct timeval tv_gmt;                                                       \
+struct timezone tz;                                                          \
+uint32_t offset
+
+typedef struct os_activity_stream_common_s {
+    OS_ACTIVITY_STREAM_COMMON();
+} * os_activity_stream_common_t;
+
+struct os_activity_create_s {
+    OS_ACTIVITY_STREAM_COMMON();
+    const char *name;
+    os_activity_id_t creator_aid;
+    uint64_t unique_pid;
+};
+
+struct os_activity_transition_s {
+    OS_ACTIVITY_STREAM_COMMON();
+    os_activity_id_t transition_id;
+};
+
+typedef struct os_log_message_s {
+    OS_ACTIVITY_STREAM_COMMON();
+    const char *format;
+    const uint8_t *buffer;
+    size_t buffer_sz;
+    const uint8_t *privdata;
+    size_t privdata_sz;
+    const char *subsystem;
+    const char *category;
+    uint32_t oversize_id;
+    uint8_t ttl;
+    bool persisted;
+} * os_log_message_t;
+
+typedef struct os_trace_message_v2_s {
+    OS_ACTIVITY_STREAM_COMMON();
+    const char *format;
+    const void *buffer;
+    size_t bufferLen;
+    xpc_object_t __unsafe_unretained payload;
+} * os_trace_message_v2_t;
+
+typedef struct os_activity_useraction_s {
+    OS_ACTIVITY_STREAM_COMMON();
+    const char *action;
+    bool persisted;
+} * os_activity_useraction_t;
+
+typedef struct os_signpost_s {
+    OS_ACTIVITY_STREAM_COMMON();
+    const char *format;
+    const uint8_t *buffer;
+    size_t buffer_sz;
+    const uint8_t *privdata;
+    size_t privdata_sz;
+    const char *subsystem;
+    const char *category;
+    uint64_t duration_nsec;
+    uint32_t callstack_depth;
+    uint64_t callstack[OS_ACTIVITY_MAX_CALLSTACK];
+} * os_signpost_t;
+
+typedef struct os_activity_statedump_s {
+    OS_ACTIVITY_STREAM_COMMON();
+    char *message;
+    size_t message_size;
+    char image_path_buffer[PATH_MAX];
+} * os_activity_statedump_t;
+
+struct os_activity_stream_entry_s {
+    os_activity_stream_type_t type;
+    
+    // information about the process streaming the data
+    pid_t pid;
+    uint64_t proc_id;
+    const uint8_t *proc_imageuuid;
+    const char *proc_imagepath;
+    
+    // the activity associated with this streamed event
+    os_activity_id_t activity_id;
+    os_activity_id_t parent_id;
+    
+    union {
+        struct os_activity_stream_common_s common;
+        struct os_activity_create_s activity_create;
+        struct os_activity_transition_s activity_transition;
+        struct os_log_message_s log_message;
+        struct os_trace_message_v2_s trace_message;
+        struct os_activity_useraction_s useraction;
+        struct os_signpost_s signpost;
+        struct os_activity_statedump_s statedump;
+    };
+};
+
+// Blocks
+
+typedef bool (^os_activity_stream_block_t)(os_activity_stream_entry_t entry,
+                                           int error);
+
+typedef void (^os_activity_stream_event_block_t)(
+                                                 os_activity_stream_t stream, os_activity_stream_event_t event);
+
+// SPI entry point prototypes
+
+typedef os_activity_stream_t (*os_activity_stream_for_pid_t)(
+                                                             pid_t pid, os_activity_stream_flag_t flags,
+                                                             os_activity_stream_block_t stream_block);
+
+typedef void (*os_activity_stream_resume_t)(os_activity_stream_t stream);
+
+typedef void (*os_activity_stream_cancel_t)(os_activity_stream_t stream);
+
+typedef char *(*os_log_copy_formatted_message_t)(os_log_message_t log_message);
+
+typedef void (*os_activity_stream_set_event_handler_t)(
+                                                       os_activity_stream_t stream, os_activity_stream_event_block_t block);
+
+#endif /* ActivityStreamSPI_h */

+ 18 - 0
Classes/GlobalStateExplorers/SystemLog/FLEXASLLogController.h

@@ -0,0 +1,18 @@
+//
+//  FLEXASLLogController.h
+//  FLEX
+//
+//  Created by Tanner on 3/14/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXLogController.h"
+
+@interface FLEXASLLogController : NSObject <FLEXLogController>
+
+/// Guaranteed to call back on the main thread.
++ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler;
+
+- (BOOL)startMonitoring;
+
+@end

+ 154 - 0
Classes/GlobalStateExplorers/SystemLog/FLEXASLLogController.m

@@ -0,0 +1,154 @@
+//
+//  FLEXASLLogController.m
+//  FLEX
+//
+//  Created by Tanner on 3/14/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXASLLogController.h"
+#import <asl.h>
+
+// Querrying the ASL is much slower in the simulator. We need a longer polling interval to keep things repsonsive.
+#if TARGET_IPHONE_SIMULATOR
+    #define updateInterval 5.0
+#else
+    #define updateInterval 1.0
+#endif
+
+@interface FLEXASLLogController ()
+
+@property (nonatomic, readonly) void (^updateHandler)(NSArray<FLEXSystemLogMessage *> *);
+
+@property (nonatomic, strong) NSTimer *logUpdateTimer;
+@property (nonatomic, readonly) NSMutableIndexSet *logMessageIdentifiers;
+
+// ASL stuff
+
+@property (nonatomic) NSUInteger heapSize;
+@property (nonatomic) dispatch_queue_t logQueue;
+@property (nonatomic) dispatch_io_t io;
+@property (nonatomic) NSString *remaining;
+@property (nonatomic) int stderror;
+@property (nonatomic) NSString *lastTimestamp;
+
+@end
+
+@implementation FLEXASLLogController
+
++ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler
+{
+    return [[self alloc] initWithUpdateHandler:newMessagesHandler];
+}
+
+- (id)initWithUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler
+{
+    NSParameterAssert(newMessagesHandler);
+
+    self = [super init];
+    if (self) {
+        _updateHandler = newMessagesHandler;
+        _logMessageIdentifiers = [NSMutableIndexSet indexSet];
+        self.logUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:updateInterval
+                                                               target:self
+                                                             selector:@selector(updateLogMessages)
+                                                             userInfo:nil
+                                                              repeats:YES];
+    }
+
+    return self;
+}
+
+- (void)dealloc
+{
+    [self.logUpdateTimer invalidate];
+}
+
+- (BOOL)startMonitoring {
+    [self.logUpdateTimer fire];
+    return YES;
+}
+
+- (void)updateLogMessages
+{
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+        NSArray<FLEXSystemLogMessage *> *newMessages;
+        @synchronized (self) {
+            newMessages = [self newLogMessagesForCurrentProcess];
+            if (!newMessages.count) {
+                return;
+            }
+
+            for (FLEXSystemLogMessage *message in newMessages) {
+                [self.logMessageIdentifiers addIndex:(NSUInteger)message.messageID];
+            }
+
+            self.lastTimestamp = @(asl_get(newMessages.lastObject.aslMessage, ASL_KEY_TIME));
+        }
+
+        dispatch_async(dispatch_get_main_queue(), ^{
+            self.updateHandler(newMessages);
+        });
+    });
+}
+
+#pragma mark - Log Message Fetching
+
+- (NSArray<FLEXSystemLogMessage *> *)newLogMessagesForCurrentProcess
+{
+    if (!self.logMessageIdentifiers.count) {
+        return [self allLogMessagesForCurrentProcess];
+    }
+
+    aslresponse response = [self ASLMessageListForCurrentProcess];
+    aslmsg aslMessage = NULL;
+
+    NSMutableArray<FLEXSystemLogMessage *> *newMessages = [NSMutableArray array];
+
+    while ((aslMessage = asl_next(response))) {
+        NSUInteger messageID = (NSUInteger)atoll(asl_get(aslMessage, ASL_KEY_MSG_ID));
+        if (![self.logMessageIdentifiers containsIndex:messageID]) {
+            [newMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
+        }
+    }
+
+    asl_release(response);
+    return newMessages;
+}
+
+- (aslresponse)ASLMessageListForCurrentProcess
+{
+    static NSString *pidString = nil;
+    if (!pidString) {
+        pidString = @([[NSProcessInfo processInfo] processIdentifier]).stringValue;
+    }
+
+    // Create system log query object.
+    asl_object_t query = asl_new(ASL_TYPE_QUERY);
+
+    // Filter for messages from the current process.
+    // Note that this appears to happen by default on device, but is required in the simulator.
+    asl_set_query(query, ASL_KEY_PID, pidString.UTF8String, ASL_QUERY_OP_EQUAL);
+    // Filter for messages after the last retreived message.
+    if (self.lastTimestamp) {
+        asl_set_query(query, ASL_KEY_TIME, self.lastTimestamp.UTF8String, ASL_QUERY_OP_GREATER);
+    }
+
+    return asl_search(NULL, query);
+}
+
+- (NSArray<FLEXSystemLogMessage *> *)allLogMessagesForCurrentProcess
+{
+    aslresponse response = [self ASLMessageListForCurrentProcess];
+    aslmsg aslMessage = NULL;
+
+    NSMutableArray<FLEXSystemLogMessage *> *logMessages = [NSMutableArray array];
+    while ((aslMessage = asl_next(response))) {
+        [logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
+    }
+    asl_release(response);
+
+    return logMessages;
+}
+
+@end

+ 19 - 0
Classes/GlobalStateExplorers/SystemLog/FLEXLogController.h

@@ -0,0 +1,19 @@
+//
+//  FLEXLogController.h
+//  FLEX
+//
+//  Created by Tanner on 3/17/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "FLEXSystemLogMessage.h"
+
+@protocol FLEXLogController <NSObject>
+
+/// Guaranteed to call back on the main thread.
++ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler;
+
+- (BOOL)startMonitoring;
+
+@end

+ 29 - 0
Classes/GlobalStateExplorers/SystemLog/FLEXOSLogController.h

@@ -0,0 +1,29 @@
+//
+//  FLEXOSLogController.h
+//  FLEX
+//
+//  Created by Tanner on 12/19/18.
+//  Copyright © 2018 Flipboard. All rights reserved.
+//
+
+#import "FLEXLogController.h"
+
+#define FLEXOSLogAvailable() ([NSProcessInfo processInfo].operatingSystemVersion.majorVersion >= 10)
+
+extern NSString * const kFLEXiOSPersistentOSLogKey;
+
+/// The log controller used for iOS 10 and up.
+@interface FLEXOSLogController : NSObject <FLEXLogController>
+
++ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler;
+
+- (BOOL)startMonitoring;
+
+/// Whether log messages are to be recorded and kept in-memory in the background.
+/// You do not need to initialize this value, only change it.
+@property (nonatomic) BOOL persistent;
+/// Used mostly internally, but also used by the log VC to persist messages
+/// that were created prior to enabling persistence.
+@property (nonatomic) NSMutableArray<FLEXSystemLogMessage *> *messages;
+
+@end

+ 218 - 0
Classes/GlobalStateExplorers/SystemLog/FLEXOSLogController.m

@@ -0,0 +1,218 @@
+//
+//  FLEXOSLogController.m
+//  FLEX
+//
+//  Created by Tanner on 12/19/18.
+//  Copyright © 2018 Flipboard. All rights reserved.
+//
+
+#import "FLEXOSLogController.h"
+#include <dlfcn.h>
+#include "ActivityStreamAPI.h"
+
+NSString * const kFLEXiOSPersistentOSLogKey = @"com.flex.enablePersistentOSLogLogging";
+
+static os_activity_stream_for_pid_t OSActivityStreamForPID;
+static os_activity_stream_resume_t OSActivityStreamResume;
+static os_activity_stream_cancel_t OSActivityStreamCancel;
+static os_log_copy_formatted_message_t OSLogCopyFormattedMessage;
+static os_activity_stream_set_event_handler_t OSActivityStreamSetEventHandler;
+static int (*proc_name)(int, char *, unsigned int);
+static int (*proc_listpids)(uint32_t, uint32_t, void*, int);
+static uint8_t (*OSLogGetType)(void *);
+
+@interface FLEXOSLogController ()
+
++ (FLEXOSLogController *)sharedLogController;
+
+@property (nonatomic) void (^updateHandler)(NSArray<FLEXSystemLogMessage *> *);
+
+@property (nonatomic) BOOL canPrint;
+@property (nonatomic) int filterPid;
+@property (nonatomic) BOOL levelInfo;
+@property (nonatomic) BOOL subsystemInfo;
+
+@property (nonatomic) os_activity_stream_t stream;
+
+@end
+
+@implementation FLEXOSLogController
+
++ (void)load
+{
+    // Persist logs when the app launches on iOS 10 if we have persitent logs turned on
+    if (FLEXOSLogAvailable()) {
+        BOOL persistent = [[NSUserDefaults standardUserDefaults] boolForKey:kFLEXiOSPersistentOSLogKey];
+        if (persistent) {
+            [self sharedLogController].persistent = YES;
+            [[self sharedLogController] startMonitoring];
+        }
+    }
+}
+
++ (instancetype)sharedLogController {
+    static FLEXOSLogController *shared = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        shared = [self new];
+    });
+    
+    return shared;
+}
+
++ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler
+{
+    FLEXOSLogController *shared = [self sharedLogController];
+    shared.updateHandler = newMessagesHandler;
+    return shared;
+}
+
+- (id)init
+{
+    NSAssert(FLEXOSLogAvailable(), @"os_log is only available on iOS 10 and up");
+
+    self = [super init];
+    if (self) {
+        _filterPid = [NSProcessInfo processInfo].processIdentifier;
+        _levelInfo = NO;
+        _subsystemInfo = NO;
+    }
+    
+    return self;
+}
+
+- (void)dealloc {
+    OSActivityStreamCancel(self.stream);
+    _stream = nil;
+}
+
+- (void)setPersistent:(BOOL)persistent {
+    if (_persistent == persistent) return;
+    
+    _persistent = persistent;
+    self.messages = persistent ? [NSMutableArray array] : nil;
+}
+
+- (BOOL)startMonitoring {
+    if (![self lookupSPICalls]) {
+        // >= iOS 10 is required
+        return NO;
+    }
+    
+    // Are we already monitoring?
+    if (self.stream) {
+        // Should we send out the "persisted" messages?
+        if (self.updateHandler && self.messages.count) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                self.updateHandler(self.messages);
+            });
+        }
+        
+        return YES;
+    }
+
+    // Stream entry handler
+    os_activity_stream_block_t block = ^bool(os_activity_stream_entry_t entry, int error) {
+        return [self handleStreamEntry:entry error:error];
+    };
+
+    // Controls which types of messages we see
+    // 'Historical' appears to just show NSLog stuff
+    uint32_t activity_stream_flags = OS_ACTIVITY_STREAM_HISTORICAL;
+    activity_stream_flags |= OS_ACTIVITY_STREAM_PROCESS_ONLY;
+//    activity_stream_flags |= OS_ACTIVITY_STREAM_PROCESS_ONLY;
+
+    self.stream = OSActivityStreamForPID(self.filterPid, activity_stream_flags, block);
+
+    // Specify the stream-related event handler
+    OSActivityStreamSetEventHandler(self.stream, [self streamEventHandlerBlock]);
+    // Start the stream
+    OSActivityStreamResume(self.stream);
+
+    return YES;
+}
+
+- (BOOL)lookupSPICalls {
+    static BOOL hasSPI = NO;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        void *handle = dlopen("/System/Library/PrivateFrameworks/LoggingSupport.framework/LoggingSupport", RTLD_NOW);
+
+        OSActivityStreamForPID = (os_activity_stream_for_pid_t)dlsym(handle, "os_activity_stream_for_pid");
+        OSActivityStreamResume = (os_activity_stream_resume_t)dlsym(handle, "os_activity_stream_resume");
+        OSActivityStreamCancel = (os_activity_stream_cancel_t)dlsym(handle, "os_activity_stream_cancel");
+        OSLogCopyFormattedMessage = (os_log_copy_formatted_message_t)dlsym(handle, "os_log_copy_formatted_message");
+        OSActivityStreamSetEventHandler = (os_activity_stream_set_event_handler_t)dlsym(handle, "os_activity_stream_set_event_handler");
+        proc_name = (int(*)(int, char *, unsigned int))dlsym(handle, "proc_name");
+        proc_listpids = (int(*)(uint32_t, uint32_t, void*, int))dlsym(handle, "proc_listpids");
+        OSLogGetType = (uint8_t(*)(void *))dlsym(handle, "os_log_get_type");
+
+        hasSPI = (OSActivityStreamForPID != NULL) &&
+                (OSActivityStreamResume != NULL) &&
+                (OSActivityStreamCancel != NULL) &&
+                (OSLogCopyFormattedMessage != NULL) &&
+                (OSActivityStreamSetEventHandler != NULL) &&
+                (OSLogGetType != NULL) &&
+                (proc_name != NULL);
+    });
+    
+    return hasSPI;
+}
+
+- (BOOL)handleStreamEntry:(os_activity_stream_entry_t)entry error:(int)error {
+    if (!self.canPrint || (self.filterPid != -1 && entry->pid != self.filterPid)) {
+        return YES;
+    }
+
+    if (!error && entry) {
+        if (entry->type == OS_ACTIVITY_STREAM_TYPE_LOG_MESSAGE ||
+            entry->type == OS_ACTIVITY_STREAM_TYPE_LEGACY_LOG_MESSAGE) {
+            os_log_message_t log_message = &entry->log_message;
+            
+            // Get date
+            NSDate *date = [NSDate dateWithTimeIntervalSince1970:log_message->tv_gmt.tv_sec];
+            
+            // Get log message text
+            const char *messageText = OSLogCopyFormattedMessage(log_message);
+            // https://github.com/limneos/oslog/issues/1
+            if (entry->log_message.format && !(strcmp(entry->log_message.format, messageText))) {
+                messageText = (char *)entry->log_message.format;
+            }
+
+            dispatch_async(dispatch_get_main_queue(), ^{
+                FLEXSystemLogMessage *message = [FLEXSystemLogMessage logMessageFromDate:date text:@(messageText)];
+                if (self.persistent) {
+                    [self.messages addObject:message];
+                }
+                if (self.updateHandler) {
+                    self.updateHandler(@[message]);
+                }
+            });
+        }
+    }
+    
+    return YES;
+}
+
+- (os_activity_stream_event_block_t)streamEventHandlerBlock {
+    return [^void(os_activity_stream_t stream, os_activity_stream_event_t event) {
+        switch (event) {
+            case OS_ACTIVITY_STREAM_EVENT_STARTED:
+                self.canPrint = YES;
+                break;
+            case OS_ACTIVITY_STREAM_EVENT_STOPPED:
+                break;
+            case OS_ACTIVITY_STREAM_EVENT_FAILED:
+                break;
+            case OS_ACTIVITY_STREAM_EVENT_CHUNK_STARTED:
+                break;
+            case OS_ACTIVITY_STREAM_EVENT_CHUNK_FINISHED:
+                break;
+            default:
+                printf("=== Unhandled case ===\n");
+                break;
+        }
+    } copy];
+}
+
+@end

+ 14 - 4
Classes/GlobalStateExplorers/SystemLog/FLEXSystemLogMessage.h

@@ -8,14 +8,24 @@
 
 #import <Foundation/Foundation.h>
 #import <asl.h>
+#import "ActivityStreamAPI.h"
+
+NS_ASSUME_NONNULL_BEGIN
 
 @interface FLEXSystemLogMessage : NSObject
 
 + (instancetype)logMessageFromASLMessage:(aslmsg)aslMessage;
+//+ (instancetype)logMessageFromOSLog:(os_log_message_t)logMessage;
++ (instancetype)logMessageFromDate:(NSDate *)date text:(NSString *)text;
+
+// ASL specific properties
+@property (nonatomic, readonly, nullable) NSString *sender;
+@property (nonatomic, readonly, nullable) aslmsg aslMessage;
 
-@property (nonatomic, strong) NSDate *date;
-@property (nonatomic, copy) NSString *sender;
-@property (nonatomic, copy) NSString *messageText;
-@property (nonatomic, assign) long long messageID;
+@property (nonatomic, readonly) NSDate *date;
+@property (nonatomic, readonly) NSString *messageText;
+@property (nonatomic, readonly) long long messageID;
 
 @end
+
+NS_ASSUME_NONNULL_END

+ 54 - 9
Classes/GlobalStateExplorers/SystemLog/FLEXSystemLogMessage.m

@@ -12,7 +12,9 @@
 
 + (instancetype)logMessageFromASLMessage:(aslmsg)aslMessage
 {
-    FLEXSystemLogMessage *logMessage = [[FLEXSystemLogMessage alloc] init];
+    NSDate *date = nil;
+    NSString *sender = nil, *text = nil;
+    long long identifier = 0;
 
     const char *timestamp = asl_get(aslMessage, ASL_KEY_TIME);
     if (timestamp) {
@@ -21,30 +23,67 @@
         if (nanoseconds) {
             timeInterval += [@(nanoseconds) doubleValue] / NSEC_PER_SEC;
         }
-        logMessage.date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
+        date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
     }
 
-    const char *sender = asl_get(aslMessage, ASL_KEY_SENDER);
-    if (sender) {
-        logMessage.sender = @(sender);
+    const char *s = asl_get(aslMessage, ASL_KEY_SENDER);
+    if (s) {
+        sender = @(s);
     }
 
     const char *messageText = asl_get(aslMessage, ASL_KEY_MSG);
     if (messageText) {
-        logMessage.messageText = @(messageText);
+        text = @(messageText);
     }
 
     const char *messageID = asl_get(aslMessage, ASL_KEY_MSG_ID);
     if (messageID) {
-        logMessage.messageID = [@(messageID) longLongValue];
+        identifier = [@(messageID) longLongValue];
     }
 
-    return logMessage;
+    FLEXSystemLogMessage *message = [[self alloc] initWithDate:date sender:sender text:text messageID:identifier];
+    message->_aslMessage = aslMessage;
+    return message;
+}
+
++ (instancetype)logMessageFromOSLog:(os_log_message_t)logMessage
+{
+    abort();
+    return nil;
+}
+
++ (instancetype)logMessageFromDate:(NSDate *)date text:(NSString *)text
+{
+    return [[self alloc] initWithDate:date sender:nil text:text messageID:0];
+}
+
+- (id)initWithDate:(NSDate *)date sender:(NSString *)sender text:(NSString *)text messageID:(long long)identifier
+{
+    self = [super init];
+    if (self) {
+        _date = date;
+        _sender = sender;
+        _messageText = text;
+        _messageID = identifier;
+    }
+
+    return self;
 }
 
 - (BOOL)isEqual:(id)object
 {
-    return [object isKindOfClass:[FLEXSystemLogMessage class]] && self.messageID == [object messageID];
+    if ([object isKindOfClass:[self class]]) {
+        if (self.messageID) {
+            // Only ASL uses messageID, otherwise it is 0
+            return self.messageID == [object messageID];
+        } else {
+            // Test message texts and dates for OS Log
+            return [self.messageText isEqual:[object messageText]] &&
+                    [self.date isEqualToDate:[object date]];
+        }
+    }
+    
+    return NO;
 }
 
 - (NSUInteger)hash
@@ -52,4 +91,10 @@
     return (NSUInteger)self.messageID;
 }
 
+- (NSString *)description
+{
+    NSString *escaped = [self.messageText stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
+    return [NSString stringWithFormat:@"(%lu) %@", self.messageText.length, escaped];
+}
+
 @end

+ 61 - 109
Classes/GlobalStateExplorers/SystemLog/FLEXSystemLogTableViewController.m

@@ -8,17 +8,16 @@
 
 #import "FLEXSystemLogTableViewController.h"
 #import "FLEXUtility.h"
-#import "FLEXSystemLogMessage.h"
+#import "FLEXASLLogController.h"
+#import "FLEXOSLogController.h"
 #import "FLEXSystemLogTableViewCell.h"
-#import <asl.h>
 
 @interface FLEXSystemLogTableViewController () <UISearchResultsUpdating, UISearchControllerDelegate>
 
 @property (nonatomic, strong) UISearchController *searchController;
+@property (nonatomic, readonly) id<FLEXLogController> logController;
 @property (nonatomic, readonly) NSMutableArray<FLEXSystemLogMessage *> *logMessages;
 @property (nonatomic, copy) NSArray<FLEXSystemLogMessage *> *filteredLogMessages;
-@property (nonatomic, strong) NSTimer *logUpdateTimer;
-@property (nonatomic, readonly) NSMutableIndexSet *logMessageIdentifiers;
 
 @end
 
@@ -28,68 +27,56 @@
 {
     [super viewDidLoad];
 
+    id logHandler = ^(NSArray<FLEXSystemLogMessage *> *newMessages) {
+        self.title = @"System Log";
+
+        [self.logMessages addObjectsFromArray:newMessages];
+
+        // "Follow" the log as new messages stream in if we were previously near the bottom.
+        BOOL wasNearBottom = self.tableView.contentOffset.y >= self.tableView.contentSize.height - self.tableView.frame.size.height - 100.0;
+        [self.tableView reloadData];
+        if (wasNearBottom) {
+            [self scrollToLastRow];
+        }
+    };
+
     _logMessages = [NSMutableArray array];
-    _logMessageIdentifiers = [NSMutableIndexSet indexSet];
+    if ([NSProcessInfo processInfo].operatingSystemVersion.majorVersion <= 9) {
+        _logController = [FLEXASLLogController withUpdateHandler:logHandler];
+    } else {
+        _logController = [FLEXOSLogController withUpdateHandler:logHandler];
+    }
 
     [self.tableView registerClass:[FLEXSystemLogTableViewCell class] forCellReuseIdentifier:kFLEXSystemLogTableViewCellIdentifier];
     self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
     self.title = @"Loading...";
-    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@" ⬇︎ " style:UIBarButtonItemStylePlain target:self action:@selector(scrollToLastRow)];
-
+    
+    UIBarButtonItem *scrollDown = [[UIBarButtonItem alloc] initWithTitle:@" ⬇︎ "
+                                                                   style:UIBarButtonItemStylePlain
+                                                                  target:self
+                                                                  action:@selector(scrollToLastRow)];
+    UIBarButtonItem *settings = [[UIBarButtonItem alloc] initWithTitle:@"Settings"
+                                                                 style:UIBarButtonItemStylePlain
+                                                                target:self
+                                                                action:@selector(showLogSettings)];
+    if (FLEXOSLogAvailable()) {
+        self.navigationItem.rightBarButtonItems = @[scrollDown, settings];
+    } else {
+        self.navigationItem.rightBarButtonItem = scrollDown;
+    }
+    
     self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
     self.searchController.delegate = self;
     self.searchController.searchResultsUpdater = self;
     self.searchController.dimsBackgroundDuringPresentation = NO;
     self.tableView.tableHeaderView = self.searchController.searchBar;
-
-    [self updateLogMessages];
 }
 
 - (void)viewWillAppear:(BOOL)animated
 {
     [super viewWillAppear:animated];
 
-    NSTimeInterval updateInterval = 1.0;
-
-#if TARGET_IPHONE_SIMULATOR
-    // Querrying the ASL is much slower in the simulator. We need a longer polling interval to keep things repsonsive.
-    updateInterval = 5.0;
-#endif
-
-    self.logUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:updateInterval target:self selector:@selector(updateLogMessages) userInfo:nil repeats:YES];
-}
-
-- (void)viewWillDisappear:(BOOL)animated
-{
-    [super viewWillDisappear:animated];
-
-    [self.logUpdateTimer invalidate];
-}
-
-- (void)updateLogMessages
-{
-    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-        NSArray<FLEXSystemLogMessage *> *newMessages = [self newLogMessagesForCurrentProcess];
-        if (!newMessages.count) {
-            return;
-        }
-
-        dispatch_async(dispatch_get_main_queue(), ^{
-            self.title = @"System Log";
-
-            [self.logMessages addObjectsFromArray:newMessages];
-            for (FLEXSystemLogMessage *message in newMessages) {
-                [self.logMessageIdentifiers addIndex:(NSUInteger)message.messageID];
-            }
-
-            // "Follow" the log as new messages stream in if we were previously near the bottom.
-            BOOL wasNearBottom = self.tableView.contentOffset.y >= self.tableView.contentSize.height - self.tableView.frame.size.height - 100.0;
-            [self.tableView reloadData];
-            if (wasNearBottom) {
-                [self scrollToLastRow];
-            }
-        });
-    });
+    [self.logController startMonitoring];
 }
 
 - (void)scrollToLastRow
@@ -101,6 +88,27 @@
     }
 }
 
+- (void)showLogSettings
+{
+    FLEXOSLogController *logController = (FLEXOSLogController *)self.logController;
+    BOOL persistent = [[NSUserDefaults standardUserDefaults] boolForKey:kFLEXiOSPersistentOSLogKey];
+    NSString *toggle = persistent ? @"Disable" : @"Enable";
+    NSString *title = [@"Persistent logging: " stringByAppendingString:persistent ? @"ON" : @"OFF"];
+    NSString *body = @"In iOS 10 and up, ASL is gone. The OS Log API is much more limited. "
+    "To get as close to the old behavior as possible, logs must be collected manually at launch and stored.\n\n"
+    "Turn this feature on only when you need it.";
+    
+    UIAlertController *settings = [UIAlertController alertControllerWithTitle:title message:body preferredStyle:UIAlertControllerStyleAlert];
+    [settings addAction:[UIAlertAction actionWithTitle:toggle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
+        [[NSUserDefaults standardUserDefaults] setBool:!persistent forKey:kFLEXiOSPersistentOSLogKey];
+        logController.persistent = !persistent;
+        [logController.messages addObjectsFromArray:self.logMessages];
+    }]];
+    [settings addAction:[UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleCancel handler:nil]];
+    
+    [self presentViewController:settings animated:YES completion:nil];
+}
+
 #pragma mark - Table view data source
 
 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
@@ -110,7 +118,7 @@
 
 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
 {
-    return self.searchController.isActive ? [self.filteredLogMessages count] : [self.logMessages count];
+    return self.searchController.isActive ? self.filteredLogMessages.count : self.logMessages.count;
 }
 
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
@@ -149,9 +157,8 @@
 - (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
 {
     if (action == @selector(copy:)) {
-        FLEXSystemLogMessage *logMessage = [self logMessageAtIndexPath:indexPath];
-        NSString *stringToCopy = [FLEXSystemLogTableViewCell displayedTextForLogMessage:logMessage] ?: @"";
-        [[UIPasteboard generalPasteboard] setString:stringToCopy];
+        // We usually only want to copy the log message itself, not any metadata associated with it.
+        [UIPasteboard generalPasteboard].string = [self logMessageAtIndexPath:indexPath].messageText;
     }
 }
 
@@ -179,59 +186,4 @@
     });
 }
 
-#pragma mark - Log Message Fetching
-
-- (NSArray<FLEXSystemLogMessage *> *)newLogMessagesForCurrentProcess
-{
-    if (!self.logMessages.count) {
-        return [[self class] allLogMessagesForCurrentProcess];
-    }
-
-    aslresponse response = [FLEXSystemLogTableViewController ASLMessageListForCurrentProcess];
-    aslmsg aslMessage = NULL;
-
-    NSMutableArray<FLEXSystemLogMessage *> *newMessages = [NSMutableArray array];
-
-    while ((aslMessage = asl_next(response))) {
-        NSUInteger messageID = (NSUInteger)atoll(asl_get(aslMessage, ASL_KEY_MSG_ID));
-        if (![self.logMessageIdentifiers containsIndex:messageID]) {
-            [newMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
-        }
-    }
-
-    asl_release(response);
-    return newMessages;
-}
-
-+ (aslresponse)ASLMessageListForCurrentProcess
-{
-    static NSString *pidString = nil;
-    if (!pidString) {
-        pidString = @([[NSProcessInfo processInfo] processIdentifier]).stringValue;
-    }
-
-    // Create system log query object.
-    asl_object_t query = asl_new(ASL_TYPE_QUERY);
-
-    // Filter for messages from the current process.
-    // Note that this appears to happen by default on device, but is required in the simulator.
-    asl_set_query(query, ASL_KEY_PID, pidString.UTF8String, ASL_QUERY_OP_EQUAL);
-
-    return asl_search(NULL, query);
-}
-
-+ (NSArray<FLEXSystemLogMessage *> *)allLogMessagesForCurrentProcess
-{
-    aslresponse response = [self ASLMessageListForCurrentProcess];
-    aslmsg aslMessage = NULL;
-
-    NSMutableArray<FLEXSystemLogMessage *> *logMessages = [NSMutableArray array];
-    while ((aslMessage = asl_next(response))) {
-        [logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
-    }
-    asl_release(response);
-
-    return logMessages;
-}
-
 @end

+ 276 - 0
Classes/GlobalStateExplorers/SystemLog/LLVM_LICENSE.TXT

@@ -0,0 +1,276 @@
+==============================================================================
+The LLVM Project is under the Apache License v2.0 with LLVM Exceptions:
+==============================================================================
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+    1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+    2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+    3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+    4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+    5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+    6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+    7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+    8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+    9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+    END OF TERMS AND CONDITIONS
+
+    APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+    Copyright [yyyy] [name of copyright owner]
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+
+---- LLVM Exceptions to the Apache 2.0 License ----
+
+As an exception, if, as a result of your compiling your source code, portions
+of this Software are embedded into an Object form of such source code, you
+may redistribute such embedded portions in such Object form without complying
+with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
+
+In addition, if you combine or link compiled forms of this Software with
+software that is licensed under the GPLv2 ("Combined Software") and if a
+court of competent jurisdiction determines that the patent provision (Section
+3), the indemnity provision (Section 9) or other Section of the License
+conflicts with the conditions of the GPLv2, you may retroactively and
+prospectively choose to deem waived or otherwise exclude such Section(s) of
+the License, but only in their entirety and only with respect to the Combined
+Software.
+
+==============================================================================
+Software from third parties included in the LLVM Project:
+==============================================================================
+The LLVM Project contains third party software which is under different license
+terms. All such code will be identified clearly using at least one of two
+mechanisms:
+1) It will be in a separate directory tree with its own `LICENSE.txt` or
+   `LICENSE` file at the top containing the specific license and restrictions
+   which apply to that software, or
+2) It will contain specific license and restriction terms at the top of every
+   file.
+
+==============================================================================
+Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy):
+==============================================================================
+University of Illinois/NCSA
+Open Source License
+
+Copyright (c) 2010 Apple Inc.
+All rights reserved.
+
+Developed by:
+
+    LLDB Team
+
+    http://lldb.llvm.org/
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal with
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimers.
+
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimers in the
+      documentation and/or other materials provided with the distribution.
+
+    * Neither the names of the LLDB Team, copyright holders, nor the names of 
+      its contributors may be used to endorse or promote products derived from 
+      this Software without specific prior written permission.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
+SOFTWARE.
+

+ 7 - 0
Example/UICatalog.xcodeproj/project.pbxproj

@@ -334,6 +334,11 @@
 			attributes = {
 				LastUpgradeCheck = 1000;
 				ORGANIZATIONNAME = f;
+				TargetAttributes = {
+					5356823918F3656900BAAD62 = {
+						DevelopmentTeam = S6N2F22V2Z;
+					};
+				};
 			};
 			buildConfigurationList = 5356823518F3656900BAAD62 /* Build configuration list for PBXProject "UICatalog" */;
 			compatibilityVersion = "Xcode 3.2";
@@ -540,6 +545,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+				DEVELOPMENT_TEAM = S6N2F22V2Z;
 				EXCLUDED_SOURCE_FILE_NAMES = "";
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
@@ -560,6 +566,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+				DEVELOPMENT_TEAM = S6N2F22V2Z;
 				EXCLUDED_SOURCE_FILE_NAMES = "FLEX*";
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",

+ 24 - 1
FLEX.xcodeproj/project.pbxproj

@@ -166,6 +166,10 @@
 		94AAF0381BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 94AAF0361BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		94AAF0391BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */; };
 		94AAF03A1BAF2F0300DE8760 /* FLEXKeyboardShortcutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 942DCD821BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		C309B82F223ED64400B228EC /* FLEXLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C309B82D223ED64400B228EC /* FLEXLogController.h */; };
+		C33E46AF223B02CD004BD0E6 /* FLEXASLLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C33E46AD223B02CD004BD0E6 /* FLEXASLLogController.h */; };
+		C33E46B0223B02CD004BD0E6 /* FLEXASLLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = C33E46AE223B02CD004BD0E6 /* FLEXASLLogController.m */; };
+		C34EE30821CB23CC00BD3A7C /* FLEXOSLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C34EE30621CB23CC00BD3A7C /* FLEXOSLogController.h */; };
 		C37A0C93218BAC9600848CA7 /* FLEXObjcInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */; };
 		C37A0C94218BAC9600848CA7 /* FLEXObjcInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */; };
 		C395D6D921789BD800BEAD4D /* FLEXColorExplorerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C395D6D721789BD800BEAD4D /* FLEXColorExplorerViewController.h */; };
@@ -174,6 +178,7 @@
 		C3DA55FF21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DA55FD21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m */; };
 		C3DB9F642107FC9600B46809 /* FLEXObjectRef.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */; };
 		C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */; };
+		C3DC287C223ED5F200F48AA6 /* FLEXOSLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = C34EE30721CB23CC00BD3A7C /* FLEXOSLogController.m */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -350,6 +355,12 @@
 		94A515241C4CA2080063292F /* FLEXToolbarItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FLEXToolbarItem.m; path = Classes/Toolbar/FLEXToolbarItem.m; sourceTree = SOURCE_ROOT; };
 		94AAF0361BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXKeyboardHelpViewController.h; sourceTree = "<group>"; };
 		94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXKeyboardHelpViewController.m; sourceTree = "<group>"; };
+		C309B82D223ED64400B228EC /* FLEXLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXLogController.h; sourceTree = "<group>"; };
+		C33E46AD223B02CD004BD0E6 /* FLEXASLLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXASLLogController.h; sourceTree = "<group>"; };
+		C33E46AE223B02CD004BD0E6 /* FLEXASLLogController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXASLLogController.m; sourceTree = "<group>"; };
+		C34EE30621CB23CC00BD3A7C /* FLEXOSLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXOSLogController.h; sourceTree = "<group>"; };
+		C34EE30721CB23CC00BD3A7C /* FLEXOSLogController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXOSLogController.m; sourceTree = "<group>"; };
+		C34EE30A21CB249E00BD3A7C /* ActivityStreamAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ActivityStreamAPI.h; sourceTree = "<group>"; };
 		C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXObjcInternal.h; sourceTree = "<group>"; };
 		C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FLEXObjcInternal.mm; sourceTree = "<group>"; };
 		C395D6D721789BD800BEAD4D /* FLEXColorExplorerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXColorExplorerViewController.h; sourceTree = "<group>"; };
@@ -576,6 +587,7 @@
 			isa = PBXGroup;
 			children = (
 				779B1EBF1C0C4D7C001F5E49 /* DatabaseBrowser */,
+				3A4C94AD1B5B21410088C3F2 /* SystemLog */,
 				3A4C949B1B5B21410088C3F2 /* FLEXClassesTableViewController.h */,
 				3A4C949C1B5B21410088C3F2 /* FLEXClassesTableViewController.m */,
 				3A4C949D1B5B21410088C3F2 /* FLEXFileBrowserFileOperationController.h */,
@@ -598,7 +610,6 @@
 				679F64851BD53B7B00A8C94C /* FLEXCookiesTableViewController.m */,
 				3A4C94AB1B5B21410088C3F2 /* FLEXWebViewController.h */,
 				3A4C94AC1B5B21410088C3F2 /* FLEXWebViewController.m */,
-				3A4C94AD1B5B21410088C3F2 /* SystemLog */,
 			);
 			path = GlobalStateExplorers;
 			sourceTree = "<group>";
@@ -612,6 +623,12 @@
 				3A4C94B11B5B21410088C3F2 /* FLEXSystemLogTableViewCell.m */,
 				3A4C94B21B5B21410088C3F2 /* FLEXSystemLogTableViewController.h */,
 				3A4C94B31B5B21410088C3F2 /* FLEXSystemLogTableViewController.m */,
+				C309B82D223ED64400B228EC /* FLEXLogController.h */,
+				C33E46AD223B02CD004BD0E6 /* FLEXASLLogController.h */,
+				C33E46AE223B02CD004BD0E6 /* FLEXASLLogController.m */,
+				C34EE30621CB23CC00BD3A7C /* FLEXOSLogController.h */,
+				C34EE30721CB23CC00BD3A7C /* FLEXOSLogController.m */,
+				C34EE30A21CB249E00BD3A7C /* ActivityStreamAPI.h */,
 			);
 			path = SystemLog;
 			sourceTree = "<group>";
@@ -748,6 +765,8 @@
 				779B1ED61C0C4D7C001F5E49 /* FLEXTableContentViewController.h in Headers */,
 				3A4C94C91B5B21410088C3F2 /* FLEXDefaultsExplorerViewController.h in Headers */,
 				3A4C95221B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h in Headers */,
+				C33E46AF223B02CD004BD0E6 /* FLEXASLLogController.h in Headers */,
+				C34EE30821CB23CC00BD3A7C /* FLEXOSLogController.h in Headers */,
 				3A4C94FF1B5B21410088C3F2 /* FLEXArgumentInputSwitchView.h in Headers */,
 				3A4C94E71B5B21410088C3F2 /* FLEXHierarchyTableViewCell.h in Headers */,
 				224D49AA1C673AB5000EAB86 /* FLEXSQLiteDatabaseManager.h in Headers */,
@@ -756,6 +775,7 @@
 				3A4C94C51B5B21410088C3F2 /* FLEXArrayExplorerViewController.h in Headers */,
 				3A4C94CB1B5B21410088C3F2 /* FLEXDictionaryExplorerViewController.h in Headers */,
 				3A4C95071B5B21410088C3F2 /* FLEXDefaultEditorViewController.h in Headers */,
+				C309B82F223ED64400B228EC /* FLEXLogController.h in Headers */,
 				94A5151F1C4CA1F10063292F /* FLEXWindow.h in Headers */,
 				779B1ECE1C0C4D7C001F5E49 /* FLEXDatabaseManager.h in Headers */,
 				3A4C94D51B5B21410088C3F2 /* FLEXObjectExplorerViewController.h in Headers */,
@@ -858,6 +878,7 @@
 			developmentRegion = English;
 			hasScannedForEncodings = 0;
 			knownRegions = (
+				English,
 				en,
 			);
 			mainGroup = 3A4C94151B5B20570088C3F2;
@@ -909,6 +930,7 @@
 				3A4C95121B5B21410088C3F2 /* FLEXPropertyEditorViewController.m in Sources */,
 				3A4C95391B5B21410088C3F2 /* FLEXNetworkRecorder.m in Sources */,
 				3A4C950E1B5B21410088C3F2 /* FLEXIvarEditorViewController.m in Sources */,
+				C3DC287C223ED5F200F48AA6 /* FLEXOSLogController.m in Sources */,
 				3A4C95101B5B21410088C3F2 /* FLEXMethodCallingViewController.m in Sources */,
 				3A4C94F61B5B21410088C3F2 /* FLEXArgumentInputJSONObjectView.m in Sources */,
 				3A4C94EC1B5B21410088C3F2 /* FLEXImagePreviewViewController.m in Sources */,
@@ -972,6 +994,7 @@
 				3A4C95231B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.m in Sources */,
 				3A4C950C1B5B21410088C3F2 /* FLEXFieldEditorViewController.m in Sources */,
 				3A4C952D1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.m in Sources */,
+				C33E46B0223B02CD004BD0E6 /* FLEXASLLogController.m in Sources */,
 				3A4C953D1B5B21410088C3F2 /* FLEXNetworkTransaction.m in Sources */,
 				3A4C94E81B5B21410088C3F2 /* FLEXHierarchyTableViewCell.m in Sources */,
 				3A4C94C81B5B21410088C3F2 /* FLEXClassExplorerViewController.m in Sources */,