Skip to content

Integrating NSLogger in your application

Florent Pillet edited this page Dec 13, 2010 · 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__)
#else
    #define LOG_NETWORK(...)    do{}while(0)
    #define LOG_GENERAL(...)    do{}while(0)
    #define LOG_GRAPHICS(...)   do{}while(0)
#endif

Now it suddenly becomes very easy and straightforward to add debug-only traces to your application. The previous code example would 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.

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 (the LoggerFlush() API is available starting with NSLogger 1.0b7):

  • 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)
    #else
        #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)
    #endif
#endif