Integrating NSLogger in your application

Legoless edited this page Jul 28, 2013 · 23 revisions

Integrating NSLogger in your application

The NSLogger API can be used for both iOS (iPhone, iPad) and Mac OS X desktop applications. The client code is the same, and compiles and run without modification on both platforms.

General guidelines

1. Add files to your project

First and foremost, add the following files to your project:

  • LoggerCommon.h
  • LoggerClient.h
  • LoggerClient.m

Also, if your project does not already include them, add the required system frameworks:

  • CFNetwork.framework
  • SystemConfiguration.framework

2. Use NSLogger API calls to log stuff

You can now use all the NSLogger API calls to log text, images and data. Take advantage of tags to categorize the output, this will allow you to better filter out unwanted traces when you want to explore specific aspects of your application traces. Use log levels to further categorize traces if you need (log levels start a 0, the bigger the number, the more specific / detailed the trace is meant to be). For example:

- (void)sendData:(NSData *)data toServer:(NSURL *)aURL
    LogMessage(@"network", 0, @"Going to send data to %@", aURL);
    LogData(@"network", 1, data);

Use macros efficiently

A good way to cleanly instrument your code, while keeping the option of disabling all calls to NSLogger in release builds, is to use macros in conjunction with a preprocessor macro. For debug builds, define the preprocessor macro DEBUG. Then prepare a header file that will contain your logging macros.

Here are some example macros. Note that we use variadic macros, which are a way to transfer the macro arguments from the macro usage to its replacement.

#ifdef DEBUG
    #define LOG_NETWORK(level, ...)    LogMessageF(__FILE__,__LINE__,__FUNCTION__,@"network",level,__VA_ARGS__)
    #define LOG_GENERAL(level, ...)    LogMessageF(__FILE__,__LINE__,__FUNCTION__,@"general",level,__VA_ARGS__)
    #define LOG_GRAPHICS(level, ...)   LogMessageF(__FILE__,__LINE__,__FUNCTION__,@"graphics",level,__VA_ARGS__)
    #define LOG_NETWORK(...)    do{}while(0)
    #define LOG_GENERAL(...)    do{}while(0)
    #define LOG_GRAPHICS(...)   do{}while(0)

minor suggestion:

#define LOG_GENERAL(...) LogMessageF(__FILE__,__LINE__,__FUNCTION__,[[NSString stringWithUTF8String:__FILE__] lastPathComponent],1,__VA_ARGS__)

Now it suddenly becomes very easy and straightforward to add debug-only traces to your application. All you need to do is import the header file or add it to prefix.pch file. Make sure LoggerClient.h is also imported.

The previous code example would now become:

- (void)sendData:(NSData *)data toServer:(NSURL *)aURL
    LOG_NETWORK(0, @"Going to send data to %@", aURL);
    LOG_NETWORK(1, @"-> data=%@", data);

And no code at all will be emitted when building the Release build of your application.

Alternative to defining a DEBUG preprocessor macro

Andreas Linde suggested an alternative approach which allows testing for the compilation configuration in Xcode:

  • Open your build settings (Project or Target)
  • Choose All Configurations
  • Search for Preprocessor Macros Not Used in Precompiled Headers

Now you can check in your code via #ifdef CONFIGURATION_Debug where the text Debug is the actual name of the configuration you are using for the debug builds.

Flushing all logs before a major failure

It is common (and good) practice to use assert() in your code to verify that important conditions are met. One drawback is that a failed assertion immediately aborts the application, and all logs that have not been transmitted to the viewer could be lost. You can use the LoggerFlush() API to ensure that all logs have been transmitted. Here is an example of assert() macro redefinition that does the following:

  • Log a message to the console informing the developer that an assertion is going to fail
  • Ensure that all logs have been transmitted, waiting (if needed) for a connection to the viewer to be acquired
  • Finally fail the assertion
#if defined(DEBUG) && !defined(NDEBUG)
    #undef assert
    #if __DARWIN_UNIX03
        #define assert(e) \
            (__builtin_expect(!(e), 0) ? (CFShow(CFSTR("assert going to fail, connect NSLogger NOW\n")), LoggerFlush(NULL,YES), __assert_rtn(__func__, __FILE__, __LINE__, #e)) : (void)0)
        #define assert(e)  \
            (__builtin_expect(!(e), 0) ? (CFShow(CFSTR("assert going to fail, connect NSLogger NOW\n")), LoggerFlush(NULL,YES), __assert(#e, __FILE__, __LINE__)) : (void)0)

Compatibility with Garbage Collection and ARC

The NSLogger client code can be used in all run environments of the Apple platforms: iOS, Mac OS X (32 and 64 bits, with or without garbage collection).

The code can be used and compiled in project using the new ARC compile-time feature. You have nothing to do – ARC mode detection is done by macros in the code. You will, however, need to cast parameters to __bridge CFStringRef instead of simply CFStringRef in the calls which take a CFStringRef as parameter: LoggerSetupBonjour(), LoggerSetViewerHost() and LoggerSetBufferFile().

Guaranteeing logs flush on application abort

NSLogger now installs an exit handler via atexit() and automatically flushes all connected loggers upon exit, guaranteeing that you see the last log messages issued.