如何禁用日志记录?

8
我有一个应用程序,正在使用由苹果提供的UITextChecker类。这个类有一个以下的错误:如果iOS设备处于离线状态(对于我的应用程序来说通常是如此),每次调用一些UITextChecker方法时,它会将以下内容记录到控制台:
2016-03-08 23:48:02.119 HereIsMyAppName [4524:469877] UITextChecker sent string:isExemptFromTextCheckerWithCompletionHandler: to com.apple.TextInput.rdt but received error Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service named com.apple.TextInput.rdt was invalidated."

我不希望这些信息在日志中被大量记录。有没有办法从代码中禁用日志记录?我想在调用任何UITextChecker方法之前禁用日志记录,然后在之后重新启用它。或者,是否有可能按类选择性地禁用日志记录(即使它是基础类而不是我的类)?还有其他解决方案吗?


2
我删除了我的回答。据我所知,无法短路NSLog,但愿有人能证明我是错的... - deadbeef
@deadbeef谢谢您的建议,以及删除“不可能”的答案。我认为您的反应是SO用户的榜样,但我希望您在这种情况下是错误的,并且有人会想出解决方案/变通方法。相反的情况对我来说将是非常糟糕的消息。 - Rasto
1
可能是 隐藏来自预编译库的 NSLog 输出 的重复问题。据我所知,没有办法在编译后的代码中(例如在苹果的库中)禁用 NSLog,重定向 stderr 似乎是唯一的解决方法。 - Martin R
@MartinR 重定向 stderr 对我来说可能是一个可行的解决方案(仅针对对 UITextChecker 方法的调用)。我有两个问题:1. 如果我在生产中进行了重定向,并且应用程序在重定向 stderr 的调用期间崩溃,苹果的崩溃日志是否包含导致崩溃的异常?2. 重定向的性能成本是多少?如果我理解正确,代码正在打开文件。在某些情况下,我需要每秒执行几次重定向,同时还要运行动画等。 - Rasto
我同意Martin R的观点。据我所知,没有办法有条件地关闭NSLog。话虽如此,这似乎是苹果API中的一个错误,如果我是你,我会提交一个radar。 - Danny Bravo
显示剩余3条评论
1个回答

11

警告:本答案使用了私有但在某种程度上已记录在案的Cocoa C函数_NSSetLogCStringFunction()_NSLogCStringFunction()

_NSSetLogCStringFunction()是由Apple创建的接口,用于处理NSLog的实现函数。它最初被暴露给WebObjects,以便在Windows机器上挂钩NSLog语句,但今天仍然存在于iOS和OS X APIs中。它在支持文章中有记录。

该函数接收一个函数指针,带有参数const char* message,即要记录的字符串,unsigned length,即消息的长度,以及一个BOOL withSysLogBanner,用于切换标准日志横幅。如果我们为记录创建自己的钩子函数,而不是像NSLog在后台调用fprintf,我们就可以有效地禁用应用程序的所有记录。

Objective-C示例(或具有桥接头文件的Swift):

extern void _NSSetLogCStringFunction(void(*)(const char*, unsigned, BOOL));

static void hookFunc(const char* message, unsigned length, BOOL withSysLogBanner) { /* Empty */ }

// Later in your application

_NSSetLogCStringFunction(hookFunc);

NSLog(@"Hello _NSSetLogCStringFunction!\n\n");  // observe this isn't logged

这个的具体实现可以在YILogHook中找到,它提供了一个接口来添加一系列代码块到任何NSLog语句中(写入文件等)。

纯Swift示例:

@asmname("_NSSetLogCStringFunction") // NOTE: As of Swift 2.2 @asmname has been renamed to @_silgen_name
func _NSSetLogCStringFunction(_: ((UnsafePointer<Int8>, UInt32, Bool) -> Void)) -> Void

func hookFunc(message: UnsafePointer<Int8>, _ length: UInt32, _ withSysLogBanner: Bool) -> Void { /* Empty */ }

_NSSetLogCStringFunction(hookFunc)

NSLog("Hello _NSSetLogCStringFunction!\n\n");  // observe this isn't logged

在Swift中,您还可以选择忽略所有块参数而不使用hookFunc,如下所示:
_NSSetLogCStringFunction { _,_,_ in }

使用Objective-C重新开启日志记录,只需将NULL作为函数指针传入:

_NSSetLogCStringFunction(NULL);

使用Swift时有些不同,因为如果我们尝试传递nilnil指针(在Swift中不提供NULL),编译器会抱怨类型不匹配。为了解决这个问题,我们需要访问另一个系统函数_NSLogCStringFunction,以获取默认日志记录实现的指针,并在禁用日志记录时保留该引用,在需要重新开启日志记录时将引用设置回来。
我通过添加NSLogCStringFunctypedef来简化了这个Swift实现:
/// Represents the C function signature used under-the-hood by NSLog
typealias NSLogCStringFunc = (UnsafePointer<Int8>, UInt32, Bool) -> Void

/// Sets the C function used by NSLog
@_silgen_name("_NSSetLogCStringFunction") // NOTE: As of Swift 2.2 @asmname has been renamed to @_silgen_name
func _NSSetLogCStringFunction(_: NSLogCStringFunc) -> Void

/// Retrieves the current C function used by NSLog
@_silgen_name("_NSLogCStringFunction")
func _NSLogCStringFunction() -> NSLogCStringFunc

let logFunc = _NSLogCStringFunction() // get function pointer to the system log function before we override it

_NSSetLogCStringFunction { (_, _, _) in } // set our own log function to do nothing in an anonymous closure

NSLog("Observe this isn't logged.");

_NSSetLogCStringFunction(logFunc) // switch back to the system log function

NSLog("Observe this is logged.")

1
你可以使用“_NSLogCStringFunction()”来获得旧的函数,先临时替换它,然后再将其重新设置为原始值。 - deadbeef
@drasto 人们一直在谈论它,就像 Objective-C 存在的时间一样长,而且这个 SDK 的增强已经开放了将近10年。 - JAL
@drasto 添加了一个Swift示例,说明如何重新打开日志记录。如果您有其他问题,请告诉我。 - JAL
发生在iPhone 6S,iPhone 6,iPhone 6 Plus等设备上......不是总发生,但非常频繁。 - Rasto
1
@drasto 当然!我也很好奇这个问题,所以我跟进了一下。不用着急,有时间再回复我就行。谢谢,祝你好运! - JAL
显示剩余25条评论

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