Selaa lähdekoodia

Add interface for viewing system logs from FLEX

Ryan Olson 11 vuotta sitten
vanhempi
commit
b67d18f569

+ 12 - 0
Classes/Global State Explorers/FLEXGlobalsTableViewController.m

@@ -16,10 +16,12 @@
 #import "FLEXFileBrowserTableViewController.h"
 #import "FLEXGlobalsTableViewControllerEntry.h"
 #import "FLEXManager+Private.h"
+#import "FLEXSystemLogTableViewController.h"
 
 static __weak UIWindow *s_applicationWindow = nil;
 
 typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
+    FLEXGlobalsRowSystemLog,
     FLEXGlobalsRowLiveObjects,
     FLEXGlobalsRowFileBrowser,
     FLEXGlobalsRowSystemLibraries,
@@ -166,6 +168,16 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
                     return [[FLEXFileBrowserTableViewController alloc] init];
                 };
                 break;
+
+            case FLEXGlobalsRowSystemLog:
+                titleFuture = ^{
+                    return @"⚠️  System Log";
+                };
+                viewControllerFuture = ^{
+                    return [[FLEXSystemLogTableViewController alloc] init];
+                };
+                break;
+
             case FLEXGlobalsRowCount:
                 break;
         }

+ 21 - 0
Classes/Global State Explorers/System Log/FLEXSystemLogMessage.h

@@ -0,0 +1,21 @@
+//
+//  FLEXSystemLogMessage.h
+//  UICatalog
+//
+//  Created by Ryan Olson on 1/25/15.
+//  Copyright (c) 2015 f. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <asl.h>
+
+@interface FLEXSystemLogMessage : NSObject
+
++ (instancetype)logMessageFromASLMessage:(aslmsg)aslMessage;
+
+@property (nonatomic, strong) NSDate *date;
+@property (nonatomic, copy) NSString *sender;
+@property (nonatomic, copy) NSString *messageText;
+@property (nonatomic, assign) long long messageID;
+
+@end

+ 55 - 0
Classes/Global State Explorers/System Log/FLEXSystemLogMessage.m

@@ -0,0 +1,55 @@
+//
+//  FLEXSystemLogMessage.m
+//  UICatalog
+//
+//  Created by Ryan Olson on 1/25/15.
+//  Copyright (c) 2015 f. All rights reserved.
+//
+
+#import "FLEXSystemLogMessage.h"
+
+@implementation FLEXSystemLogMessage
+
++(instancetype)logMessageFromASLMessage:(aslmsg)aslMessage
+{
+    FLEXSystemLogMessage *logMessage = [[FLEXSystemLogMessage alloc] init];
+
+    const char *timestamp = asl_get(aslMessage, ASL_KEY_TIME);
+    if (timestamp) {
+        NSTimeInterval timeInterval = [@(timestamp) integerValue];
+        const char *nanoseconds = asl_get(aslMessage, ASL_KEY_TIME_NSEC);
+        if (nanoseconds) {
+            timeInterval += [@(nanoseconds) doubleValue] / NSEC_PER_SEC;
+        }
+        logMessage.date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
+    }
+
+    const char *sender = asl_get(aslMessage, ASL_KEY_SENDER);
+    if (sender) {
+        logMessage.sender = @(sender);
+    }
+
+    const char *messageText = asl_get(aslMessage, ASL_KEY_MSG);
+    if (messageText) {
+        logMessage.messageText = @(messageText);
+    }
+
+    const char *messageID = asl_get(aslMessage, ASL_KEY_MSG_ID);
+    if (messageID) {
+        logMessage.messageID = [@(messageID) longLongValue];
+    }
+
+    return logMessage;
+}
+
+- (BOOL)isEqual:(id)object
+{
+    return [object isKindOfClass:[FLEXSystemLogMessage class]] && self.messageID == [object messageID];
+}
+
+- (NSUInteger)hash
+{
+    return self.messageID;
+}
+
+@end

+ 23 - 0
Classes/Global State Explorers/System Log/FLEXSystemLogTableViewCell.h

@@ -0,0 +1,23 @@
+//
+//  FLEXSystemLogTableViewCell.h
+//  UICatalog
+//
+//  Created by Ryan Olson on 1/25/15.
+//  Copyright (c) 2015 f. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@class FLEXSystemLogMessage;
+
+extern NSString *const kFLEXSystemLogTableViewCellIdentifier;
+
+@interface FLEXSystemLogTableViewCell : UITableViewCell
+
+@property (nonatomic, strong) FLEXSystemLogMessage *logMessage;
+@property (nonatomic, copy) NSString *highlightedText;
+
++ (NSString *)displayedTextForLogMessage:(FLEXSystemLogMessage *)logMessage;
++ (CGFloat)preferredHeightForLogMessage:(FLEXSystemLogMessage *)logMessage inWidth:(CGFloat)width;
+
+@end

+ 128 - 0
Classes/Global State Explorers/System Log/FLEXSystemLogTableViewCell.m

@@ -0,0 +1,128 @@
+//
+//  FLEXSystemLogTableViewCell.m
+//  UICatalog
+//
+//  Created by Ryan Olson on 1/25/15.
+//  Copyright (c) 2015 f. All rights reserved.
+//
+
+#import "FLEXSystemLogTableViewCell.h"
+#import "FLEXSystemLogMessage.h"
+
+NSString *const kFLEXSystemLogTableViewCellIdentifier = @"FLEXSystemLogTableViewCellIdentifier";
+
+@interface FLEXSystemLogTableViewCell ()
+
+@property (nonatomic, strong) UILabel *logMessageLabel;
+@property (nonatomic, strong) NSAttributedString *logMessageAttributedText;
+
+@end
+
+@implementation FLEXSystemLogTableViewCell
+
+- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
+{
+    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
+    if (self) {
+        self.logMessageLabel = [[UILabel alloc] init];
+        self.logMessageLabel.numberOfLines = 0;
+        self.separatorInset = UIEdgeInsetsZero;
+        self.selectionStyle = UITableViewCellSelectionStyleNone;
+        [self.contentView addSubview:self.logMessageLabel];
+    }
+    return self;
+}
+
+- (void)setLogMessage:(FLEXSystemLogMessage *)logMessage
+{
+    if (![_logMessage isEqual:logMessage]) {
+        _logMessage = logMessage;
+        self.logMessageAttributedText = nil;
+        [self setNeedsLayout];
+    }
+}
+
+- (void)setHighlightedText:(NSString *)highlightedText
+{
+    if (![_highlightedText isEqual:highlightedText]) {
+        _highlightedText = highlightedText;
+        self.logMessageAttributedText = nil;
+        [self setNeedsLayout];
+    }
+}
+
+- (NSAttributedString *)logMessageAttributedText
+{
+    if (!_logMessageAttributedText) {
+        _logMessageAttributedText = [[self class] attributedTextForLogMessage:self.logMessage highlightedText:self.highlightedText];
+    }
+    return _logMessageAttributedText;
+}
+
+static const UIEdgeInsets kFLEXLogMessageCellInsets = {10.0, 10.0, 10.0, 10.0};
+
+- (void)layoutSubviews
+{
+    [super layoutSubviews];
+
+    self.logMessageLabel.attributedText = self.logMessageAttributedText;
+    self.logMessageLabel.frame = UIEdgeInsetsInsetRect(self.contentView.bounds, kFLEXLogMessageCellInsets);
+}
+
+#pragma mark - Stateless helpers
+
++ (NSAttributedString *)attributedTextForLogMessage:(FLEXSystemLogMessage *)logMessage highlightedText:(NSString *)highlightedText
+{
+    NSString *text = [self displayedTextForLogMessage:logMessage];
+    NSDictionary *attributes = @{ NSFontAttributeName : [UIFont fontWithName:@"CourierNewPSMT" size:12.0] };
+    NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:attributes];
+
+    if ([highlightedText length] > 0) {
+        NSMutableAttributedString *mutableAttributedText = [attributedText mutableCopy];
+        NSMutableDictionary *highlightAttributes = [@{ NSBackgroundColorAttributeName : [UIColor yellowColor] } mutableCopy];
+        [highlightAttributes addEntriesFromDictionary:attributes];
+        
+        NSRange remainingSearchRange = NSMakeRange(0, text.length);
+        while (remainingSearchRange.location < text.length) {
+            remainingSearchRange.length = text.length - remainingSearchRange.location;
+            NSRange foundRange = [text rangeOfString:highlightedText options:NSCaseInsensitiveSearch range:remainingSearchRange];
+            if (foundRange.location != NSNotFound) {
+                remainingSearchRange.location = foundRange.location + foundRange.length;
+                [mutableAttributedText setAttributes:highlightAttributes range:foundRange];
+            } else {
+                break;
+            }
+        }
+        attributedText = mutableAttributedText;
+    }
+
+    return attributedText;
+}
+
++ (NSString *)displayedTextForLogMessage:(FLEXSystemLogMessage *)logMessage
+{
+    return [NSString stringWithFormat:@"%@: %@", [self logTimeStringFromDate:logMessage.date], logMessage.messageText];
+}
+
++ (CGFloat)preferredHeightForLogMessage:(FLEXSystemLogMessage *)logMessage inWidth:(CGFloat)width
+{
+    UIEdgeInsets insets = kFLEXLogMessageCellInsets;
+    CGFloat availableWidth = width - insets.left - insets.right;
+    NSAttributedString *attributedLogText = [self attributedTextForLogMessage:logMessage highlightedText:nil];
+    CGSize labelSize = [attributedLogText boundingRectWithSize:CGSizeMake(availableWidth, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context:nil].size;
+    return labelSize.height + insets.top + insets.bottom;
+}
+
++ (NSString *)logTimeStringFromDate:(NSDate *)date
+{
+    static NSDateFormatter *formatter = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        formatter = [[NSDateFormatter alloc] init];
+        formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS";
+    });
+
+    return [formatter stringFromDate:date];
+}
+
+@end

+ 13 - 0
Classes/Global State Explorers/System Log/FLEXSystemLogTableViewController.h

@@ -0,0 +1,13 @@
+//
+//  FLEXSystemLogTableViewController.h
+//  UICatalog
+//
+//  Created by Ryan Olson on 1/19/15.
+//  Copyright (c) 2015 f. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface FLEXSystemLogTableViewController : UITableViewController
+
+@end

+ 196 - 0
Classes/Global State Explorers/System Log/FLEXSystemLogTableViewController.m

@@ -0,0 +1,196 @@
+//
+//  FLEXSystemLogTableViewController.m
+//  UICatalog
+//
+//  Created by Ryan Olson on 1/19/15.
+//  Copyright (c) 2015 f. All rights reserved.
+//
+
+#import "FLEXSystemLogTableViewController.h"
+#import "FLEXUtility.h"
+#import "FLEXSystemLogMessage.h"
+#import "FLEXSystemLogTableViewCell.h"
+#import <asl.h>
+
+@interface FLEXSystemLogTableViewController () <UISearchDisplayDelegate>
+
+@property (nonatomic, strong) UISearchDisplayController *searchController;
+@property (nonatomic, copy) NSArray *logMessages;
+@property (nonatomic, copy) NSArray *filteredLogMessages;
+@property (nonatomic, strong) NSTimer *logUpdateTimer;
+
+@end
+
+@implementation FLEXSystemLogTableViewController
+
+- (void)viewDidLoad
+{
+    [super viewDidLoad];
+
+    [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)];
+
+    UISearchBar *searchBar = [[UISearchBar alloc] init];
+    [searchBar sizeToFit];
+    self.searchController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
+    self.searchController.delegate = self;
+    self.searchController.searchResultsDataSource = self;
+    self.searchController.searchResultsDelegate = self;
+    [self.searchController.searchResultsTableView registerClass:[FLEXSystemLogTableViewCell class] forCellReuseIdentifier:kFLEXSystemLogTableViewCellIdentifier];
+    self.searchController.searchResultsTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
+    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 *logMessages = [[self class] allLogMessagesForCurrentProcess];
+        dispatch_async(dispatch_get_main_queue(), ^{
+            self.title = @"System Log";
+            self.logMessages = logMessages;
+
+            // "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];
+            }
+        });
+    });
+}
+
+- (void)scrollToLastRow
+{
+    NSInteger numberOfRows = [self.tableView numberOfRowsInSection:0];
+    if (numberOfRows > 0) {
+        NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:numberOfRows - 1 inSection:0];
+        [self.tableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
+    }
+}
+
+#pragma mark - Table view data source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+    return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+    NSInteger numberOfRows = 0;
+    if (tableView == self.tableView) {
+        numberOfRows = [self.logMessages count];
+    } else if (tableView == self.searchDisplayController.searchResultsTableView) {
+        numberOfRows = [self.filteredLogMessages count];
+    }
+    return numberOfRows;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
+{
+    FLEXSystemLogTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXSystemLogTableViewCellIdentifier forIndexPath:indexPath];
+    if (tableView == self.tableView) {
+        cell.logMessage = [self.logMessages objectAtIndex:indexPath.row];
+        cell.highlightedText = nil;
+    } else if (tableView == self.searchDisplayController.searchResultsTableView) {
+        cell.logMessage = [self.filteredLogMessages objectAtIndex:indexPath.row];
+        cell.highlightedText = self.searchDisplayController.searchBar.text;
+    }
+    if (indexPath.row % 2 == 0) {
+        cell.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0];
+    } else {
+        cell.backgroundColor = [UIColor whiteColor];
+    }
+    
+    return cell;
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    FLEXSystemLogMessage *logMessage = nil;
+    if (tableView == self.tableView) {
+        logMessage = [self.logMessages objectAtIndex:indexPath.row];
+    } else if (tableView == self.searchDisplayController.searchResultsTableView) {
+        logMessage = [self.filteredLogMessages objectAtIndex:indexPath.row];
+    }
+    return [FLEXSystemLogTableViewCell preferredHeightForLogMessage:logMessage inWidth:self.tableView.bounds.size.width];
+}
+
+#pragma mark - Search display delegate
+
+- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
+{
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+        NSArray *filteredLogMessages = [self.logMessages filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FLEXSystemLogMessage *logMessage, NSDictionary *bindings) {
+            NSString *displayedText = [FLEXSystemLogTableViewCell displayedTextForLogMessage:logMessage];
+            return [displayedText rangeOfString:searchString options:NSCaseInsensitiveSearch].length > 0;
+        }]];
+        dispatch_async(dispatch_get_main_queue(), ^{
+            if ([self.searchDisplayController.searchBar.text isEqual:searchString]) {
+                self.filteredLogMessages = filteredLogMessages;
+                [self.searchDisplayController.searchResultsTableView reloadData];
+            }
+        });
+    });
+
+    // Reload done after the data fetches asynchronously
+    return NO;
+}
+
+#pragma mark - Log Message Fetching
+
++ (NSArray *)allLogMessagesForCurrentProcess
+{
+    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.
+    NSString *pidString = [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]];
+    asl_set_query(query, ASL_KEY_PID, [pidString UTF8String], ASL_QUERY_OP_EQUAL);
+
+    aslresponse response = asl_search(NULL, query);
+    aslmsg aslMessage = NULL;
+
+    NSMutableArray *logMessages = [NSMutableArray array];
+
+    // Work around deprecated API warnings while supporting multiple deployment targets.
+#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0
+    while ((aslMessage = asl_next(response))) {
+        [logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
+    }
+    asl_release(response);
+#else
+    while ((aslMessage = aslresponse_next(response))) {
+        [logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
+    }
+    aslresponse_free(response);
+#endif
+
+    return logMessages;
+}
+
+@end

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

@@ -91,6 +91,9 @@
 		944F74B5197B458C009AB039 /* FLEXHierarchyTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 944F7484197B458C009AB039 /* FLEXHierarchyTableViewCell.m */; };
 		944F74B6197B458C009AB039 /* FLEXHierarchyTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 944F7486197B458C009AB039 /* FLEXHierarchyTableViewController.m */; };
 		944F74B7197B458C009AB039 /* FLEXImagePreviewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 944F7488197B458C009AB039 /* FLEXImagePreviewViewController.m */; };
+		946C6EC91A7598D3006545C2 /* FLEXSystemLogTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 946C6EC81A7598D3006545C2 /* FLEXSystemLogTableViewCell.m */; };
+		946C6ECC1A759928006545C2 /* FLEXSystemLogTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 946C6ECA1A759928006545C2 /* FLEXSystemLogTableViewController.m */; };
+		946C6ECF1A7599C4006545C2 /* FLEXSystemLogMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 946C6ECE1A7599C4006545C2 /* FLEXSystemLogMessage.m */; };
 		94C681F31A3E941800E1936D /* FLEXLayerExplorerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 94C681F21A3E941800E1936D /* FLEXLayerExplorerViewController.m */; };
 		D03647D919847248007D2A1B /* FLEXGlobalsTableViewControllerEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = D03647D819847248007D2A1B /* FLEXGlobalsTableViewControllerEntry.m */; };
 /* End PBXBuildFile section */
@@ -258,6 +261,12 @@
 		944F7486197B458C009AB039 /* FLEXHierarchyTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXHierarchyTableViewController.m; sourceTree = "<group>"; };
 		944F7487197B458C009AB039 /* FLEXImagePreviewViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXImagePreviewViewController.h; sourceTree = "<group>"; };
 		944F7488197B458C009AB039 /* FLEXImagePreviewViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXImagePreviewViewController.m; sourceTree = "<group>"; };
+		946C6EC71A7598D3006545C2 /* FLEXSystemLogTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FLEXSystemLogTableViewCell.h; path = "System Log/FLEXSystemLogTableViewCell.h"; sourceTree = "<group>"; };
+		946C6EC81A7598D3006545C2 /* FLEXSystemLogTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FLEXSystemLogTableViewCell.m; path = "System Log/FLEXSystemLogTableViewCell.m"; sourceTree = "<group>"; };
+		946C6ECA1A759928006545C2 /* FLEXSystemLogTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FLEXSystemLogTableViewController.m; path = "System Log/FLEXSystemLogTableViewController.m"; sourceTree = "<group>"; };
+		946C6ECB1A759928006545C2 /* FLEXSystemLogTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FLEXSystemLogTableViewController.h; path = "System Log/FLEXSystemLogTableViewController.h"; sourceTree = "<group>"; };
+		946C6ECD1A7599C4006545C2 /* FLEXSystemLogMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FLEXSystemLogMessage.h; path = "System Log/FLEXSystemLogMessage.h"; sourceTree = "<group>"; };
+		946C6ECE1A7599C4006545C2 /* FLEXSystemLogMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FLEXSystemLogMessage.m; path = "System Log/FLEXSystemLogMessage.m"; sourceTree = "<group>"; };
 		94C681F11A3E941800E1936D /* FLEXLayerExplorerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXLayerExplorerViewController.h; sourceTree = "<group>"; };
 		94C681F21A3E941800E1936D /* FLEXLayerExplorerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXLayerExplorerViewController.m; sourceTree = "<group>"; };
 		D03647D51984720F007D2A1B /* FLEXManager+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FLEXManager+Private.h"; sourceTree = "<group>"; };
@@ -549,6 +558,7 @@
 		944F7473197B458C009AB039 /* Global State Explorers */ = {
 			isa = PBXGroup;
 			children = (
+				946C6EC61A75986C006545C2 /* System Log */,
 				944F7474197B458C009AB039 /* FLEXClassesTableViewController.h */,
 				944F7475197B458C009AB039 /* FLEXClassesTableViewController.m */,
 				944F7476197B458C009AB039 /* FLEXFileBrowserTableViewController.h */,
@@ -593,6 +603,19 @@
 			name = "FLEX Integration";
 			sourceTree = "<group>";
 		};
+		946C6EC61A75986C006545C2 /* System Log */ = {
+			isa = PBXGroup;
+			children = (
+				946C6ECB1A759928006545C2 /* FLEXSystemLogTableViewController.h */,
+				946C6ECA1A759928006545C2 /* FLEXSystemLogTableViewController.m */,
+				946C6ECD1A7599C4006545C2 /* FLEXSystemLogMessage.h */,
+				946C6ECE1A7599C4006545C2 /* FLEXSystemLogMessage.m */,
+				946C6EC71A7598D3006545C2 /* FLEXSystemLogTableViewCell.h */,
+				946C6EC81A7598D3006545C2 /* FLEXSystemLogTableViewCell.m */,
+			);
+			name = "System Log";
+			sourceTree = "<group>";
+		};
 		D03647D619847235007D2A1B /* Private */ = {
 			isa = PBXGroup;
 			children = (
@@ -671,6 +694,8 @@
 			files = (
 				944F749B197B458C009AB039 /* FLEXArgumentInputNotSupportedView.m in Sources */,
 				535682AE18F3670300BAAD62 /* AAPLDatePickerController.m in Sources */,
+				946C6ECC1A759928006545C2 /* FLEXSystemLogTableViewController.m in Sources */,
+				946C6ECF1A7599C4006545C2 /* FLEXSystemLogMessage.m in Sources */,
 				535682AB18F3670300BAAD62 /* AAPLButtonViewController.m in Sources */,
 				944F74A2197B458C009AB039 /* FLEXArgumentInputViewFactory.m in Sources */,
 				944F74A1197B458C009AB039 /* FLEXArgumentInputView.m in Sources */,
@@ -701,6 +726,7 @@
 				944F74A5197B458C009AB039 /* FLEXFieldEditorViewController.m in Sources */,
 				944F7489197B458C009AB039 /* FLEXArrayExplorerViewController.m in Sources */,
 				535682B418F3670300BAAD62 /* AAPLPickerViewController.m in Sources */,
+				946C6EC91A7598D3006545C2 /* FLEXSystemLogTableViewCell.m in Sources */,
 				535682BE18F3670300BAAD62 /* AAPLWebViewController.m in Sources */,
 				944F7490197B458C009AB039 /* FLEXObjectExplorerViewController.m in Sources */,
 				944F7499197B458C009AB039 /* FLEXArgumentInputFontView.m in Sources */,