Warning: This answer uses the private-yet-sort-of-documented Cocoa C functions _NSSetLogCStringFunction()
and _NSLogCStringFunction()
.
_NSSetLogCStringFunction()
is an interface created by Apple to handle the implementation function for NSLog
. It was initially exposed for WebObjects to hook into NSLog
statements on Windows machines, but still exists in the iOS and OS X APIs today. It is documented in this support article.
The function takes in a function pointer with the arguments const char* message
, the string to log, unsigned length
, the length of the message, and a BOOL withSysLogBanner
which toggles the standard logging banner. If we create our own hook function for logging that doesn't actually do anything (an empty implementation rather than calling fprintf
like NSLog
does behind-the-scenes), we can effectively disable all logging for your application.
Objective-C Example (or Swift with bridging header):
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
An example implementation of this can be found in YILogHook, which provides an interface to add an array of blocks to any NSLog
statement (write to file, etc).
Pure Swift Example:
@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
In Swift, you can also chose to ignore all of the block parameters without using hookFunc
like so:
_NSSetLogCStringFunction { _,_,_ in }
To turn logging back on using Objective-C, just pass in NULL
as the function pointer:
_NSSetLogCStringFunction(NULL);
With Swift things are a little different, since the compiler will complain about a type mismatch if we try to pass in nil
or a nil
pointer (NULL
is unavailable in Swift). To solve this, we need to access another system function, _NSLogCStringFunction
, to get a pointer to the default logging implementation, retain that reference while logging is disabled, and set the reference back when we want to turn logging back on.
I've cleaned up the Swift implementation of this by adding a NSLogCStringFunc
typedef
:
/// 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.")
UITextChecker
methods). I have 2 questions about it: 1. If I did redirect in production and the app crashes during the call for which stderr is redirected would the crash log from Apple containt the exception that casused the crash or not? 2. What is performance cost of the redirect? If I understand it correctly the code is opening files. In some cases I would need to do that redirect several times per second while also running animations etc. – ContravallationNSLog
. – Imprintf
forNSLog
, which seems to come in one step before where the strings are logged. – Im