确实不应该在生产代码上使用NSLog()吗?


155

在这个站点上我曾多次被告知,但我想确保确实如此。

我期望能够在整个代码中散布NSLog函数调用,并且Xcode / gcc在构建我的Release / Distribution构建时会自动剥离这些调用。

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


7
我知道这个问题现在已经很老了,但是,如果您仍然可以,我会把马克·夏博诺的答案标记为已接受。我已经修改了答案以指向他,但是他的答案是正确的。
e.James 2009年

5
他说,在发现频繁的循环后,NSLog()绝对会破坏您的性能。
willc2

Answers:


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对象交互来构建更复杂的日志记录系统。例如,您可能有一个日志记录类,它会写入其自己的日志文件(或数据库),并包含一个可以在运行时设置的“ priority”参数,因此调试消息不会显示在您的发行版中,而错误消息是(如果执行此操作,则可以制作DebugLog(),WarningLog()等)。

哦,请记住#define DEBUG_MODE可以在应用程序的不同位置重复使用。例如,在我的应用程序中,我使用它禁用许可证密钥检查,并且仅在特定日期之前允许应用程序运行。这使我能够以最小的努力分发时间有限,功能齐全的beta副本。


8
+1是一个很好的答案。我更改了我的代码,以表明您可以使用#define宏,并且希望OP切换可接受的答案(我给他留下了评论)。我之所以使用虚拟函数,是因为我不知道您可以在宏中使用...参数。生活和学习!
e.James 2009年

15
尽管我建议在您的“ DEBUG_MODE”定义上使用个人前缀,例如将其称为“ JPM_DEBUG”等,但这是一个很好的答案。我经常遇到第三方代码,这些代码也使用DEBUG或DEBUG_MODE等,有时在DEBUG模式下该代码将无法正常工作。如果要打开第三方库调试,则应有意这样做。(当然,应该在库编写者之前加上其符号的前缀,但是很多C和C ++框架却不需要这样做,尤其是对于此定义)。
罗布·纳皮尔

1
是否存在一个Xcode预定义宏,仅当配置设置为debug时才可以将其打开?我宁愿不要自己在每个项目中手动设置此预处理器宏。我们可以做类似下面的伪代码#if XCODE_CONFIGURATION == DEBUG的事情吗?
frankodwyer 2009年

1
#include <TargetConditionals.h>
slf 2010年

2
当日志记录语句仅出于计算要记录的值的目的而使用中间变量时,此方法会在释放模式下导致编译器产生虚假的“未使用的变量”警告。如果您像我一样讨厌编译器警告,那么避免该问题的最聪明方法是什么?
Jean-Denis Muys 2010年

78

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

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

您无需在项目中定义任何内容,因为DEBUG在创建项目时默认情况下是在构建设置中定义的。


2
迄今为止最好的解决方案。您需要从XCode 6手动添加prefix.pch。–
Teddy

我们仍然需要在发布之前更改构建设置,即调试到发布
UserDev 2015年

25

NSLog调用可以保留在生产代码中,但仅在真正例外的情况下才应存在,或将需要的信息记录到系统日志中。

乱扔系统日志的应用程序很烦人,而且不专业。


14
抱歉-对谁不专业 谁会在发布的应用程序上检查您的日志并以此为基础判断您的专业水平?(要明确地说,我完全同意您不应该在应用程序的发行版中保留大量NSLogs,但我对“专业主义”的论点感到困惑。)
WendiKidd 2012年

4
其他开发人员会做您正在做的事情,并感到烦恼。Android也有类似的问题,有些开发人员的确很糟糕plus.google.com/110166527124367568225/posts/h4jK38n4XYR
Roger Binns

24

我无法评论Marc Charbonneau的答案,所以我将其发布为答案。

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

如果选择“ 调试 ”,DEBUG_MODE将定义活动配置,并且宏将展开为完整NSLog定义。

选择“ 发布 ”活动配置将不会定义,DEBUG_MODE并且发布版本中将NSLog省略您的ging。

脚步:

  • 目标>获取信息
  • 构建标签
  • 搜索“预处理器宏”(或GCC_PREPROCESSOR_DEFINITIONS
  • 选择配置:调试
  • 在此级别编辑定义
  • DEBUG_MODE=1
  • 选择配置:发布
  • 确认DEBUG_MODE未设置GCC_PREPROCESSOR_DEFINITIONS

如果您在定义中省略了'='字符,则会从预处理器中收到错误消息

另外,将此注释(如下所示)粘贴在宏定义上方,以提醒您DEBUG_MACRO定义来自何处;)

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

1
这是对该问题的宝贵补充答案。值得一提的不仅仅是评论。
上午星

DEBUG_MODE并且DEBUG_MACRO是非常规的。我DEBUG_MACRO在苹果网站(opensource.apple.com/source/gm4/gm4-15/src/m4.h?txt)上仅找到一个参考。也许更标准DEBUGNDEBUG将是更好的选择?NDEBUG由Posix指定;而DEBUG按惯例使用。
jww

+1是,这是一则古老的文章,但这就是重点...在我的Xcode版本(4年后)中,搜索GCC_PREPROCESSOR_DEFINITIONS会返回一些不同的语言。为了清楚起见,请考虑更新此出色的答案。
David

11

编辑:Marc Charbonneau发布的方法,由sho引起了我的注意,它远胜于此方法。

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


要在Xcode中实现自动(和预期的)行为:

在项目设置中,转到“构建”选项卡,然后选择“调试”配置。找到“预处理器宏”部分,然后添加一个名为的宏DEBUG_MODE

...

编辑:请参见Marc Charbonneau的答案,以了解启用和禁用DEBUG_MODE宏记录的正确方法。


7

我同意马修的观点。生产代码中的NSLog没有错。实际上,它对用户很有用。就是说,如果您使用NSLog的唯一原因是帮助调试,那么可以,在发布之前应将其删除。

此外,由于您已将此问题标记为iPhone问题,因此NSLog会占用资源,而这是iPhone所没有的。如果你NSLogging 东西在iPhone上,从您的应用程序带走处理器时间。明智地使用它。


4

简单的事实是,NSLog只是很慢。

但为什么?为了回答这个问题,让我们找出NSLog的功能,然后看看它是如何做到的。

NSLog到底做什么?

NSLog做两件事:

它将日志消息写入Apple系统日志记录(asl)工具。这允许日志消息显示在Console.app中。它还检查是否将应用程序的stderr流发送到终端(例如,通过Xcode运行应用程序时)。如果是这样,它将日志消息写入stderr(以便它显示在Xcode控制台中)。

写入STDERR听起来并不困难。可以使用fprintf和stderr文件描述符参考来完成。但是asl呢?

我发现的有关ASL的最佳文档是Peter Hosey撰写的10部分博客文章:链接

无需赘述,重点(涉及性能)如下:

要将日志消息发送到ASL设施,您基本上可以打开与ASL守护程序的客户端连接并发送消息。但是-每个线程必须使用单独的客户端连接。因此,为了确保线程安全,每次调用NSLog时,它都会打开一个新的asl客户端连接,发送消息,然后关闭该连接。

资源可以在这里这里找到。


编辑了文字。资源只需要放在页脚中。
约翰·卡尔森

2

如其他答案所述,您可以使用#define更改在编译时是否使用NSLog。

但是,更灵活的方法是使用可可伐木工人之类的日志记录库,该库允许您更改是否在运行时也记录了某些内容。

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

为了与NSLog具有相同的效果,配置代码为

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

2

从安全角度来看,这取决于要记录的内容。如果NSLog(或其他记录器)正在写入敏感信息,则应在生产代码中删除记录器。

从审核的角度来看,审核员不想查看每次使用NSLog以确保其不会记录敏感信息。他/她只会告诉您删除记录器。

我与两个小组一起工作。我们审核代码,编写编码指南等。我们的指南要求在生产代码中禁用日志记录。所以内部团队知道不要尝试;)

我们也将拒绝登录生产的外部应用程序,因为我们不希望承担与意外泄露敏感信息有关的风险。我们不在乎开发人员告诉我们什么。根本不值得我们花时间调查。

记住,我们定义的是“敏感的”,而不是开发人员;)

我也认为一个执行大量日志记录的应用程序可以立即崩溃。有一个原因是执行/需要执行大量日志记录,并且通常情况下不稳定。它带有“看门狗”线程,可以重新启动挂起的服务。

如果您从未经历过安全体系结构(SecArch)审查,那么这些就是我们要关注的事情。


1

在发行代码中,您不必对printf或NSLog过于冗长。如果应用程序发生了一些不好的情况,即IE无法恢复的错误,请尝试仅执行printf或NSLog。


1

请记住,NSLogs会减慢UI /主线程的速度。除非绝对必要,否则最好将它们从发行版本中删除。


0

我强烈建议使用TestFlight进行日志记录(免费)。他们的方法将覆盖NSLog(使用宏),并允许您打开/关闭对NSLog的所有现有调用的日志记录(它们的服务器),Apple System日志和STDERR日志。这样做的好处是,您仍然可以查看已部署到测试仪的应用程序和已部署到App Store的应用程序的日志消息,而日志不会显示在用户的系统日志中。两全其美的。


人们应该考虑TestFlight给应用程序增加的开销。是否可以仅添加TestFlight的日志记录部分?
Johan Karlsson
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.