欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-12-19 21:58:34
前言 对于那些做后端开发的工程师来说,看log解bug应该是理所当然的事,但我接触到的移动应用开发的工程师里面,很多人并没有这个意识,查bug时总是一遍一遍的试图重现,试...

前言

对于那些做后端开发的工程师来说,看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来请求最新日志了。

上传服务端的部分很简单,实现简单的网络请求就可以,这儿不做叙述。

另外在实际项目中,可以设置一个开关来开启或关闭这个重定向,在调试测试的过程中可以打开开关来查看程序当前的日志。

通过以上处理,真机测试中,日志就可以很方便的获取和查看了,这样能节省不少人力和时间成本。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。

参考文档

上一篇:

下一篇: