iOS IO 重定向(NSLog to UITextView)

5 Comments

情形描述:

在调试程序的时候,通过NSLog打印log,很方便的就可以在Xcode里面看到。但是程序一旦“离开XCode运行”, 比如将App交付给了公司的测试团队,怎样能够很随意看到NSLog打印的信息呢?通常在离开xcode之后,NSLog的信息会保存在Systemlog里面(这里有NSLog详细描述),你可以通过一定办法取出这个log。甚至可以写一套日志系统,然后将这些信息保存到日志中,然后导出或者上传自己的服务器。但是这些太麻烦了,简直是弱爆鸟。我们的目的是:在App里面能够直接像xCode console窗口那样显示NSLog的信息,准确的说是标准输出的信息。

关键技术:IO重定向

通过IO重定向,我们可以直接“截取” stdout,stderr等标准输出的信息(NSLog->stderr),然后再在自己的View上显示出来。这里只展示IO重定向相关代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@implement TestAppDelegate

- (void)redirectNotificationHandle:(NSNotification *)nf{
  NSData *data = [[nf userInfo] objectForKey:NSFileHandleNotificationDataItem];
  NSString *str = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];

  self.logTextView.text = [NSString stringWithFormat:@"%@\n%@",self.logTextView.text, str];
  NSRange range;
  range.location = [self.logTextView.text length] - 1;
  range.length = 0;
  [self.logTextView scrollRangeToVisible:range];

  [[nf object] readInBackgroundAndNotify];
}

- (void)redirectSTD:(int )fd{
  NSPipe * pipe = [NSPipe pipe] ;
  NSFileHandle *pipeReadHandle = [pipe fileHandleForReading] ;
  dup2([[pipe fileHandleForWriting] fileDescriptor], fd) ;

  [[NSNotificationCenter defaultCenter] addObserver:self
                                           selector:@selector(redirectNotificationHandle:)
                                               name:NSFileHandleReadCompletionNotification
                                             object:pipeReadHandle] ;
  [pipeReadHandle readInBackgroundAndNotify];
}
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOption{

  [self redirectSTD:STDOUT_FILENO];
  [self redirectSTD:STDERR_FILENO];

//YOUR CODE HERE...
}

//YOUR CODE HERE...

@end

主要代码就功能就是:

  1. 通过NSPipe创建一个管道(这里有详细讲NSPipe的文章),pipe有读端和写端。
  2. 通过dup2(这里有详细将dup2的文章)讲标准输入重定向到pipe的写端。
  3. 通过NSFileHandle监听pipe的读端,然后讲读出的信息显示在uitextview上。

通过上面三步,一旦通过printf或者NSLog写数据,因为重定向过,这些数据都会写到Pipe的写端。同时pipe会将这些数据从写端直接传送到读端。读端通过NSFileHandle的“监控”函数取出这些数据,并最终显示在uitextview上。

DONE!
在真实的项目中,你可以设置一个开关去开启或者关闭这个重定向。在调试测试程序的过程中,打开开关,快捷及时查看程序运行状况;产品发布的时候关掉开关就OK了,用户可以毫不知情。这样,也许会提高不少社会主义生产力。

下面展示一下效果截图:

iOS/Mac OS 调试信息输出(一)

No Comments

调试信息输出方法介绍

在Apple Tech Note TN2239:iOS Debugging Magic(http://developer.apple.com/library/ios/#technotes/tn2010/tn2239.html)中提到了程序开发中Debug output 方法:

  1. NSLog
  2. stderr
  3. system log

调试信息的输出主要有方式,一是通过输出到终端输出,二是输出到日志系统。下面讲介绍一下这几种输出调试信息的方式,首先从stderr说起。

1. stderr (引用自TN2239):

Many programs, and indeed many system frameworks, print debugging messages to stderr. The destination for this output is ultimately controlled by the program: it can redirect stderr to whatever destination it chooses. However, in most cases a program does not redirect stderr, so the output goes to the default destination inherited by the program from its launch environment. This is typically one of the following:

  • If you launch a GUI application as it would be launched by a normal user, the system redirects any messages printed on stderr to the system log. You can view these messages using the techniques described earlier.
  • If you run a program from within Xcode, you can see its stderr output in Xcode’s debugger Console window (choose the Console menu item from the Run menu to see this window).

Attaching to a running program (using Xcode’s Attach to Process menu, or the attach command in GDB) does not automatically connect the program’s stderr to your GDB window. You can do this from within GDB using the trick described in the “Seeing stdout and stderr After Attaching” section of Technical Note TN2030, ‘GDB for MacsBug Veterans’.

这样一段代码在真机上跑:

NSLog(@”This is message from NSLog”);

fprintf(stderr, “This is message from stderr\n”);

1)如果是通过Xcode调试加载运行这个程序,那么

在xcode的console中打印如下:

2011-03-12 18:52:26.948 Test86[7891:307] This is message from NSLog

This is message from stderr

在iPhone的system log中(通过Organizer的console查看)只打印

Sat Mar 12 18:52:26 unknown Test86[7891] <Warning>: This is message from NSLog

2)但是如果在iPhone上通过手指触摸启动这个程序,在iPhone的system log中会打印:

Sat Mar 12 18:53:38 unknown Test86[7900] <Warning>: This is message from NSLogSat Mar 12 18:53:38 unknown UIKitApplication:com.yourcompany.Test86[0x7d60][7900] <Notice>: This is message from stderr

说明确实stderr在user 自己launch的app中被重定向为system log,而且log的等级为Notice;NSLog的等级为Warning。

 

2. system log

其实system log是unix系统都有采用syslog协议的一个日志系统(RFC详细讲解了这种协议http://tools.ietf.org/html/rfc5424)。每条日志是有等级的,主要分为如下等级:

  • Level 0 – “Emergency”
  • Level 1 – “Alert”
  • Level 2 – “Critical”
  • Level 3 – “Error”
  • Level 4 – “Warning”
  • Level 5 – “Notice”
  • Level 6 – “Info”
  • Level 7 – “Debug”

在创建好日志之后,通过调用API发送日志信息给一个叫做syslogd的守护进程,然后syslogd根据自己的配置文件(位于/private/etc/syslog.conf, 具体怎么配置这篇文章说得很详细 http://study.chyangwa.com/IT/AIX/aixcmds5/syslogd.htm),最后讲日志保存早自己的系统日志“数据库”里面。有兴趣的可以去打卡这个syslog.conf文件看看,我Mac上的配置文件是这样的:

*.notice;authpriv,remoteauth,ftp,install,internal.none /var/log/system.log(此处省去若干字。。。)

其中” *.notice “ 指明了任何等级比notice高的等级都要录入到 /var/log/system.log 这个文件中去。

在mac os和ios那么怎样调用  API讲日志发送给系统日志呢?有两种API:

这里还有一些讲Syslog不错的文章,:

“Accesing the iOS system log” :  http://www.cocoanetics.com/2011/03/accessing-the-ios-system-log/

“Why ASL?”  http://boredzo.org/blog/archives/2008-01-20/why-asl

 

3. NSLog

NSLog应该是我们最熟悉的方式,其实也应该是每一个学习Objective C第一句会的语法,然后你对它真正了解多少?NSLog顾名思义,出去namespace NS 就是Log,其主要的功能就是为Cocoa程序编写人员提供一种简单的输入日志的方式。但是我们很多时候都讲其误认为是printf,而且也只是当printf用。如果是这样就太可惜了。

NSLog is a high-level API for logging which is used extensively by Objective-C code. The exact behaviour of NSLog is surprisingly complex, and has changed significantly over time, making it beyond the scope of this document. However, it’s sufficient to know that NSLog prints to stderr, or logs to the system log, or both. So, if you understand those two mechanisms, you can see anything logged via NSLog.[引用自TN2239]

看见这个surprisingly complex也许就知道NSLog也许没那么简单了,我讲在另外一篇文章中详细讲解NSLog, 《iOS/Mac OS 调试信息输出(二)之 NSLog没那么简单》

 

iOS/Mac OS 调试信息输出(二)

8 Comments

NSLog,没那么简单

在前一篇《iOS/Mac OS 调试信息输出(一)》中介绍了输出调试信息的方法。现在讲主角:NSLog。

1.NSLog工作流程

前面已经说过我们很多时候都只是讲NSLog当做是printf,比printf好就好在:1. 自动添加了换行符;2. 在信息头还添加了一些其他更易于阅读和标示的信息,如“2011-03-12 20:18:34.000 Test14[40871:903]”。但其实NSLog更重要的功能是Log功能。看了下面这幅图你也许久明白了NSLog的工作原理了。

NSLog工作流程图

所以从图中可以看出NSLog的工作主要是分为:1. 输出信息到终端,2. 输出信息到System Log中去。

一般开发的时候程序都是从Xcode中启动,所以这个时候NSLog就具有打印信息到终端的能力就像printf一样(说fprintf(stderr, ….) 更合适一点)。而且很自然也就认为NSLog只是用来打印这些调试信息用的。

但是当程序不是从xcode或者ternimal启动等时,因为stderr不是被定向到标准终端这个时候NSLog的功能就是把信息输入到System Log中去。如果这个时候你也用的stderr输入信息,这个时候stderr会被定向到System Log中去。

2. Log结构

我之前说过Log信息是有level之分的,完整的Log是包含很多key-value形式的其他信息的,下面我们看一个通过NSLog输入到System Log的Log实例:
{
ASLMessageID = 3827200;
“CFLog Local Time” = “2011-03-12 16:31:02.592″;
“CFLog Thread” = 903;
Facility = “com.apple.console”;
GID = 20;
Host = “Xu-Benyangs-MacBook-Pro”;
Level = 4;
Message = “Hello World!”;
PID = 27692;
ReadUID = 501;
Sender = Test14;
Time = 1299918662;
TimeNanoSec = 593334000;
UID = 501;
}

其中我们比较关注的有这几项:

  1. Facility:用一个反链接形式字符串标示发送Log的来源,比如NSLog内部就使用的是com.apple.console
  2. Level: 等级,NSLog的等级是4,也就是Warnning,根据之前我们多/private/etc/syslog.conf 查看,只要是在Notice(5)之上的Log都可以被记录到/private/var/system.log中去。所以你调用一个NSLog你就可以去看看你的system.log文件
  3. Message: 就是消息本省
  4. Sender:你的应用程序名称
所以从上面信息可以看出NSLog的Log是:Facility=“com.apple.console”; Level=4(每一条NSLog都当做是一个Warning Log)的信息。而顺便提一下stderr如果被定向到System Log,那么是:Facility=“user”, Level=5(每一条是当做一个Notice的Log)。

3. 查看Log的方法

那么NSLog既然主要作用是用来发送System Log的,那么如何可以方便查看这些Log呢?特别是哪些搞测试和调程序的同学来讲,看Log是做常见的事情,所以这里给出在Mac和iOS下在没有源代码或者脱离了Xcode运行环境怎么看Log的方法。

Mac

  1. 如果你的systemlog.conf配置正确,就可以直接去查看这个文件 /private/var/log/system.log
  2. 打开/Applications/Utilities/Console这个程序,在Log List中选Console Messages就可以查看所有程序的NSLog打印出来的信息了。如图:

如图所示,Console中也提供了查看system.log的快捷方式。这里可能对你造成困惑,为什么有了system.log还有一个Console Messages。其实说白了Console Messages只是讲system log 中facility为“com.apple.console”的都筛选出来了,所以你就姑且可以把Console Messages中的信息认识全部是NSLog产生的。但是其实如果你自己发送一个facility=“com.apple.console”的日志,同样会出现在Console Messages中。

iOS
在真机上调试程序的时候打开Xcode 中的Oganizer, 然后在你当前的device下选取Console就可以看到这个Device的System Log了,如图。


这里我用的是xcode 4,所以界面和你的也许会不一样。

4. 程序员使用NSLog注意事项

在了解了NSLog的工作原理之后,你还敢像只是用printf那样使用NSLog了吗?我可以很负责人的告诉你虽然苹果的设备硬件条件都很牛B,但是NSLog还是是一件非常expensive的事情,主要体现在这两点:
  1. NSLog在打印信息的同时要发送日志,效率低
  2. 程序一些调试信息通过NSLog发送到System Log之后,很容易被其他人查看到,对你程序的安全性造成了直接的威胁。除了通过xcode的oganizer可以查看console,在iPhone上有一个叫ConsoleLog的程序可以查看你的这些日志(http://itunes.apple.com/us/app/consolelog/id415116252?mt=8)。
所以综合这些因素,我们应该在程序release的时候尽量去掉NSLog,但是如果程序大了一个一个去删NSLog似乎也不现实。于是网上就有不少牛人提供了不少的解决方案。推荐一篇著名的Cocoa Is My Girlfriend的Dropping NSLog in Release Build” : http://www.cimgf.com/2009/01/24/dropping-nslog-in-release-builds/ .

最后,感谢CamScanner帮我把手绘直接扫描成电子版。

 

  • RSS
  • Twitter
  • Buzz
  • LinkedIn
  • Flickr