添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

1. 前言

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

对于神策分析 iOS SDK 而言,通过日志系统不但可以了解到 SDK 的行为,而且便于我们排查问题。因此,日志系统是 SDK 中必不可少的一项功能。

下面针对神策分析 iOS SDK 日志系统进行解析,希望能够给大家提供一些参考。

2. 日志打印方式

对于 iOS 开发而言,在控制台打印日志的常用方式有 NSLog 和 printf,我们先来看一下两者的区别。

2.1. NSLog

NSLog 是 Foundation 框架提供的日志输出函数,可以在控制台进行格式化输出。

日志的内容会自动包含一些系统信息,例如:项目名称、时间等。另外,NSLog 还可以打印 OC 中的对象,并且输出的内容会自动换行。示例代码如下:

/**控制台日志:
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!

通过前面的介绍我们不难看出,在 iOS 平台上通过 NSLog 输出日志更有优势。既然如此,神策为什么不选用 NSLog 来进行日志输出呢?主要原因如下:

  • NSLog 只有简单的控制台打印输出,没有其他的输出方式;
  • NSLog 打印格式相对单一,不够灵活,又无法自定义和扩展;
  • NSLog 打印效率不高,而且有长度限制;
  • 如果客户重写了 NSLog,那么日志可能无法打印到控制台,影响问题定位。
  • 为了解决上述问题,神策开发了自己的日志系统(SALog)来进行日志输出。

    3. 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: 方法中实现各自的功能逻辑。核心代码如下所示:

    @interface SAAbstractLogger : NSObject <SALogger>
    @property (nonatomic, assign) BOOL enableLog;
    @property (nonatomic, strong) dispatch_queue_t loggerQueue;
    // SAConsoleLogger.m
    - (void)logMessage:(SALogMessage *)logMessage {
    if (!self.enableLog) {
    return ;
    [super logMessage:logMessage];
    ······
    //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];
    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 stack
    if (!useStack) {
    free (msg);
    typedef NS_OPTIONS(NSUInteger, SALogLevel) {
    SALogLevelError = (1 << 0),
    SALogLevelWarn = (1 << 1),
    SALogLevelInfo = (1 << 2),
    SALogLevelDebug = (1 << 3),
    SALogLevelVerbose = (1 << 4)
    #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__)
  • 埋点事件触发成功时,SDK 会输出 【track event】 字段开头的事件数据;
  • 埋点事件触发失败时,SDK 会输出相应的错误原因;
  • 事件数据上报成功时,SDK 会输出 【valid message】 字段开头的事件数据;
  • 事件数据上报失败时,SDK 会输出 【invalid message】 字段开头的事件数据并输出错误原因。
  • 通常情况下,我们只希望在 DEBUG 模式下输出日志。此时,可以通过宏定义来解决此问题:

    如上配置后,SDK 只会在 DEBUG 模式下输出日志,这样避免了线上应用因忘记关掉日志输出功能而产生的性能影响。

    5. 展望未来

    通过 SALog 可以在控制台上输出各种各样的日志信息,给我们排查线上问题带来了极大的便利。不过,SALog 并非完美的,还有一些改进的空间。

    在实际使用的过程中,我们收集了一些关于 SALog 的痛点:

  • 当前没有对输出日志的级别进行控制,导致在开启日志的情况下,会将所有级别的日志进行输出;
  • 无法对于日志内容进行筛选;
  • 目前只支持输出日志到控制台。
  • 针对这些痛点,我们也给出了相应的解决方案:

  • 在 SDK 的配置项中增加字段,用于控制输出日志的级别;
  • 针对日志内容增加模糊匹配的功能,实现对于日志内容的筛选;
  • 增加日志输出的渠道,例如:文件系统、云端等。
  • 目前,这些功能已经在逐步研发中,不久之后大家就可以体验这些功能。

    6. 总结

    本文主要介绍了神策分析 iOS SDK 日志系统的实现和使用,并对日志系统的未来进行了规划。希望通过这篇文章,大家能够对日志系统的实现和使用有更深入的了解。

    7. 参考文献

    [1] https://www.cplusplus.com/reference/cstdio/printf/

    [2] https://linux.die.net/man/2/writev

    By | 2021-07-08T15:20:01+08:00 7月 8th, 2021 | 开源博客精选 你的日志打印对了么? 已关闭评论