2019独角兽企业重金招聘Python工程师标准>>>

先说下需求,我理想中的 Log 系统需要:

  1. 可以设定 Log 等级

  2. 可以积攒到一定量的 log 后,一次性发送给服务器,绝对不能打一个 Log 就发一次

  3. 可以一定时间后,将未发送的 log 发送到服务器

  4. 可以在 App 切入后台时将未发送的 log 发送到服务器

其他一些需求,比如可以远程设定发送 log 的等级阀值,还有阀值的有效期等,和本文无关就不写了。

开始动手前,先了解下 CocoaLumberjack 是什么:

CocoaLumberjack 最早是由 Robbie Hanson 开发的日志库,可以在 iOS 和 MacOSX 开发上使用。其简单,快读,强大又不失灵活。它自带了几种log方式,分别是:

  • DDASLLogger 将 log 发送给苹果服务器,之后在 Console.app 中可以查看

  • DDTTYLogger 将 log 发送给 Xcode 的控制台

  • DDFileLogger 讲 log 写入本地文件

CocoaLumberjack 打一个 log 的流程大概就是这样的:

所有的 log 都会发给 DDLog 对象,其运行在自己的一个GCD队列(GlobalLoggingQueue),之后,DDLog 会将 log 分发给其下注册的一个或多个 Logger,这步在多核下是并发的,效率很高。每个 Logger 处理收到的 log 也是在它们自己的 GCD队列下(loggingQueue)做的,它们询问其下的 Formatter,获取 Log 消息格式,然后最终根据 Logger 的逻辑,将 log 消息分发到不同的地方。

因为一个 DDLog 可以把 log 分发到所有其下注册的 Logger 下,也就是说一个 log 可以同时打到控制台,打到远程服务器,打到本地文件,相当灵活。

CocoaLumberjack 支持 Log 等级:

typedef NS_OPTIONS(NSUInteger, DDLogFlag) {DDLogFlagError      = (1 << 0), // 0...00001DDLogFlagWarning    = (1 << 1), // 0...00010DDLogFlagInfo       = (1 << 2), // 0...00100DDLogFlagDebug      = (1 << 3), // 0...01000DDLogFlagVerbose    = (1 << 4)  // 0...10000};typedef NS_ENUM(NSUInteger, DDLogLevel) {DDLogLevelOff       = 0,DDLogLevelError     = (DDLogFlagError),                       // 0...00001DDLogLevelWarning   = (DDLogLevelError   | DDLogFlagWarning), // 0...00011DDLogLevelInfo      = (DDLogLevelWarning | DDLogFlagInfo),    // 0...00111DDLogLevelDebug     = (DDLogLevelInfo    | DDLogFlagDebug),   // 0...01111DDLogLevelVerbose   = (DDLogLevelDebug   | DDLogFlagVerbose), // 0...11111DDLogLevelAll       = NSUIntegerMax                           // 1111....11111 (DDLogLevelVerbose plus any other flags)};

DDLogLevel 定义了全局的 log 等级,DDLogFlag 是我们打 log 时设定的 log 等级,CocoaLumberjack 会比较两者,如果 flag 低于 level,则不会打 log:

#define LOG_MAYBE(async, lvl, flg, ctx, tag, fnct, frmt, ...) \        do { if(lvl & flg) LOG_MACRO(async, lvl, flg, ctx, tag, fnct, frmt, ##__VA_ARGS__); } while(0)

DDLogger 协议定义了 logger 对象需要遵从的方法和变量,为了方便使用,其提供了 DDAbstractLogger 对象,我们只需要继承该对象就可以自定义自己的 logger。对于第二点和第三点需求,我们可以利用 DDAbstractDatabaseLogger,其也是继承自 DDAbstractLogger,并在其上定义了 saveThreshold, saveInterval 等控制参数。这个 logger 本身是针对写入数据库的 log 设计的,我们也可以利用它这几个参数,实现我们上面所提的需求的第二和第三点。

对于第二点,设定 _saveThreshold 值即可,比如如果希望积攒1000条 log 再一次性发送,就赋值 1000.
对于第三点,设定 _saveInterval,比如如果希望每分钟发送一次,就设定 60.

由此,CocoaLumberjack 已经实现了需求中的 1、2、3 点,我们要做的无非是自定义 Logger 和 Formatter,将 log 的最终去处改为发送到我们自己的服务器中。

而第四点,我们可以监听 UIApplicationWillResignActiveNotification 事件,当触发时,手动调用 logger 的 db_save 方法,发送数据给服务器。

废话了半天,现在看下实现。

首先我们设定 log 的消息结构。自定义一个 LogFormatter, 遵从 DDLogFormatter 协议,我们需要重写 formatLogMessage 这个方法,这个方法返回值是 NSString,就是最终 log 的消息体字符串。而输入参数 logMessage 是由 logger 发的一个 DDLogMessage 对象,包含了一些必要的信息:

@interface DDLogMessage : NSObject <NSCopying>{// Direct accessors to be used only for performance@publicNSString *_message;DDLogLevel _level;DDLogFlag _flag;NSUInteger _context;NSString *_file;NSString *_fileName;NSString *_function;NSUInteger _line;id _tag;DDLogMessageOptions _options;NSDate *_timestamp;NSString *_threadID;NSString *_threadName;NSString *_queueLabel;}

可以利用这些信息构建自己的 log 消息体。比如我们这里只需要 log 所在文件名,行数还有所在函数名,则可以这样写:

- (NSString *)formatLogMessage:(DDLogMessage *)logMessage{NSMutableDictionary *logDict = [NSMutableDictionary dictionary];//取得文件名NSString *locationString;NSArray *parts = [logMessage->_file componentsSeparatedByString:@"/"];if ([parts count] > 0)locationString = [parts lastObject];if ([locationString length] == 0)locationString = @"No file";//这里的格式: {"location":"myfile.m:120(void a::sub(int)"}, 文件名,行数和函数名是用的编译器宏 __FILE__, __LINE__, __PRETTY_FUNCTION__logDict[@"location"] = [NSString stringWithFormat:@"%@:%lu(%@)", locationString, (unsigned long)logMessage->_line, logMessage->_function]//尝试将logDict内容转为字符串,其实这里可以直接构造字符串,但真实项目中,肯定需要很多其他的信息,不可能仅仅文件名、行数和函数名就够了的。NSError *error;NSData *outputJson = [NSJSONSerialization dataWithJSONObject:logfields options:0 error:&error];if (error)return @"{\"location\":\"error\"}"NSString *jsonString = [[NSString alloc] initWithData:outputJson encoding:NSUTF8StringEncoding];if (jsonString)return jsonString;return @"{\"location\":\"error\"}"}

接下来自定义 logger,其继承自 DDAbstractDatabaseLogger。在初始化方法中,先设定好一些参数,以及添加一个UIApplicationWillResignActiveNotification的观察者,用以实现第四个需求。

- (instancetype)init {self = [super init];if (self) {self.deleteInterval = 0;self.maxAge = 0;self.deleteOnEverySave = NO;self.saveInterval = 60;self.saveThreshold = 500;//别忘了在 dealloc 里 removeObserver[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(saveOnSuspend)name:@"UIApplicationWillResignActiveNotification"object:nil];}return self;}- (void)saveOnSuspend {dispatch_async(_loggerQueue, ^{[self db_save];});}

每次打 log 时,db_log: 会被调用,我们在这个函数里,将 log 发给 formatter,将返回的 log 消息体字符串保存在缓冲中。 db_log 的返回值告诉 DDLog 该条 log 是否成功保存进缓存。

- (BOOL)db_log:(DDLogMessage *)logMessage{if (!_logFormatter) {//没有指定 formatterreturn NO;}if (!_logMessagesArray)_logMessagesArray = [NSMutableArray arrayWithCapacity:500]; // 我们的saveThreshold只有500,所以一般情况下够了if ([_logMessagesArray count] > 2000) {// 如果段时间内进入大量log,并且迟迟发不到服务器上,我们可以判断哪里出了问题,在这之后的 log 暂时不处理了。// 但我们依然要告诉 DDLog 这个存进去了。return YES;}//利用 formatter 得到消息字符串,添加到缓存[_logMessagesArray addObject:[_logFormatter formatLogMessage:logMessage]];return YES;}

当1分钟或者未写入 log 数达到 500 时, db_save 就会被调用,我们在这里,将缓存的数据上传到自己的服务器。

- (void)db_save{//判断是否在 logger 自己的GCD队列中if (![self isOnInternalLoggerQueue])NSAssert(NO, @"db_saveAndDelete should only be executed on the internalLoggerQueue thread, if you're seeing this, your doing it wrong.");//如果缓存内没数据,啥也不做if ([_logMessagesArray count] == 0)return;获取缓存中所有数据,之后将缓存清空NSArray *oldLogMessagesArray = [_logMessagesArray copy];_logMessagesArray = [NSMutableArray arrayWithCapacity:0];//用换行符,把所有的数据拼成一个大字符串 NSString *logMessagesString = [oldLogMessagesArray componentsJoinedByString:@"\n"];//发送给咱自己服务器(自己实现了)[self post:logMessagesString];}

最后,我们需要在程序某处定义全局 log 等级(我这里使用 Info),并在 AppDelegate 的 didFinishLaunchingWithOptions 里初始化所有 Log 相关的东西:

static NSUInteger LOG_LEVEL_DEF = DDLogLevelInfo;- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{MyLogger *logger = [MyLogger new];[logger setLogFormatter:[MyLogFormatter new]];[DDLog addLogger:logger];//....}

然后就可以利用 DDLogError, DDLogWarning 等宏在程序中打 log 了。使用方法与 NSLog 一样。这几个宏的定义:

//注意,DDLogError 是肯定同步的#define DDLogError(frmt, ...) LOG_MAYBE(NO, LOG_LEVEL_DEF, DDLogFlagError, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)#define DDLogWarn(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagWarning, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)#define DDLogInfo(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagInfo, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)#define DDLogDebug(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagDebug, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)#define DDLogVerbose(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagVerbose, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)

最后感谢 CocoaLumberjack 的作者 Robbie Hanson ,如果你喜欢他开发的库,比如 XMPPFramework,别忘了帮他买杯啤酒哦~

转载于:https://my.oschina.net/u/1440723/blog/381812

利用 CocoaLumberjack 搭建自己的 Log 系统相关推荐

  1. 微信还能这么玩:Geek大学生利用微信搭建英语课堂互动系统,将社交参与性和实时性带入课堂

    微信还能这么玩:Geek大学生利用微信搭建英语课堂互动系统,将社交参与性和实时性带入课堂 小马哥不止一次说过,腾讯只会搭建微信的平台和规则,至于具体怎么玩还要靠大家发挥想象力.在见识了各种奇思妙想的微 ...

  2. cloudreve win10 解析域名_利用Cloudreve搭建自有网盘系统

    0. 前言 所需知识储备:了解服务器. 目录 1. 简介 2. 搭建及基础使用 2.1 Linux版搭建教程 2.1.1 安装LNMP运行环境 2.1.2 创建网站并上传Cloudreve 2.2 W ...

  3. 利用自己的电脑设置web服务器建网站_win7系统篇,win7系统利用iis搭建web服务器实现信息浏览资源共享的操作方法...

    很多小伙伴都遇到过对win7系统利用iis搭建web服务器实现信息浏览资源共享进行设置的困惑吧,一些朋友看过网上对win7系统利用iis搭建web服务器实现信息浏览资源共享设置的零散处理方法,并没有完 ...

  4. 我的NVIDIA开发者之旅——利用NVIDIA TAO工具包3.0和Deepstream快速搭建车辆信息识别系统

    利用NVIDIA TAO工具包3.0和Deepstream快速搭建车辆信息识别系统 实现目标 部署工具:NVIDIA DeepStream SDK 简单设置参数 工作流程 注意事项 GPU深度学习推理 ...

  5. win10系统如何配置web服务器,win10系统利用iis搭建web服务器的设置教程

    有关win10系统利用iis搭建web服务器的操作方法想必大家有所耳闻.但是能够对win10系统利用iis搭建web服务器进行实际操作的人却不多.其实解决win10系统利用iis搭建web服务器的问题 ...

  6. 利用chatgpt+低代码技术搭建进销存系统

    1 前言 在当今数字化时代,企业管理系统已经成为各行各业不可或缺的一部分.而进销存系统更是企业管理中的重要组成部分,它可以帮助企业实现产品库存管理.采购管理.销售管理等多个方面的自动化管理. 然而,搭 ...

  7. ZooKeeper1 利用虚拟机搭建自己的ZooKeeper集群

    前言:       前段时间自己参考网上的文章,梳理了一下基于分布式环境部署的业务系统在解决数据一致性问题上的方案,其中有一个方案是使用ZooKeeper,加之在大数据处理中,ZooKeeper确实起 ...

  8. 如何搭建量化投资研究系统?(工具篇之quantmod)

    "工欲善其事,必先利其器". 在 如何搭建量化投资研究系统?(数据篇)中,作者介绍了如何依靠网络爬虫收集整理交易数据,搭建一个金融数据库.在数据的问题基本解决之后,量化投资的研究工 ...

  9. 可视化搭建数据大屏系统的前端实现

    随着公司业务的发展,经常会收到一些数据大屏的需求.目前我司有两种实现方案,一是人肉搭建,二是用阿里云 DataV 搭建. 人肉搭建,在本地脚手架开发环境中进行编码,有大量的重复劳动,能力复用性差,占用 ...

最新文章

  1. 【知识星球】超3万字的网络结构解读,学习必备
  2. iview select 怎么清空_在使用iview时发现要先重置一下表单然后填写完后再重置可以清空Select多选框,否则清不掉,什么原因?...
  3. linux内存free低cache高,Linux之free命令buff/cache过高
  4. gulp + webpack 构建多页面前端项目 1
  5. Android Studio Problems
  6. matlab中“存储空间不足,无法处理此命令”
  7. C#LeetCode刷题之#290-单词模式(Word Pattern)
  8. tempdb相关文章
  9. ubuntu装机必备+主题美化
  10. 2021-08-08备份数据库
  11. 设计模式:卑微的代理模式
  12. Scratch案例——画长城
  13. 怎么给图片批量加边框
  14. matlab分数阶微分算子,数字图像处理以及数值运算中6种经典的分数阶微分掩模(分数阶微分算子)...
  15. erp系统云端服务器,erp系统软件云服务器
  16. tcp的time wait为什么要等2个MSL
  17. Visio 安装暴雷记录
  18. Day 10 - Anticipation | RIPS 2017 PHP代码安全审计挑战(RIPSTECH PRESENTS PHP SECURITY CALENDAR)/ Writeup
  19. 独上高楼 望尽天涯路
  20. 数据预处理之特征选择(Feature Selections Methods)

热门文章

  1. 创建python虚拟环境,安装django,创建一个django项目,在项目中创建一个应用(ubuntu16.04)...
  2. Java内存模型解析
  3. 完整的node脚手架搭建服务
  4. Tmux的安装、使用与配置
  5. thinkphp使用问题
  6. HDU-2072 单词数 水题一枚
  7. C#中模态对话框释放问题
  8. java线程基础知识
  9. dll动态库调用约定
  10. python输入两个正整数m和n用for循环求其最大公约数_输入两个正整数,m和n,求其最大公约数和最小公倍数。...