前言
对于那些做后端开发的工程师来说,看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, "%sn", "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;
}










