采用os_log API并保持向后兼容性

3
我正在尝试以一种方式向库中添加对新的日志记录和活动跟踪API的支持,以保持库的用户在尚未采用最新操作系统版本(iOS或macOS)的情况下的向后兼容性。我为每个日志级别定义了自定义日志宏,然后对于较旧的操作系统,回退到NSLog。我已经解决了这个问题,但还存在一个问题。
新的API要求您将任何非常量、非标量值明确标记为public,如果您希望它们显示在日志输出中。这是我的宏调用的样子:
UZKLogInfo("Reading file %{public}@ from archive", fileName);

这段代码使用包含os_log的SDK(例如iOS 10.0或更高版本)编译没有问题,但是当我使用早期版本编译时,我的宏将退回到NSLog,就会收到编译器警告:

os_log()/os_trace()之外使用“public”格式说明符注释

并且打印的日志行如下:

Reading file <decode: missing data> from archive

这是我宏定义的简化版本(仅包括info定义并简化条件语句):
#if UNIFIED_LOGGING_SUPPORTED
    @import os.log;

    #define UZKLogInfo(format, ...) os_log_info(OS_LOG_DEFAULT, format, ##__VA_ARGS__);
#else // Fall back to regular NSLog
    #define UZKLogInfo(format, ...) NSLog(@format, ##__VA_ARGS__);
#endif

有没有什么方法可以从回退情况下的format中去掉"{public}"文本(一种字符串替换?)?或者还有其他方法可以支持旧API和新API而不放弃日志中一直显示的信息级别?根据去年关于这个主题的WWDC会议,我需要使用一个宏,否则我将失去调用站点元数据。

“某种字符串替换”听起来像是你已经回答了自己的问题。你看过NSString提供的方法吗? - CRD
@CRD 但我想替换的不是 NSString,而是从源文件中放入宏的文本 - 它在内存中并没有被分配成一个对象。我不确定预处理器有哪些工具可用,但我认为我无法利用 NSString 上的方法。 - Dov
1
你的@format是一个NSString,不管这个字符串是程序生成的还是字面量,它仍然是一个NSString。你可能无法在编译期间修改字符串,但可以在运行时进行更改。你的宏包括对NSLog的调用,它可以包括一个方法调用。 - CRD
@CRD 现在我明白了你的建议,但是我当时正在寻找一个编译时解决方案。不过,我认为作为最后的办法,添加 NSString 替换可能可行。 - Dov
编译时的解决方案是更改代码文件的文件扩展名,例如.myExt,并在Xcode中为.myExt添加构建规则。然后,构建规则将.myExt处理成.m,删除不需要的{public}出现,编译器再处理.m。构建规则是一个脚本,可以调用任何命令行应用程序等来完成其工作。Apple有(曾经有过?)一个示例,使用Ruby脚本预处理字符串文件,您可能可以进行适应。或者您可以在宏中包含NSString方法,运行时成本可能微不足道。 - CRD
1个回答

2

我选择在宏中使用NSString替换,并将抑制编译器警告作为其中的一部分,这样可以针对每一行而不是整个文件或项目进行操作。代码如下:

#if UNIFIED_LOGGING_SUPPORTED
    @import os.log;

    #define UZKLogInfo(format, ...) os_log_info(OS_LOG_DEFAULT, format, ##__VA_ARGS__);

#else // Fall back to regular NSLog

    #define _removeLogFormatTokens(format) [@format stringByReplacingOccurrencesOfString:@"{public}" withString:@""]
    #define _stringify(a) #a
    #define _nsLogWithoutWarnings(format, ...) \
        _Pragma( _stringify( clang diagnostic push ) ) \
        _Pragma( _stringify( clang diagnostic ignored "-Wformat-nonliteral" ) ) \
        _Pragma( _stringify( clang diagnostic ignored "-Wformat-security" ) ) \
        NSLog(_removeLogFormatTokens(format), ##__VA_ARGS__); \
        _Pragma( _stringify( clang diagnostic pop ) )

    #define UZKLogInfo(format, ...) _nsLogWithoutWarnings(format, ##__VA_ARGS__);
#endif

这被称为:

UZKLogInfo("Message: %@", anObjectToLog);

我也一直在考虑如何做这件事。谢谢分享。我正在考虑的一件事情是,与其删除旧版本中的日志格式,不如将{public}添加到iOS 10+的日志格式中。这样我就不必在已有的所有日志语句上进行查找和替换...因为我们的日志数据本来就不是“私有”的。我们会看看效果如何。 - manroe
我很好奇您的#if UNIFIED_LOGGING_SUPPORTED逻辑实际上是什么。但我不知道在库中如何在编译时进行此类检查,因为支持基于设备上的iOS版本,对吗? - manroe
@manroe 是的,我正在对所有平台的操作系统版本进行分组。您可以在此处查看:https://github.com/abbeycode/UnzipKit/commit/8cf48b84d4b674467e774f3d1844e4008fedc7ad#diff-9b62aea6b32c19c0cc4f2ac623b5d6a4R21 - Dov
谢谢提供链接。我猜这对你有效是因为你提供的头文件是公开的,所以__IPHONE_OS_VERSION_MIN_REQUIRED >= 100000会根据SDK用户的应用程序设置进行评估,而不是你的SDK构建设置。此外,如果应用程序支持iOS 8,则您将不支持在iOS 10设备上使用os_log。这正是我想要做的事情。 - manroe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接