捕獲NSLog日誌小記

既往不戀,縱情向前ios

原文連接git

1、NSLog概述

一、NSLog是什麼
  • NSLog是一個C函數,函數聲明以下:
//Logs an error message to the Apple System Log facility.
FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2) NS_NO_TAIL_CALL;
複製代碼
  • 根據蘋果的文檔介紹,NSLog的做用是輸出信息到標準的Error控制檯和 蘋果的日誌系統(ASL,Apple System Log)裏面(iOS 10以前)。github

  • iOS10以後,蘋果使用新的統一日誌系統(Unified Logging System)來記錄日誌,全面取代ASL的方式,此種方式,是把日誌集中存放在內存和數據庫裏,並提供單1、高效和高性能的接口去獲取系統全部級別的消息傳遞。objective-c

  • 新的統一日誌系統沒有ASL那樣的接口可讓咱們取出所有日誌。數據庫

二、NSLog平常使用
  • NSLog在調試階段,日誌會輸出到到Xcode中,而在iOS真機上,它會輸出到系統的/var/log/syslog這個文件中。瀏覽器

  • 在平常開發中,不少人喜歡使用NSLog來輸出調試信息,可是都知道NSLog是比較消耗性能呢,NSLog輸出的內容或次數多了以後,甚至會影響App的體驗。緩存

  • 因而乎,比較常見的手段是,線上不使用NSLog,DEBUG下才真正使用NSLog。bash

#if DEBUG
#define MYLOG(fmt, ...) NSLog((@"%s [Line %d] " fmt), PRETTY_FUNCTION, LINE, ##VA_ARGS);
#else
#define MYLOG(fmt,...) {}
#endif
複製代碼
三、常見的日記收集框架
  • 日誌收集主要用了兩個開源框架來實現:PLCrashReporterCocoaLumberjackPLCrashReporter主要用來崩潰日誌收集,CocoaLumberjack是用來收集非崩潰日誌。
  • CocoaLumberjack中實現了對NSLog日誌的捕獲。
四、捕獲NSLog日誌有三種方式
  • iOS 10之前能夠經過ASL接口來獲取
  • 經過fishhook庫hook NSLog方法重定向NSLog函數
  • 使用dup2函數和STDERR句柄重定向NSLog函數

2、獲取NSLog的日誌輸出(iOS 10前)

參考CocoaLumberjack中的DDASLLogCapture實現服務器

一、流程介紹
  • 執行DDASLLogCapturestart方法,啓動一個異步全局隊列去捕獲ASL存儲的日誌;
+ (void)start {
	//...
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
        [self captureAslLogs];
    });
}
複製代碼
  • 當日志被保存到ASL的數據庫時候,syslogd(系統裏用於接收分發日誌消息的日誌守護進程)會發出一條通知。由於發過來的這一條通知可能有多條日誌,須要先將幾條日誌進行合併。
+ (void)captureAslLogs {
    //....
}
複製代碼
  • 將得到到的數據轉成char 字符串類型,再轉成NSString類型,最後封裝成DDLogMessage對象,經過[DDLog log: message:] 方法將日誌記錄下來。
+ (void)aslMessageReceived:(aslmsg)msg {
    //...
}
複製代碼

說明:以上方法不會影響Xcode控制檯的輸出,無侵入。app

二、註冊進程間的系統通知
  • captureAslLogs中經過notify_register_dispatch來註冊監聽進程間的系統通知;
notify_register_dispatch(kNotifyASLDBUpdate, &notifyToken, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(int token)
        {
            //...
        });
複製代碼
  • 其中宏kNotifyASLDBUpdate表示:日誌被保存在ASL數據庫發出的跨進程通知;
/*
 * ASL notifications
 * Sent by syslogd to advise clients that new log messages have been
 * added to the ASL database.
 */
#define kNotifyASLDBUpdate "com.apple.system.logger.message"
複製代碼
  • 將日誌保存到ASL數據庫時還有不少通知,好比宏kNotifyVFSLowDiskSpace表示:系統磁盤空間不足,捕獲到這個通知時,能夠去清理緩存空間,避免緩存寫入磁盤失敗的狀況。
#define kNotifyVFSLowDiskSpace "com.apple.system.lowdiskspace"
複製代碼

3、NSLog重定向

一、介紹
  • 在iOS10以後,新的統一日誌系統(Unified Logging System)全面取代ASL,沒有ASL那樣的接口可讓咱們取出所有日誌,因此爲了兼容新的統一日誌系統,你就須要對NSLog日誌的輸出進行重定向。
  • NSLog 進行重定向,能夠採用 Hook的方式。由於 NSLog 自己就是一個 C 函數,可使用fishhook進行重定向。
  • fishhook是Facebook提供的一個動態修改連接Mach-O文件的工具,可以hook C函數。
二、fishhook原理
  • APP運行時,Mach-O文件被dyld(動態加載器)加載進內存

  • ASLR(地址空間佈局隨機化)讓Mach-O被加載時內存地址隨機分配

  • 蘋果的PIC位置與代碼獨立技術,讓Mach-O調用系統庫函數時,先在Mach-O表中的_DATA段創建一個指針指向外部庫函數,dyld加載MachO時知道外部庫函數的調用地址,會動態的把_DATA段的指針指向外部庫函數

  • fishhook可以替換NSLog等庫函數,這事是由於Mach-O的符號表裏有NSLog等,能夠經過符號表找到NSLog字符串。

說明:具體原理參考iOS逆向工程 - fishhook原理

三、利用fishhook hook NSLog函數

實現代碼以下:

//申明一個函數指針用於保存原NSLog的真實函數地址
static void (*orig_nslog)(NSString *format, ...);

//NSLog重定向
void redirect_nslog(NSString *format, ...) {
    
    //能夠添加本身的處理,好比輸出到本身的持久化存儲系統中

    //繼續執行原來的 NSLog
    va_list va;
    format = [NSString stringWithFormat:@"[hook success]%@",format];
    va_start(va, format);
    NSLogv(format, va);
    va_end(va);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
         struct rebinding nslog_rebinding = {"NSLog",redirect_nslog,(void*)&orig_nslog};
    	 rebind_symbols((struct rebinding[1]){nslog_rebinding}, 1);
         NSLog(@"%@, hello word!",@"ss");
    }
    return
}

//[hook success]ss, hello word!
複製代碼
  • 利用fishhook對方法的符號地址進行了從新板頂,從而只要是NDSLog的調用就會轉向redirect_nslog方法調用。

參考使用fishhook hook NSLog 函數

4、dup2重定向

一、介紹
  • NSLog最後重定向的句柄是STDERR,NSLog輸出的日誌內容,最終都經過STDERR句柄來記錄,而dup2函數式專門進行文件重定向的;
  • 可使用dup2重定向STDERR句柄,將內容重定向指定的位置,如寫入文件,上傳服務器,顯示到View上。
二、核心代碼
  • 實現重定向,須要經過NSPipe建立一個管道,pipe有讀端和寫端,而後經過dup2將標準輸入重定向到pipe的寫端。再經過NSFileHandle監聽pipe的讀端,最後再處理讀出的信息。
  • 以後經過printf或者NSLog寫數據,都會寫到pipe的寫端,同時pipe會將這些數據直接傳送到讀端,最後經過NSFileHandle的監控函數取出這些數據。
- (void)redirectSTD:(int )fd {
    
    NSPipe * pipe = [NSPipe pipe] ;
    NSFileHandle *pipeReadHandle = [pipe fileHandleForReading] ;
    int pipeFileHandle = [[pipe fileHandleForWriting] fileDescriptor];
    dup2(pipeFileHandle, fd) ;
    
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(redirectNotificationHandle:)
                                                 name:NSFileHandleReadCompletionNotification
                                               object:pipeReadHandle] ;
    [pipeReadHandle readInBackgroundAndNotify];
}

- (void)redirectNotificationHandle:(NSNotification *)nf {
    NSData *data = [[nf userInfo] objectForKey:NSFileHandleNotificationDataItem];
    NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ;
    //能夠添加本身的處理,能夠將內容顯示到View,或者是存放到另外一個文件中等等
    //todo
    
    
    [[nf object] readInBackgroundAndNotify];
}

//使用
[self redirectSTD:STDERR_FILENO];
複製代碼
相關文章
相關標籤/搜索