生产代码中是否不应使用NSLog()?

156

在这个网站上,我已经被告知了几次,但我想确保这是否确实是情况。

我希望能够在我的代码中随处添加NSLog函数调用,并且Xcode / gcc会在构建我的发布/分发版本时自动删除这些调用。

我应该避免使用这个吗?如果是这样,有什么替代方案是经验丰富的Objective-C程序员之间最常见的吗?


7
我知道这个问题现在已经很旧了,但是如果您还可以的话,我想把Marc Charbonneau的答案标记为被采纳的答案。我已经修改了我的答案以指向他的答案,但他的答案才是正确的。 - e.James
5
他说,在一个频繁的循环中使用NSLog()会极大地影响性能,他通过艰难的方式发现了这一点。 - willc2
12个回答

197

预处理器宏确实非常适用于调试。NSLog()没有问题,但是定义自己的日志记录函数并具有更好的功能非常简单。这是我使用的一个函数,它包括文件名和行号,以便更容易跟踪日志语句。

#define DEBUG_MODE

#ifdef DEBUG_MODE
    #define DebugLog( s, ... ) NSLog( @"<%p %@:(%d)> %@", self, [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#else
    #define DebugLog( s, ... ) 
#endif

我发现将整个语句放在前缀头文件中比单独建立一个文件更容易。如果你愿意,可以通过让DebugLog与普通的Objective-C对象相互作用来构建更复杂的日志记录系统。例如,您可以有一个日志记录类,该类写入其自己的日志文件(或数据库),并包括一个“优先级”参数,您可以在运行时设置,以便调试消息不显示在发布版本中,但错误消息是(如果这样做,您可以制作DebugLog(),WarningLog()等)。

哦,记住#define DEBUG_MODE可以在应用程序的不同位置重复使用。例如,在我的应用程序中,我使用它来禁用许可密钥检查,并仅在它之前运行某个日期时允许应用程序运行。这使我能够轻松地分发时间限制的、完全功能的测试版副本,而无需付出太多的努力。


8
非常棒的回答,我点赞了。我已经改变了我的回答以表明你的 #define 宏定义方法是可行的,我希望提问者能够选择您的回答作为最佳答案(我已给他留言)。之前我一直在使用一个虚拟函数,因为我不知道宏定义中可以使用省略号参数。学习了! - e.James
15
虽然你的"DEBUG_MODE"定义是很好的,但我建议使用个人前缀,比如称其为"JPM_DEBUG"或类似的名称。因为我经常遇到第三方代码也使用了DEBUG或DEBUG_MODE等类似的名称,有时该代码在DEBUG模式下将无法正常工作。如果你想打开第三方库的调试功能,你应该有意识地这样做。(当然,应该是库作者给他们的符号加上前缀,但许多C和C++框架并没有这样做,特别是对于此定义)。 - Rob Napier
1
有没有 Xcode 预定义的宏可以在配置设置为调试时启用?我不想在每个项目中手动设置这个预处理器宏。我们能否像以下伪代码那样做 #if XCODE_CONFIGURATION==DEBUG? - frankodwyer
2
这种方法会导致编译器在发布模式下产生虚假的“未使用变量”警告,因为日志记录语句使用中间变量仅用于计算要记录的值。如果您像我一样讨厌编译器警告,避免这种情况的最聪明方法是什么? - Jean-Denis Muys
1
+1 有一个小问题;在生产代码中使用NSLog()确实存在真正的风险。滥用NSLog()可能会导致显著的内存使用量(大量自动释放的字符串)和CPU周期消耗(大量解析、格式化和字节复制)。您绝对不希望在生产环境中承担这样的成本,尤其是在嵌入式设备上! - bbum
显示剩余5条评论

78

将这3行放在-prefix.pch文件的末尾:

#ifndef DEBUG
  #define NSLog(...) /* suppress NSLog when in release mode */
#endif

当您创建项目时,默认情况下在构建设置中定义了DEBUG,因此您不需要将任何内容定义到您的项目中。


2
目前最好的解决方案。您需要手动从XCode 6中添加prefix.pch。 - Teddy
我们在发布之前是否仍需要更改构建设置,即从调试模式切换到发布模式? - Jagdev Sendhav

25

NSLog调用可以留在生产代码中,但应仅在真正异常的情况下或希望记录到系统日志的信息时才存在。

过多地记录系统日志的应用程序会令人感到烦恼,并显得不专业。


14
抱歉,这会给谁留下不专业的印象?谁有可能会检查你发布的应用程序日志并根据此来评判你的专业水平?(明确一点,我完全同意在发布版本中不能保留大量的NSLog,但是我对“专业性”论点感到困惑。) - WendiKidd
4
其他开发人员会观察到你正在做的事情并感到烦恼。Android也存在类似问题,有些开发者非常糟糕。https://plus.google.com/110166527124367568225/posts/h4jK38n4XYR - Roger Binns

24

我无法在Marc Charbonneau的回答中进行评论,因此我将这个作为一个答案发布。

除了将宏添加到你的预编译头文件中之外,你还可以使用目标构建配置来控制定义(或不定义)DEBUG_MODE

如果选择"Debug"活动配置,DEBUG_MODE将被定义,宏将扩展为完整的NSLog定义。

选择"Release"活动配置将不定义DEBUG_MODE,并且你的NSLog会在发布版本中省略。

步骤:

  • 目标 > 获取信息
  • 生成选项卡
  • 搜索“PreProcessor Macros”(或GCC_PREPROCESSOR_DEFINITIONS)
  • 选择配置:Debug
  • 编辑此级别的定义
  • 添加DEBUG_MODE=1
  • 选择配置:Release
  • 确认在GCC_PREPROCESSOR_DEFINITIONS中未设置DEBUG_MODE

如果在定义中省略'='字符,你将从预处理器中得到一个错误

此外,在宏定义之前插入以下注释以提醒你DEBUG_MACRO的定义来自何处;)

// Target > Get Info > Build > GCC_PREPROCESSOR_DEFINITIONS
// Configuration = Release: <empty>
//               = Debug:   DEBUG_MODE=1

1
这是对问题的有价值的补充回答。它应该不仅仅是一条评论。 - morningstar
DEBUG_MODEDEBUG_MACRO 是不寻常的。我只在苹果网站上找到了一个有关 DEBUG_MACRO 的参考资料(http://www.opensource.apple.com/source/gm4/gm4-15/src/m4.h?txt)。也许更标准的选择是 DEBUGNDEBUGNDEBUG 被 POSIX 规定使用,而 DEBUG 则是惯例。 - jww
+1 是的,这是一篇旧帖子,但这正是重点所在... 在我的 Xcode 版本(4 年后),搜索 GCC_PREPROCESSOR_DEFINITIONS 返回了一些不同的语言。请考虑更新这个优秀的答案以提高清晰度。 - David

11

编辑:Marc Charbonneau发布的方法比这个更好,由sho提醒我注意到这一点。

我已删除了我的答案部分,该部分建议使用空函数在调试模式被禁用时禁用日志记录。处理自动预处理器宏的部分仍然相关,因此它仍然存在。我还编辑了预处理器宏的名称,使其更符合Marc Charbonneau的答案。


为实现Xcode中的自动(和期望的)行为:

在项目设置中,转到“Build”选项卡,选择“Debug”配置。找到“Preprocessor Macros”部分,并添加一个名为DEBUG_MODE的宏。

...

编辑:有关使用DEBUG_MODE宏启用和禁用日志记录的正确方法,请查看Marc Charbonneau的答案


7

我同意Matthew的说法。在生产代码中使用NSLog没有任何问题。实际上,它对用户可能有用。但如果你唯一使用NSLog的原因是为了帮助调试,那么在发布之前应该将其删除。

此外,由于您将此标记为iPhone问题,NSLog会占用资源,而iPhone的资源非常宝贵。如果您在iPhone上记录任何东西,则会从您的应用程序中夺走处理器时间。请明智地使用它。


4
简单的事实是,NSLog非常缓慢。
但为什么呢?为了回答这个问题,让我们先了解一下NSLog具体做了什么,然后再来看它是如何做到的。
NSLog到底是干什么的?
NSLog有两个作用:
1. 它将日志消息写入Apple System Logging (asl)设施。这使得日志消息可以显示在Console.app中。
2. 它还检查应用程序的stderr流是否将被发送到终端(例如当应用程序通过Xcode运行时)。如果是,则将日志消息写入stderr中(以便在Xcode控制台中显示)。
写入STDERR并不困难。这可以通过fprintf和stderr文件描述符引用来完成。但是asl呢?
我发现关于ASL的最好文档是Peter Hosey的一个由10部分组成的博客文章:链接 不详细展开,与性能有关的亮点是:
要向ASL设施发送日志消息,你基本上需要打开一个客户端到ASL守护进程的连接并发送消息。但是,每个线程必须使用单独的客户端连接。因此,为了线程安全,每次调用NSLog时,它都会打开一个新的asl客户端连接,发送消息,然后关闭连接。
有关资源,请参见此处此处

已编辑文本。资源只需要放在页脚中。 - Johan Karlsson

2
从安全角度来看,这取决于记录了什么。如果NSLog(或其他记录器)正在写入敏感信息,则应在生产代码中删除记录器。
从审计角度来看,审计员不想查看每个NSLog的使用情况以确保其未记录敏感信息。他/她只会告诉你删除记录器。
我与两个团队合作。我们审核代码,编写编码指南等。我们的指南要求在生产代码中禁用日志记录。所以内部团队知道不要尝试它 ;)
我们还将拒绝在生产中记录日志的外部应用程序,因为我们不希望承担意外泄露敏感信息的风险。我们不关心开发人员告诉我们什么。调查这个问题根本不值得我们的时间。
记住,我们定义“敏感”,而不是开发人员 ;)
我还认为,执行大量日志记录的应用程序是一个即将崩溃的应用程序。有很多日志记录需要执行/需要执行的原因,通常不是稳定性。它与重启挂起服务的“看门狗”线程齐名。
如果您从未经历过安全架构(SecArch)审查,则这些是我们考虑的事项。

2
如其他回答中所述,您可以使用 #define 来在编译时更改是否使用 NSLog。但是,更灵活的方式是使用类似 Cocoa Lumberjack 的日志记录库,使您能够在运行时更改是否记录某些内容。

在您的代码中用 DDLogVerbose 或 DDLogError 等替换 NSLog,添加宏定义等 #import 并设置记录器,通常在 applicationDidFinishLaunching 方法中进行。

要达到与 NSLog 相同的效果,配置代码如下:

[DDLog addLogger:[DDASLLogger sharedInstance]];
[DDLog addLogger:[DDTTYLogger sharedInstance]];

1

在发布代码中,不应该无谓地使用printf或NSLog。只有当应用程序发生问题(例如,不可恢复的错误)时,请尝试仅使用printf或NSLog。


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