一、前言
日志不仅记录了程序的执行过程,同时也是剖析问题的一种重要伎俩。
对于神策剖析 iOS SDK 而言,通过日志零碎岂但能够理解到 SDK 的行为,而且便于咱们排查问题。因而,日志零碎是 SDK 中必不可少的一项性能。
上面针对神策剖析 iOS SDK 日志零碎进行解析,心愿可能给大家提供一些参考。
二、日志打印形式
对于 iOS 开发而言,在控制台打印日志的罕用形式有 NSLog 和 printf,咱们先来看一下两者的区别。
2.1NSLog
NSLog 是 Foundation 框架提供的日志输入函数,能够在控制台进行格式化输入。
日志的内容会主动蕴含一些零碎信息,例如:项目名称、工夫等。另外,NSLog 还能够打印 OC 中的对象,并且输入的内容会主动换行。示例代码如下:
// 代码 NSString * a = @”Hello World!”;
NSLog(@” 这里是第一个 %@”, a);
NSLog(@” 这里是第二个 %@”, a);
/** 控制台日志:
2021-05-17 14:45:01.550403+0800 use-sdk[4608:164047] 这里是第一个 Hello World!
2021-05-17 14:45:01.550498+0800 use-sdk[4608:164047] 这里是第二个 Hello World!
*/
2.2printf
printf 只能打印 char 类型,并且内容不会主动换行,须要咱们手动操作 [1]。示例代码如下:
// 代码
printf(“Hello World!!!\nHello World!!!\nHello World!!!”);
/**
Hello World!!!
Hello World!!!
Hello World!!!
*/
2.3 小结
通过后面的介绍咱们不难看出,在 iOS 平台上通过 NSLog 输入日志更有劣势。既然如此,神策为什么不选用 NSLog 来进行日志输入呢?次要起因如下:
1、NSLog 只有简略的控制台打印输出,没有其余的输入形式;
2、NSLog 打印格局绝对繁多,不够灵便,又无奈自定义和扩大;
3、NSLog 打印效率不高,而且有长度限度;
4、如果客户重写了 NSLog,那么日志可能无奈打印到控制台,影响问题定位。
为了解决上述问题,神策开发了本人的日志零碎(SALog)来进行日志输入。
三、SALog 的实现
SALog 是一个日志零碎,具备较好的扩展性和易用性,上面咱们来看下 SALog 具体是如何实现的。
3.1 原理简介
目前 SALog 中最重要的性能就是在控制台输入日志(通过子类 SAConsoleLogger 实现),SAConsoleLogger 的次要原理是:实例化一个 iovec 构造体来包容数据,而后通过 writev 函数来发送这些数据。
构造体 iovec 蕴含了 iov_base 和 iov_len 两个属性 [2],它们的含意如下:
iov_base 属性指向一个缓冲区,寄存将要发送的数据;
iov_len 属性记录了输入日志的长度。外围代码如下所示:
struct iovec dataBuffer[1];
dataBuffer[0].iov_base = msg;
dataBuffer[0].iov_len = messageLength;
writev(STDERR_FILENO, dataBuffer, 1);
3.2 整体设计
SALog 是一个日志零碎,既能够用于控制台日志的打印也能够输入到本地文件。这样的日志零碎有一套外围的设计标准:创立一个基类实现根本的逻辑,具体的模块通过继承基类并重写基类的办法实现性能。
这样的设计具备较好的扩展性与维护性,整体的 UML 如图 3-1 所示:
图 3-1 日志零碎的 UML 图
通过上图能够看到神策定义了 SALogger 协定,在基类 SAAbstractLogger 中实现了根本的逻辑。SAFileLogger(文件日志)、SAConsoleLogger(控制台日志)等日志模块都是通过继承 SAAbstractLogger 这个基类,在 – logMessage: 办法中实现各自的性能逻辑。外围代码如下所示:
// SAAbstractLogger.h@protocol SALogger <NSObject>
@required
(void)logMessage:(SALogMessage *)logMessage;
@end
@protocol SALogMessageFormatter <NSObject>
@required
(NSString )formattedLogMessage:(SALogMessage )logMessage;
@end
@interface SAAbstractLogger : NSObject <SALogger>
@property (nonatomic, assign) BOOL enableLog;@property (nonatomic, strong) dispatch_queue_t loggerQueue;
@end
// SAConsoleLogger.m
(void)logMessage:(SALogMessage *)logMessage {if (!self.enableLog) {return;}[super logMessage:logMessage];······}3.3 日志格局神策定义了 SALogMessageFormatter 协定,基于此协定可实现多种不同的 formatter,便于前期的保护和扩大。例如:SALoggerConsoleFormatter、SALoggerPrePostFixFormatter 等,能够标注不同色彩、前后缀、特定标识等。外围代码如下所示:
//SALoggerConsoleFormatter.m
(NSString )formattedLogMessage:(nonnull SALogMessage )logMessage {NSString prefixEmoji = @””;NSString levelString = @””;switch (logMessage.level) {case SALogLevelError:prefixEmoji = @”❌”;levelString = @”Error”;break;······default:break;}
······return [NSString stringWithFormat:@”%@ %@ %@ %@ %@ %@ line:%@ %@\n”, dateString, prefixEmoji, levelString, self.prefix, logMessage.fileName, logMessage.function, line, logMessage.message];}3.4 长度限度不同开发者的代码习惯是不同的,业务逻辑也是不同的,这就导致 SDK 采集的数据大小都是不确定的。如果开发人员想要查看采集的数据是否正确,就须要保障输入的日志信息是残缺的。为了保障采集的数据都能残缺打印,SALog 将低于 1024 4 Bytes 的日志数据存储在栈区,超过 1024 4 Bytes 的日志数据存储在堆区,以保障打印的完整性。外围代码如下所示:// SAConsoleLogger.m
(instancetype)init{self = [super init];if (self) {_maxStackSize = 1024 * 4;self.loggerQueue = dispatch_queue_create(“cn.sensorsdata.SAConsoleLoggerSerialQueue”, DISPATCH_QUEUE_SERIAL);}return self;}
(void)logMessage:(SALogMessage )logMessage {······BOOL useStack = messageLength < _maxStackSize;char messageStack[useStack ? (messageLength + 1) : 1];char msg = useStack ? messageStack : (char *)calloc(messageLength + 1, sizeof(char));
······
// free memory if not use stackif (!useStack) {free(msg);}}3.5 日志分级为了不便开发人员查看和筛选日志,SDK 通过设置枚举类型对日志等级进行划分,从高到低顺次为 Error、Warn、Info、Debug、Verbose,如下所示:
typedef NS_OPTIONS(NSUInteger, SALogLevel) {SALogLevelError = (1 << 0),SALogLevelWarn = (1 << 1),SALogLevelInfo = (1 << 2),SALogLevelDebug = (1 << 3),SALogLevelVerbose = (1 << 4)}; 另外,为了便于应用,SALog 通过宏定义的形式实现了不同等级的日志接口:
$$
#define SALogError(frmt, …) SENSORS_ANALYTICS_LOG_MACRO(YES, SALogLevelError, __PRETTY_FUNCTION__, 0, frmt, ##__VA_ARGS__)
#define SALogWarn(frmt, …) SENSORS_ANALYTICS_LOG_MACRO(YES, SALogLevelWarn, __PRETTY_FUNCTION__, 0, frmt, ##__VA_ARGS__)
#define SALogInfo(frmt, …) SENSORS_ANALYTICS_LOG_MACRO(YES, SALogLevelInfo, __PRETTY_FUNCTION__, 0, frmt, ##__VA_ARGS__)
#define SALogDebug(frmt, …) SENSORS_ANALYTICS_LOG_MACRO(YES, SALogLevelDebug, __PRETTY_FUNCTION__, 0, frmt, ##__VA_ARGS__)
#define SALogVerbose(frmt, …) SENSORS_ANALYTICS_LOG_MACRO(YES, SALogLevelVerbose, __PRETTY_FUNCTION__, 0, frmt, ##__VA_ARGS__)
$$
四、SALog 的应用
SALog 的接口设计非常敌对,应用起来也比拟不便。只须要在初始化 SDK 时,关上日志输入性能即可:
options.enableLog = YES; 而后在 Xcode 控制台中可筛选关键字进行查看:
埋点事件触发胜利时,SDK 会输入【track event】字段结尾的事件数据;埋点事件触发失败时,SDK 会输入相应的谬误起因;
事件数据上报胜利时,SDK 会输入【valid message】字段结尾的事件数据;
事件数据上报失败时,SDK 会输入【invalid message】字段结尾的事件数据并输入谬误起因。通常状况下,咱们只心愿在 DEBUG 模式下输入日志。此时,能够通过宏定义来解决此问题:
$$
#ifdef DEBUG
options.enableLog = YES;
#endif
$$
如上配置后,SDK 只会在 DEBUG 模式下输入日志,这样防止了线上利用因遗记关掉日志输入性能而产生的性能影响。
五、展望未来
通过 SALog 能够在管制台上输入各种各样的日志信息,给咱们排查线上问题带来了极大的便当。不过,SALog 并非完满的,还有一些改良的空间。
在理论应用的过程中,咱们收集了一些对于 SALog 的痛点:
1、以后没有对输入日志的级别进行管制,导致在开启日志的状况下,会将所有级别的日志进行输入;
2、无奈对于日志内容进行筛选;
3、目前只反对输入日志到控制台。
针对这些痛点,咱们也给出了相应的解决方案:
1、在 SDK 的配置项中减少字段,用于管制输入日志的级别;
2、针对日志内容减少含糊匹配的性能,实现对于日志内容的筛选;
3、减少日志输入的渠道,例如:文件系统、云端等。
目前,这些性能曾经在逐渐研发中,不久之后大家就能够体验这些性能。
六、总结
本文次要介绍了神策剖析 iOS SDK 日志零碎的实现和应用,并对日志零碎的将来进行了布局。心愿通过这篇文章,大家可能对日志零碎的实现和应用有更深刻的理解。
参考文献:
[1] https://www.cplusplus.com/ref…
[2] https://linux.die.net/man/2/w…
文章起源:公众号 - 神策技术社区