iOS开发-全量日志捕获CocoaLumberjack
文章目录
- 前言
- 日志重定向
- Lumberjack组成
- Capture 捕捉
- Logger 输出
- message and formatter 消息以及格式化
- ASL 日志系统
- TTY 控制台输出
- os_log 新日志系统
- file logger 文件输出
- DDLogFileManager 文件输出协议
- 文件压缩
前言
全量日志
就是app的运行日志打印等等。有时候光凭Crash
日志并不能找到并解决问题,如果有Crash
时App
的日志输出,则会事半功倍。
CocoaLumberjack
是OSX
和iOS
平台优秀的全量日志
抓取第三方库。github链接
此篇文章更着重于分析其实现以及结构组成。
日志重定向
我们通过日志重定向可以进行将控制台的输出日志存储到文件中
- (void)redirectLogToDocumentFolder
{// 获取沙盒路径NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);NSString *documentDirectory = [paths objectAtIndex:0];// 获取打印输出文件路径NSString *fileName = [NSString stringWithFormat:@"myData.log"];NSString *logFilePath = [documentDirectory stringByAppendingPathComponent:fileName];// 先删除已经存在的文件NSFileManager *defaultManager = [NSFileManager defaultManager];[defaultManager removeItemAtPath:logFilePath error:nil];// 将NSLog的输出重定向到文件,因为C语言的printf打印是往stdout打印的,这里也把它重定向到文件freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding],"a+", stdout);freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding],"a+", stderr);
}
这样的坏处是,当重定向之后,控制台不再打印日志输出了,虽然我们可以判断xcode是否连接,然后再进行重定向,来解决连接xcode调试的问题。
但是还是有不足,就是当你的日志输出你只想自己看到,而不想影响控制台的输出时,显然重定向不能做到,我们需要自己的日志输出入口,同时还要能监听到系统的日志输出,而不影响控制台的日志。
CocoaLumberjack
就可以帮我们实现这个功能。一般来说我们需要创建3个logger
,分别是
ASL
用于记录系统的日志输出,这个输出xcode有些
不会打印出来TTY
控制台会打印的日志输出File
写入文件中,存为log文件,然后上传,便于分析问题。
Lumberjack组成
传统的NSLog()
函数将它的输出指向两个地方:
- 苹果系统日志
ASL (Apple System Logs)
StdErr
(如果StdErr
是一个TTY
),所以日志语句显示在Xcode
控制台
Capture 捕捉
DDASLLogCapture
为CocoaLumberjack
中唯一的一个Capture
类,用与捕获ASL
日志
Logger 输出
logger
用于输出日志,有
DDASLLogger
用于输出到ASL
DDOSLogger
是iOS 10
之后公开的日志输出方式,用于取代ASL
,你可以在官方文档 中查看接口DDTTYLogger
该类为终端输出
或Xcode控制台
输出提供一个日志记录器DDFileLogger
用于将日志输出到文件中,我们一般存储日志文件之后进行压缩。
要实现替换 NSLog()
的功能,您可以简单地添加DDASLLogger
和一个DDTTYLogger
。
但是,如果您选择使用文件记录器(DDFileLogger)
(以获得更快的性能),
你可以选择只使用一个文件记录器(DDFileLogger)
和一个tty记录器(DDTTYLogger)
。
message and formatter 消息以及格式化
DDLogMessage
是封装的消息实体
DDLogFormatter
是对输出的字符串格式化的类别
你可以对照CocoaLumberjack
源码中的Demos
进行更好的理解
里面各种场景都很有参考意义。
ASL 日志系统
ASL (Apple system logger)
是苹果公司自己实现的一套输出日志的接口。
通过DDASLLogger.m
文件,我们了解到captureAslLogs
做了捕捉日志输出的功能
+ (void)captureAslLogs {@autoreleasepool{/*We use ASL_KEY_MSG_ID to see each message once, but there's noobvious way to get the "next" ID. To bootstrap the process, we'llsearch by timestamp until we've seen a message.*/struct timeval timeval = {.tv_sec = 0};gettimeofday(&timeval, NULL);unsigned long long startTime = (unsigned long long)timeval.tv_sec;__block unsigned long long lastSeenID = 0;/*syslogd posts kNotifyASLDBUpdate (com.apple.system.logger.message)through the notify API when it saves messages to the ASL database.There is some coalescing - currently it is sent at most twice persecond - but there is no documented guarantee about this. In anycase, there may be multiple messages per notification.Notify notifications don't carry any payload, so we need to searchfor the messages.*/int notifyToken = 0; // Can be used to unregister with notify_cancel().notify_register_dispatch(kNotifyASLDBUpdate, ¬ifyToken, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(int token){// At least one message has been posted; build a search query.@autoreleasepool{aslmsg query = asl_new(ASL_TYPE_QUERY);char stringValue[64];if (lastSeenID > 0) {//格式化一个64位的字符串snprintf(stringValue, sizeof stringValue, "%llu", lastSeenID);//进行查找 - 按 > seenID 查找asl_set_query(query, ASL_KEY_MSG_ID, stringValue, ASL_QUERY_OP_GREATER | ASL_QUERY_OP_NUMERIC);} else {//时间查找snprintf(stringValue, sizeof stringValue, "%llu", startTime);asl_set_query(query, ASL_KEY_TIME, stringValue, ASL_QUERY_OP_GREATER_EQUAL | ASL_QUERY_OP_NUMERIC);}[self configureAslQuery:query];// Iterate over new messages.aslmsg msg;aslresponse response = asl_search(NULL, query);while ((msg = asl_next(response))){[self aslMessageReceived:msg];// Keep track of which messages we've seen.lastSeenID = (unsigned long long)atoll(asl_get(msg, ASL_KEY_MSG_ID));}asl_release(response);asl_free(query);if (_cancel) {notify_cancel(token);return;}}});}
}
timeval
timeval
表示时间的一个结构体,
struct timeval {long tv_sec; /* 秒 */long tv_usec; /* 毫秒 */
};
通过gettimeofday(&timeval, NULL);
能够获得当前系统时间
notify_register_dispatch
用于注册进程间的系统通知,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"
通过/usr/include/notify_keys.h
文件可以查看更多相关通知内容。
aslMessageReceived
入参是aslmsg
类型,aslget
将其转为char字符串类型后,再转为NSString
NSString *message = @(messageCString);//这里获取秒和毫微秒(十亿分之一秒)const char* secondsCString = asl_get( msg, ASL_KEY_TIME );const char* nanoCString = asl_get( msg, ASL_KEY_TIME_NSEC );NSTimeInterval seconds = secondsCString ? strtod(secondsCString, NULL) : [NSDate timeIntervalSinceReferenceDate] - NSTimeIntervalSince1970;double nanoSeconds = nanoCString? strtod(nanoCString, NULL) : 0;//1e9 = 1000000000NSTimeInterval totalSeconds = seconds + (nanoSeconds / 1e9);NSDate *timeStamp = [NSDate dateWithTimeIntervalSince1970:totalSeconds];//生成messageDDLogMessage *logMessage = [[DDLogMessage alloc]initWithMessage:messagelevel:_captureLevelflag:flagcontext:0file:@"DDASLLogCapture"function:nilline:0tag:niloptions:0timestamp:timeStamp];//记录到文件[DDLog log:async message:logMessage];
这里跟踪log:message:
方法最终也是通过写入日志到文件句柄
方式
NSFileHandle *handle = [self lt_currentLogFileHandle];
[handle seekToEndOfFile];
[handle writeData:data];
了解到其实现之后,我们就可以自己编写简单的ASL
日志捕获工具类,或者使用DDASLLogger
输出到ASL
,DDASLLogger
能够将DDLogMessage
转化为aslmsg
进行赋值之后输出到ASL
系统。
TTY 控制台输出
前面我们说到了日志重定向,目的是将本来写入到控制台的输出,转而写入到文件中。
NSLog
其实就是写入到文件syslog
中,既然要往文件中写,那么肯定就有文件的句柄了,C语言中,有3个句柄,也是我们进行重定向时用到的。
#define stdin __stdinp#define stdout __stdoutp#define stderr __stderrp
在iOS平台中,有以下3个:
#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
中写入,你可以使用c语言
的输出到文件的fprintf
来验证一下:
NSLog(@"ViewController viewDidLoad");
fprintf (stderr, "%s\n", "ViewController viewDidLoad222");
控制台可见输出为:
2016-06-15 12:57:17.286 TestNSlog[68073:1441419] ViewController viewDidLoad
ViewController viewDidLoad222
关于重定向的更多内容你可以查看 这篇博文
好了,这下说说TTY
的实现,要想实现控制台的输出,那么就输出到STDERR_FILENO
就行了,源码中也是这样的实现,值得注意的是,控制台的输出可以输出颜色,所以DDTTYLogger
实现中包含了很多颜色的处理,你以搭配CLIColor
来了解。
下面是TTYLogger
的输出部分:
// Write the log message to STDERRif (isFormatted) {// The log message has already been formatted.int iovec_len = (_automaticallyAppendNewlineForCustomFormatters) ? 5 : 4;struct iovec v[iovec_len];if (colorProfile) {v[0].iov_base = colorProfile->fgCode;v[0].iov_len = colorProfile->fgCodeLen;v[1].iov_base = colorProfile->bgCode;v[1].iov_len = colorProfile->bgCodeLen;v[iovec_len - 1].iov_base = colorProfile->resetCode;v[iovec_len - 1].iov_len = colorProfile->resetCodeLen;} else {v[0].iov_base = "";v[0].iov_len = 0;v[1].iov_base = "";v[1].iov_len = 0;v[iovec_len - 1].iov_base = "";v[iovec_len - 1].iov_len = 0;}v[2].iov_base = (char *)msg;v[2].iov_len = msgLen;if (iovec_len == 5) {v[3].iov_base = "\n";v[3].iov_len = (msg[msgLen] == '\n') ? 0 : 1;}writev(STDERR_FILENO, v, iovec_len);} else {// The log message is unformatted, so apply standard NSLog style formatting....}
os_log 新日志系统
iOS 10
之后的os_log
更为简单
常用接口有:
os_log_with_type
将特定日志级别的消息(如default、info、debug、error)发送到日志系统。os_log_debug
向日志系统发送调试级别消息。os_log_info
向日志系统发送信息级消息。os_log_error
向日志系统发送错误级消息。os_log_fault
向日志系统发送默认级别的消息。os_log
向日志系统发送一个默认级别的消息。与os_log_fault
一样
你可以看看 官方文档
- (void)logMessage:(DDLogMessage *)logMessage {// Skip captured log messagesif ([logMessage->_fileName isEqualToString:@"DDASLLogCapture"]) {return;}if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) {NSString * message = _logFormatter ? [_logFormatter formatLogMessage:logMessage] : logMessage->_message;if (message != nil) {const char *msg = [message UTF8String];__auto_type logger = [self logger];switch (logMessage->_flag) {case DDLogFlagError :os_log_error(logger, "%{public}s", msg);break;case DDLogFlagWarning:case DDLogFlagInfo :os_log_info(logger, "%{public}s", msg);break;case DDLogFlagDebug :case DDLogFlagVerbose:default :os_log_debug(logger, "%{public}s", msg);break;}}}
}
file logger 文件输出
文件logger
在CocoaLumberjack
算是比较重要的部分了,其实现全部在DDFileLogger
中
DDLogFileManager 文件输出协议
对于文件管理协议DDLogFileManager
,主要是下面的4个属性
maximumNumberOfLogFiles
要保存在磁盘上的归档日志文件的最大数量。如果这个属性设置为3
,将只保留3
个归档日志文件(加上当前活动的日志文件)在磁盘上,你可以设置0
将其禁用logFilesDiskQuota
日志占用的最大空间。在滚动日志文件时,所有超过logFilesDiskQuota
的旧日志文件都将被删除。你可以设置0
将其禁用logsDirectory
所有日志文件都放在logsDirectory
中。
如果没有指定特定的logsDirectory
,则使用默认目录。
在
Mac
上,这是在~/Library/Logs/<Application Name>
在
iPhone
上,这是在~/Library/Caches/Logs
.日志文件被命名为
<bundle identifier> <date> <time>.log
,例如 Example:com.organization.myapp 2013-12-03 17-14.log
存档的日志文件
会根据“maximumNumberOfLogFiles”
属性自动删除。
maximumFileSize
允许日志文件增长的最大大小(以字节为单位)。 如果日志文件大于这个值, 将会生成一个新的日志文件进行继续写入。rollingFrequency
滚日志文件的频率。 频率以NSTimeInterval
的形式给出,它是一个双精度浮点数,指定以秒为单位的间隔。 一旦日志文件变得这么旧,它就会被重新生成。例如10min = 60x10
就重新生成一个日志文件
您可以通过将“maximumFileSize
”设置为0来选择性地禁用由于文件大小而导致的滚动。 如果你这样做,滚动是完全基于“rollingFrequency
”。
您可以选择通过将“rollingFrequency
”设置为0(或任何非正数)来禁用由于时间而导致的滚动。 如果你这样做了,滚动仅仅是基于“maximumFileSize
”。
如果您同时禁用“maximumFileSize
”和“rollingFrequency
”,那么日志文件将永远不会被滚动。 这是强烈不鼓励的。
这些值默认值为
// maximumFileSize -> kDDDefaultLogMaxFileSize
// rollingFrequency -> kDDDefaultLogRollingFrequency
// maximumNumberOfLogFiles -> kDDDefaultLogMaxNumLogFiles
// logFilesDiskQuota -> kDDDefaultLogFilesDiskQuotaunsigned long long const kDDDefaultLogMaxFileSize = 1024 * 1024; // 1 MB
NSTimeInterval const kDDDefaultLogRollingFrequency = 60 * 60 * 24; // 24 Hours
NSUInteger const kDDDefaultLogMaxNumLogFiles = 5; // 5 Files
unsigned long long const kDDDefaultLogFilesDiskQuota = 20 * 1024 * 1024; // 20 MB
文件压缩
可以参考Demos
中的LogFileCompressor
,使用zlib
压缩成了gz
压缩文件。
具体你可以查看CompressingLogFileManager.m
的实现。
iOS开发-全量日志捕获CocoaLumberjack相关推荐
- ios开发中打印日志消息控制
问题 在ios项目开发中,项目发布时需要去掉NSLog消息,不然会非常影响性能,但是去掉NSLog是一件非常费事的事情 解决办法 在项目的目录Supporting Files->项目名称-Pre ...
- IOS 开发高手课 学习笔记(第二部分)
第二部分主要是性能监控相关 Part 7. 包大小:如何从资源和代码层面实现全方位瘦身? 官方 App Thinning App Thinning 是由苹果公司推出的一项可以改善 App 下载进程的新 ...
- 2018年最全iOS开发之第三方库
最全iOS开发之第三方库 最新增加 EAIntroView 一个灵活的介绍界面,可以用作引导页 UI 下拉刷新 EGOTableViewPullRefresh– 最早的下拉刷新控件. SVPullTo ...
- 史上最全iOS开发之第三方库整理汇总
UI 下拉刷新 EGOTableViewPullRefresh – 最早的下拉刷新控件. SVPullToRefresh – 下拉刷新控件. MJRefresh – 仅需一行代码就可以为UITable ...
- iOS:iOS开发非常全的三方库、插件、大牛博客等等
iOS开发非常全的三方库.插件.大牛博客等等 github排名:https://github.com/trending, github搜索:https://github.com/search. 此文章 ...
- ios非常全的库iOS开发 非常全的三方库、插件、大牛博客等等
转自: TimLiu-iOS Swift版本点击这里欢迎加入交QQ流群: 594119878 github排名 https://github.com/trending,github搜索:https:/ ...
- 互联网公司iOS开发工程师面试必看(最全知识点梳理)
序言 目前形势,参加到iOS队伍的人是越来越多,甚至已经到供过于求了.今年,找过工作人可能会更深刻地体会到今年的就业形势不容乐观,加之,培训机构一火车地向用人单位输送iOS开发人员,打破了生态圈的动态 ...
- MySQL数据库之全量+增量+二进制日志的备份与恢复
一.简介数据的备份与恢复 1.为什么备份? 灾难恢复:人为错误.硬件故障(冗余).软件故障(bug).自然灾害.黑客攻击.误操作.-: 测试: 2.备份时应该注意些什么? 能容忍最多丢失多少数据: 恢 ...
- iOS开发教程:Storyboard全解析-第二部分
如果你想了解更多Storyboard的特性,那么你就来对了地方,下面我们就来接着上次的内容详细讲解Storyboard的使用方法. 在上一篇<iOS开发教程:Storyboard全解析-第一部分 ...
最新文章
- 解题报告:luoguP2868 Sightseeing Cows G(最优比率环,负环判定,二分答案)
- SQLserver创建与主外键的看法
- springcloud的fallback与fallbackFactory
- 用python让excel飞起来 pdf_电脑卡?用u盘制作一个提速工具飞起来
- ITK:创建一个向量
- 概述 Linux系统扫描技术及安全防范
- 微信小程序 地图组件使用
- MONGODB 与sql聚合操作对应图
- 网络协议:传输层(http://java-mzd.iteye.com/blog/1007577)
- 超频电脑黑屏(超频失败怎么办)
- Ubuntu20.04(标题栏实时显示网速,cpu以及内存使用率)
- Lisp自动画梯形_CAD lisp 求助一段代码实现自动画弧!
- 键盘上那个字母代表w ndows,电脑键盘上各个键位作用
- c++ 制作走迷宫游戏
- c语言灵异事件之“字符串被吞”
- ckfinder java 源码_Ckeditor与Ckfinder(java)整合实现富媒体内容编辑(支持文件上传)
- 3G门户手机浏览器试用感受
- 以梦为马之89c51单片机精确1s时间LED灯闪烁(中断技术+定时/计数器技术)
- Java --- Xstream使用
- 软件测试之搜索框功能点用例梳理
热门文章
- 大数据框架图谱(总览)
- ARM视频 嵌入式linux培训班视频
- Windows和Linux系统下的共享文件夹配置
- 如何解决System.FormatException:“索引(从零开始)必须大于或等于零,且小于参数列表的大小。”类似错误?
- Python将png格式批量转成jpg格式,并批量用圆抠图
- Linux内核级木马与病毒攻防:基础工具介绍
- 20句简短含蓄的爱情名言
- TT100K/BDD100K数据集格式转换
- MXNET:set the environment variable MXNET_CUDNN_AUTOTUNE_DEFAULT to 0 to disable
- HTML5 DIV+CSS综合运用