中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

iOS中日志同步獲取NSLog重定向以及其他詳解

發布時間:2020-09-13 11:06:02 來源:腳本之家 閱讀:467 作者:DragonFly 欄目:移動開發

前言

對于那些做后端開發的工程師來說,看LOG解Bug應該是理所當然的事,但我接觸到的移動應用開發的工程師里面,很多人并沒有這個意識,查Bug時總是一遍一遍的試圖重現,試圖調試,特別是對一些不太容易重現的Bug經常焦頭爛額。

我們在真機測試時經常會發現一個難題是無法查看真機的NSLog類型的實時日志,這時候需要RD復現問題來定位當時的日志,以方便查找問題。這個問題在測試中是非常常見的,也是功能測試會花費比較長時間的一個原因。

以下我們討論下能即時查看日志的幾種方案。

NSLog輸出到哪里?

在iOS開發中,我們經常會用到NSLog調試,但是我們卻不太了解它。在NSLog本質是一個C函數,它的函數聲明如下:
FOUNDATION_EXPORT void NSLog(NSString *format, ...)

系統對它的說明是:Logs an error message to the Apple System Log facility.。他是用來輸出信息到標準Error控制臺上去的,其內部其實是使用Apple System Log的API。在調試階段,日志會輸出到到Xcode中,而在iOS真機上,它會輸出到系統的/var/log/syslog這個文件中。

在iOS中,把日志輸出到文件中的句柄在unistd.h文件中有定義:

#define STDIN_FILENO 0 /* standard input file descriptor */
#define STDOUT_FILENO 1 /* standard output file descriptor */
#define STDERR_FILENO 2 /* standard error file descriptor */

NSLog輸出的是到STDERR_FILENO上,我們可以在iOS中使用c語言輸出到的文件的fprintf來驗證:

NSLog(@"iOS NSLog");
fprintf (stderr, "%s\n", "fprintf log");

由于fprintf并不會像NSLog那樣,在內部調用ASL接口,所以只是單純的輸出信息,并沒有添加日期、進程名、進程id等,也不會自動換行。

ASL讀取日志

首先我們可以想到的是既然日志寫入系統的syslog中,那我們可以直接讀取這些日志。從ASL讀取日志的核心代碼如下:

#import <asl.h>
// 從日志的對象aslmsg中獲取我們需要的數據
+(instancetype)logMessageFromASLMessage:(aslmsg)aslMessage{
 SystemLogMessage *logMessage = [[SystemLogMessage 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.timeInterval = timeInterval;
  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);//NSLog寫入的文本內容
 }
 const char *messageID = asl_get(aslMessage, ASL_KEY_MSG_ID);
 if (messageID) {
  logMessage.messageID = [@(messageID) longLongValue];
 }
 return logMessage;
}
+ (NSMutableArray<SystemLogMessage *> *)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];
 while ((aslMessage = asl_next(response))) {
  [logMessages addObject:[SystemLogMessage logMessageFromASLMessage:aslMessage]];
 }
 asl_release(response);
 return logMessages;
}

使用以上方法的好處是不會影響Xcode控制臺的輸出,可以用非侵入性的方式來讀取日志。

NSLog重定向

另一種方式就是重定向NSLog,這樣NSLog就不會寫到系統的syslog中了。

dup2重定向

通過重定向,可以直接截取stdout,stderr等標準輸出的信息,然后保存在想要存儲的位置,上傳到服務器或者顯示到View上。

要做到重定向,需要通過NSPipe創建一個管道,pipe有讀端和寫端,然后通過dup2將標準輸入重定向到pipe的寫端。再通過NSFileHandle監聽pipe的讀端,最后再處理讀出的信息。

之后通過printf或者NSLog寫數據,都會寫到pipe的寫端,同時pipe會將這些數據直接傳送到讀端,最后通過NSFileHandle的監控函數取出這些數據。

核心代碼如下:

- (void)redirectStandardOutput{
 //記錄標準輸出及錯誤流原始文件描述符
 self.outFd = dup(STDOUT_FILENO);
 self.errFd = dup(STDERR_FILENO);
#if BETA_BUILD
 stdout->_flags = 10;
 NSPipe *outPipe = [NSPipe pipe];
 NSFileHandle *pipeOutHandle = [outPipe fileHandleForReading];
 dup2([[outPipe fileHandleForWriting] fileDescriptor], STDOUT_FILENO);
 [pipeOutHandle readInBackgroundAndNotify];
 stderr->_flags = 10;
 NSPipe *errPipe = [NSPipe pipe];
 NSFileHandle *pipeErrHandle = [errPipe fileHandleForReading];
 dup2([[errPipe fileHandleForWriting] fileDescriptor], STDERR_FILENO);
 [pipeErrHandle readInBackgroundAndNotify];
 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(redirectOutNotificationHandle:) name:NSFileHandleReadCompletionNotification object:pipeOutHandle];
 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(redirectErrNotificationHandle:) name:NSFileHandleReadCompletionNotification object:pipeErrHandle];
#endif
}
-(void)recoverStandardOutput{
#if BETA_BUILD
 dup2(self.outFd, STDOUT_FILENO);
 dup2(self.errFd, STDERR_FILENO);
 [[NSNotificationCenter defaultCenter] removeObserver:self];
#endif
}
// 重定向之后的NSLog輸出
- (void)redirectOutNotificationHandle:(NSNotification *)nf{
#if BETA_BUILD
 NSData *data = [[nf userInfo] objectForKey:NSFileHandleNotificationDataItem];
 NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
 // YOUR CODE HERE... 保存日志并上傳或展示
#endif
 [[nf object] readInBackgroundAndNotify];
}
// 重定向之后的錯誤輸出
- (void)redirectErrNotificationHandle:(NSNotification *)nf{
#if BETA_BUILD
 NSData *data = [[nf userInfo] objectForKey:NSFileHandleNotificationDataItem];
 NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
 // YOUR CODE HERE... 保存日志并上傳或展示
#endif
 [[nf object] readInBackgroundAndNotify];
}

文件重定向

另一種重定向的方式是利用c語言的freopen函數進行重定向,將寫往stderr的內容重定向到我們制定的文件中去,一旦執行了上述代碼那么在這個之后的NSLog將不會在控制臺顯示了,會直接輸出在指定的文件中。

在模擬器中,我們可以使用終端的tail命令(tail -f xxx.log)對這個文件進行實時查看,就如同我們在Xcode的輸出窗口中看到的那樣,你還可以結合grep命令進行實時過濾查看,非常方便在大量的日志信息中迅速定位到我們要的日志信息。

FILE * freopen ( const char * filename, const char * mode, FILE * stream );

具體代碼如下:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *loggingPath = [documentsPath stringByAppendingPathComponent:@"/xxx.log"];
//redirect NSLog
freopen([loggingPath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);

這樣我們就可以把可獲取的日志文件發送給服務端或者通過itunes共享出來。但是由于iOS嚴格的沙盒機制,我們無法知道stderr原來的文件路徑,也無法直接使用沙盒外的文件,所以freopen無法重定向回去,只能使用第1點所述的dup和dup2來實現。

// 重定向
int origin1 = dup(STDERR_FILENO);
FILE * myFile = freopen([loggingPath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
// 恢復重定向
dup2(origin1, STDERR_FILENO);

使用GCD的dispatch Source重定向方式

具體代碼如下:

- (dispatch_source_t)_startCapturingWritingToFD:(int)fd {
 int fildes[2];
 pipe(fildes); // [0] is read end of pipe while [1] is write end
 dup2(fildes[1], fd); // Duplicate write end of pipe "onto" fd (this closes fd)
 close(fildes[1]); // Close original write end of pipe
 fd = fildes[0]; // We can now monitor the read end of the pipe
 char* buffer = malloc(1024);
 NSMutableData* data = [[NSMutableData alloc] init];
 fcntl(fd, F_SETFL, O_NONBLOCK);
 dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
 dispatch_source_set_cancel_handler(source, ^{
  free(buffer);
 });
 dispatch_source_set_event_handler(source, ^{
  @autoreleasepool {
   while (1) {
    ssize_t size = read(fd, buffer, 1024);
    if (size <= 0) {
     break;
    }
    [data appendBytes:buffer length:size];
    if (size < 1024) {
     break;
    }
   }
   NSString *aString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
   //printf("aString = %s",[aString UTF8String]);
   //NSLog(@"aString = %@",aString);
   // Do something
  }
 });
 dispatch_resume(source);
 return source;
}

日志同步/上傳

重定向或者存儲的數據可以傳到服務端或者通過server同步到網頁上,就可以更方便的看到這些數據了。

如果想再網頁端實時查看日志,可以在App內置一個小型http web服務器。GitHub上開源的項目有GCDWebServer,可以使用該工具,在APP開啟webserver服務,并在同一局域網下,使用http://localhost:8080來請求最新日志了。

上傳服務端的部分很簡單,實現簡單的網絡請求就可以,這兒不做敘述。

另外在實際項目中,可以設置一個開關來開啟或關閉這個重定向,在調試測試的過程中可以打開開關來查看程序當前的日志。

通過以上處理,真機測試中,日志就可以很方便的獲取和查看了,這樣能節省不少人力和時間成本。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。

參考文檔

  • iOS IO重定向
  • iOS 日志
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

弋阳县| 岢岚县| 建始县| 安阳市| 德阳市| 富平县| 香格里拉县| 巴里| 开封县| 永寿县| 炉霍县| 上高县| 陆良县| 大安市| 台南市| 皮山县| 明水县| 新田县| 叙永县| 保康县| 大邑县| 蕉岭县| 米脂县| 泾阳县| 威信县| 荆门市| 晴隆县| 自治县| 保山市| 平顶山市| 神木县| 万宁市| 桓台县| 杂多县| 禹州市| 西乌珠穆沁旗| 醴陵市| 泰来县| 东方市| 新平| 鄄城县|