你的日志打印对了么?

码林苔藓
• 阅读 2381

一、前言

日志不仅记录了程序的执行过程,同时也是分析问题的一种重要手段。

对于神策分析 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...

文章来源:公众号-神策技术社区

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
ELK学习
   大型网站遇到性能瓶颈或发生故障时,分析日志往往是发现问题根源最有效的手段。传统的日志分析手段不外乎以下几类:1\.运维人员用脚本grep,分析再汇总2\.通过流式计算引擎,storm/spark实时产生汇总数据,供监控分析3\.将数据堆放到HDFS,之后通过map/reduce定期做批量分析一个完整的集中式日志系统,需要包
Wesley13 Wesley13
3年前
IP被攻击了怎么办
1、断开所有网络连接。服务器之所以被攻击是因为连接在网络上,因此在确认系统遭受攻击后,第一步一定要断开网络连接,即断开攻击。2、根据日志查找攻击者。根据系统日志进行分析,查看所有可疑的信息进行排查,寻找出攻击者。3、根据日志分析系统。根据系统日志进行分析,查看攻击者是通过什么方式入侵到服务器的,通过
Stella981 Stella981
3年前
Linux日志安全分析技巧
0x00前言我正在整理一个项目,收集和汇总了一些应急响应案例(不断更新中)。GitHub地址:https://github.com/Bypass007/EmergencyResponseNotes本文主要介绍Linux日志分析的技巧,更多详细信息请访问Github地址,欢迎Star。0x01日志简介Lin
Stella981 Stella981
3年前
Linux应急响应(二):捕捉短连接
0x00前言​短连接(shortconnnection)是相对于长连接而言的概念,指的是在数据传送过程中,只在需要发送数据时,才去建立一个连接,数据发送完成后,则断开此连接,即每次连接只完成一项业务的发送。在系统维护中,一般很难去察觉,需要借助网络安全设备或者抓包分析,才能够去发现。0x01应急场景​
分布式日志追踪ID实战 | 京东物流技术团队
本文通过介绍分布式应用下各个场景的全局日志ID透传思路,以及介绍分布式日志追踪ID简单实现原理和实战效果,从而达到通过提高日志查询排查问题的效率。背景开发排查系统问题用得最多的手段就是查看系统日志,相信不少人都值过班当过小秘吧:给下接口和出入参吧,麻烦看看
京东云开发者 京东云开发者
9个月前
日志框架简介-Slf4j+Logback入门实践
作者:京东零售张洪前言随着互联网和大数据的迅猛发展,分布式日志系统和日志分析系统已广泛应用,几乎所有应用程序都使用各种日志框架记录程序运行信息。因此,作为工程师,了解主流的日志记录框架非常重要。虽然应用程序的运行结果不受日志的有无影响,但没有日志的应用程序
京东云开发者 京东云开发者
7个月前
分布式日志追踪ID实战
作者:京东物流张小龙本文通过介绍分布式应用下各个场景的全局日志ID透传思路,以及介绍分布式日志追踪ID简单实现原理和实战效果,从而达到通过提高日志查询排查问题的效率。背景开发排查系统问题用得最多的手段就是查看系统日志,相信不少人都值过班当过小秘吧:给下接口
日志框架简介-Slf4j+Logback入门实践 | 京东云技术团队
前言随着互联网和大数据的迅猛发展,分布式日志系统和日志分析系统已广泛应用,几乎所有应用程序都使用各种日志框架记录程序运行信息。因此,作为工程师,了解主流的日志记录框架非常重要。虽然应用程序的运行结果不受日志的有无影响,但没有日志的应用程序是不完整的,甚至可
码林苔藓
码林苔藓
Lv1
雁来音信无凭,路遥归梦难成。
文章
5
粉丝
0
获赞
0