文章目录

  • 前言
  • 日志重定向
  • Lumberjack组成
    • Capture 捕捉
    • Logger 输出
    • message and formatter 消息以及格式化
  • ASL 日志系统
  • TTY 控制台输出
  • os_log 新日志系统
  • file logger 文件输出
    • DDLogFileManager 文件输出协议
    • 文件压缩

前言

全量日志就是app的运行日志打印等等。有时候光凭Crash日志并不能找到并解决问题,如果有CrashApp的日志输出,则会事半功倍。

CocoaLumberjackOSXiOS平台优秀的全量日志抓取第三方库。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 捕捉

DDASLLogCaptureCocoaLumberjack中唯一的一个Capture类,用与捕获ASL日志

Logger 输出

logger用于输出日志,有

  • DDASLLogger 用于输出到ASL
  • DDOSLoggeriOS 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, &notifyToken, 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输出到ASLDDASLLogger能够将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 文件输出

文件loggerCocoaLumberjack算是比较重要的部分了,其实现全部在DDFileLogger

DDLogFileManager 文件输出协议

对于文件管理协议DDLogFileManager,主要是下面的4个属性

  • maximumNumberOfLogFiles 要保存在磁盘上的归档日志文件的最大数量。如果这个属性设置为3,将只保留3个归档日志文件(加上当前活动的日志文件)在磁盘上,你可以设置0将其禁用

  • logFilesDiskQuota 日志占用的最大空间。在滚动日志文件时,所有超过logFilesDiskQuota的旧日志文件都将被删除。你可以设置0将其禁用

  • logsDirectory
    所有日志文件都放在logsDirectory中。
    如果没有指定特定的logsDirectory,则使用默认目录。

  1. Mac上,这是在~/Library/Logs/<Application Name>

  2. 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相关推荐

  1. ios开发中打印日志消息控制

    问题 在ios项目开发中,项目发布时需要去掉NSLog消息,不然会非常影响性能,但是去掉NSLog是一件非常费事的事情 解决办法 在项目的目录Supporting Files->项目名称-Pre ...

  2. IOS 开发高手课 学习笔记(第二部分)

    第二部分主要是性能监控相关 Part 7. 包大小:如何从资源和代码层面实现全方位瘦身? 官方 App Thinning App Thinning 是由苹果公司推出的一项可以改善 App 下载进程的新 ...

  3. 2018年最全iOS开发之第三方库

    最全iOS开发之第三方库 最新增加 EAIntroView 一个灵活的介绍界面,可以用作引导页 UI 下拉刷新 EGOTableViewPullRefresh– 最早的下拉刷新控件. SVPullTo ...

  4. 史上最全iOS开发之第三方库整理汇总

    UI 下拉刷新 EGOTableViewPullRefresh – 最早的下拉刷新控件. SVPullToRefresh – 下拉刷新控件. MJRefresh – 仅需一行代码就可以为UITable ...

  5. iOS:iOS开发非常全的三方库、插件、大牛博客等等

    iOS开发非常全的三方库.插件.大牛博客等等 github排名:https://github.com/trending, github搜索:https://github.com/search. 此文章 ...

  6. ios非常全的库iOS开发 非常全的三方库、插件、大牛博客等等

    转自: TimLiu-iOS Swift版本点击这里欢迎加入交QQ流群: 594119878 github排名 https://github.com/trending,github搜索:https:/ ...

  7. 互联网公司iOS开发工程师面试必看(最全知识点梳理)

    序言 目前形势,参加到iOS队伍的人是越来越多,甚至已经到供过于求了.今年,找过工作人可能会更深刻地体会到今年的就业形势不容乐观,加之,培训机构一火车地向用人单位输送iOS开发人员,打破了生态圈的动态 ...

  8. MySQL数据库之全量+增量+二进制日志的备份与恢复

    一.简介数据的备份与恢复 1.为什么备份? 灾难恢复:人为错误.硬件故障(冗余).软件故障(bug).自然灾害.黑客攻击.误操作.-: 测试: 2.备份时应该注意些什么? 能容忍最多丢失多少数据: 恢 ...

  9. iOS开发教程:Storyboard全解析-第二部分

    如果你想了解更多Storyboard的特性,那么你就来对了地方,下面我们就来接着上次的内容详细讲解Storyboard的使用方法. 在上一篇<iOS开发教程:Storyboard全解析-第一部分 ...

最新文章

  1. 解题报告:luoguP2868 Sightseeing Cows G(最优比率环,负环判定,二分答案)
  2. SQLserver创建与主外键的看法
  3. springcloud的fallback与fallbackFactory
  4. 用python让excel飞起来 pdf_电脑卡?用u盘制作一个提速工具飞起来
  5. ITK:创建一个向量
  6. 概述 Linux系统扫描技术及安全防范
  7. 微信小程序 地图组件使用
  8. MONGODB 与sql聚合操作对应图
  9. 网络协议:传输层(http://java-mzd.iteye.com/blog/1007577)
  10. 超频电脑黑屏(超频失败怎么办)
  11. Ubuntu20.04(标题栏实时显示网速,cpu以及内存使用率)
  12. Lisp自动画梯形_CAD lisp 求助一段代码实现自动画弧!
  13. 键盘上那个字母代表w ndows,电脑键盘上各个键位作用
  14. c++ 制作走迷宫游戏
  15. c语言灵异事件之“字符串被吞”
  16. ckfinder java 源码_Ckeditor与Ckfinder(java)整合实现富媒体内容编辑(支持文件上传)
  17. 3G门户手机浏览器试用感受
  18. 以梦为马之89c51单片机精确1s时间LED灯闪烁(中断技术+定时/计数器技术)
  19. Java --- Xstream使用
  20. 软件测试之搜索框功能点用例梳理

热门文章

  1. 大数据框架图谱(总览)
  2. ARM视频 嵌入式linux培训班视频
  3. Windows和Linux系统下的共享文件夹配置
  4. 如何解决System.FormatException:“索引(从零开始)必须大于或等于零,且小于参数列表的大小。”类似错误?
  5. Python将png格式批量转成jpg格式,并批量用圆抠图
  6. Linux内核级木马与病毒攻防:基础工具介绍
  7. 20句简短含蓄的爱情名言
  8. TT100K/BDD100K数据集格式转换
  9. MXNET:set the environment variable MXNET_CUDNN_AUTOTUNE_DEFAULT to 0 to disable
  10. HTML5 DIV+CSS综合运用