Skip to content

Using NSLogger in frameworks and libraries

fpillet edited this page Dec 30, 2014 · 4 revisions

Using NSLogger in your frameworks and libraries

If you are developing a framework or library, you may want to provide logging support (either for debug builds of the framework / library, or for special cases in release builds). The problem would be that you would introduce another dependency on NSLogger itself and require the client application to link with it.

A smarter approach would be to use NSLogger if it has been linked into the application, and otherwise go for regular NSLog. This way you can take advantage of the advanced features of NSLogger while developing your code, and if the client application uses NSLogger itself, inject your logs in the app’s log flow. This is a much better experience for the application developer.

Suggested approach

Starting with NSLogger 1.5.1, the code supports a compilation mode where NSLogger APIs are not stripped by the linker. This is required for framework code to be able to use entry points that the application may not use, and that would otherwise be stripped at link time.

To use this compilation mode, the application developer can either:

  • #define NSLOGGER_ALLOW_NOSTRIP in its precompiled header, or anywhere prior to including LoggerClient.h
  • or if the client application is using CocoaPods, use the pod "NSLogger/NoStrip" subspec instead of plain pod "NSLogger"

Conditionalize logging according to NSLogger detected presence

The idea is that your framework / library code will contain one or more entry points, depending on the number of NSLogger APIs you want to use, and either use the NSLogger functions if they have been linked in, or go for NSLog if they are missing.

To do this, we can use the dynamic linker’s dlsym() function to obtain the address of the NSLogger functions we want to use. Since looking up symbols dynamically doesn’t trigger the linker into keep them at link time in the final product, this is why we have to use the NoStrip variant above.

Here is an example function (using ARC) that your code should use instead of calling LogMessageF:

/* This code provides a logging function you can use from within frameworks.
 * When using the framework with a client application, the code will either
 * use NSLogger if it is linked in the application, or go with a regular NSLog() call
 *
 * Helpful to provide debug versions of frameworks that can take advantage of NSLogger.
 *
 * This requires NSLogger 1.5.1 or later, which tags its log functions with the proper attribute to
 * prevent them from being stripped. Client application MUST define NSLOGGER_ALLOW_NOSTRIP or use
 * the "NSLogger/NoStrip" CocoaPod
 */
#import <dlfcn.h>
#import <Foundation/Foundation.h>

typedef void (*LogMessageF_func)(const char *file, int line, const char *function,
                                 NSString *tag, int level, NSString * const format,
                                 va_list args);

void FrameworkLog(const char *file, int line, const char *function, NSString *tag, int level, ...)
{
  static LogMessageF_func logFunc;
  static dispatch_once_t once;
  dispatch_once(&once, ^{
    logFunc = dlsym(RTLD_DEFAULT, "LogMessageF_va");
  });

  va_list args;
  va_start(args, level);
  NSString * format = va_arg(args, NSString *);
  if (logFunc) {
    // we know that this symbol exists, so we can safely call it
    logFunc(file, line, function, tag, level, format, args);
  } else {
    NSLog(@"[%@] %@", tag, [[NSString alloc] initWithFormat:format arguments:args]);
  }
  va_end(args);
}

Use your own log function in your log macros

Use log macros in your framework or library code that calls your own log function. A good idea is to clearly identify logs coming from your framework with a proper tag (if you are using several different tags, prefix them all with the same prefix). It will make it easy for the application developer to filter out your logs, or search through them.

extern void FrameworkLog(const char *file, int line, const char *function, NSString *tag, int level, ...);

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