Flexible iOS Logging

[bw_floater]

[bw_download title=”Download”]

[download id=”1″]

[/bw_download]

[bw_paypal_donation amount=”5″]

[/bw_floater]

Apple’s Cocoa SDK framework for the iOS provides basic logging capabilities through the NSLog function. This allows the developer to log simple text messages to the development console.

However, the use of NSLog suffers from two drawbacks:

  1. There is only one level of logging with NSLog. So during development, you get a record of all of the NSLog calls that you’ve inserted, some of which might be quite detailed and frequent, and it’s easy to be overwhelmed by the clutter. There is no way of selectively turning off some of the output.
  2. The NSLog function is called every time it is encountered, including within executing production release code. To avoid this, developers must typically remove all calls to NSLog before compiling for a production release. This is a headache fraught with possible mistakes and inhibits debugging future releases.

To remedy this, we’ve developed a logging framework that solves both of these issues. This library consists of a single Logging.h header file which adds, to iOS, flexible, non-intrusive logging capabilities that are efficiently enabled or disabled via compile switches.

Quick Start

To use this enhanced logging functionality, simply #import the Logging.h header file into any code file to which you want to add enhanced logging, and set the compiler switch LOGGING_ENABLED=1, either in a #define statement or in the Xcode build setting GCC_PREPROCESSOR_DEFINITIONS.

Logging Levels

To address the first issue described above, that of a single level of logging, this library defines four levels of logging: Trace, Info, Error and Debug, and each is enabled independently via a distinct compiler switch. Invoking any level is as simple as calling the appropriate variadic (variable number of arguments) function as shown in the following examples:

[objc]
LogTrace(@"Simple message");
LogTrace(@"Message with %i argument…oops I mean %.1f arguments", 1, 2.0f);
LogInfo(@"Do you object to this %@ date object?", [NSDate new]);
LogDebug(@"I’ll take this out if ever I solve this bug");
LogError(@"Don’t do that: %@", [NSException exceptionWithName: @"CRYING" reason: @"Spilled milk" userInfo:nil]);
[/objc]

In the examples above, you can see the four logging levels in action, along with the use of variable arguments that match against templates in the message string, in exactly the same manner as the NSLog or printf functions. The first argument is an NSString that optionally includes embedded format specifiers, and subsequent optional arguments indicate data to be formatted and inserted into that string. As with NSLog and printf, there must be an equal number of embedded format specifiers and optional arguments. For more information on string formatting, see the documentation for NSLog, String Format Specifiers and printf formatting.

The output of the above code is:

[text]
2010-07-23 18:27:23.398 Logging Sample[35700:20b] [trace] Simple message
2010-07-23 18:27:23.399 Logging Sample[35700:20b] [trace] Message with 1 argument…oops I mean 2.0 arguments
2010-07-23 18:27:23.402 Logging Sample[35700:20b] [info] Do you object to this 2010-07-23 18:27:23 -0400 date object?
2010-07-23 18:27:23.402 Logging Sample[35700:20b] [DEBUG] I’ll take this out if ever I solve this bug
2010-07-23 18:27:23.403 Logging Sample[35700:20b] [***ERROR***] Don’t do that: Spilled milk
[/text]

In this output listing, Logging Sample is just the name of the Xcode project. As you can see, each log message identifies the level of logging. Trace logging is recommended for detailed tracing of program flow, such as inside a loop, or on each frame render in a graphic engine. Info logging is recommended for general, infrequent, information messages such as initialization or shutdown activities, file loads, or user settings changes. Error logging, as the name suggests, is recommended for use only when there is an error to be logged. And finally, debug logging is recommended for temporary use during debugging. When trying to track down a coding error, you might temporarily sprinkle even your most detailed code with debug log messages. But once the problem is resolved, you should remove them, or possibly convert the most useful of them into info or trace logs.

Each of the logging levels is independently turned on or off by setting a boolean compiler switch to either 1 or 0, respectively, as follows:

  • LOGGING_LEVEL_TRACE enables or disables the LogTrace function.
  • LOGGING_LEVEL_INFO enables or disables the LogInfo function.
  • LOGGING_LEVEL_ERROR enables or disables the LogError function.
  • LOGGING_LEVEL_DEBUG enables or disables the LogDebug function.

By selectively turning on or off each of these switches, you could choose to output only error log messages so that logging will only occur if something goes wrong, or perhaps include info messages, so that you can keep track of high-level events such as file loads, or initialization and shutdown code. And when attempting to debug the details of a module, where you want more copious and detailed logging, you might enable either or both of the trace or debug logging levels.

The LOGGING_ENABLED compiler switch controls overall logging. This is normally turned on in development code to make use of the logging functionality. But in production code, you can disable all logging in one fell swoop by turning off the LOGGING_ENABLED compiler switch. This has the effect of overriding all the individual level switches, and disables all logging. None of the levels will produce log messages with the LOGGING_ENABLED compiler switch turned off.

To assist with tracking down bugs, you can opt to have each logged entry automatically include class, method and line information by turning on the LOGGING_INCLUDE_CODE_LOCATION compiler switch. Doing so will modify the log output above to appear as:

[text]
2010-07-23 18:28:17.238 Logging Sample[35732:20b] -[Logging_SampleAppDelegate applicationDidFinishLaunching:][Line 20] [trace] Simple message
2010-07-23 18:28:17.239 Logging Sample[35732:20b] -[Logging_SampleAppDelegate applicationDidFinishLaunching:][Line 21] [trace] Message with 1 argument…oops I mean 2.0 arguments
2010-07-23 18:28:17.247 Logging Sample[35732:20b] -[Logging_SampleAppDelegate applicationDidFinishLaunching:][Line 22] [info] Do you object to this 2010-07-23 18:28:17 -0400 date object?
2010-07-23 18:28:17.248 Logging Sample[35732:20b] -[Logging_SampleAppDelegate applicationDidFinishLaunching:][Line 23] [DEBUG] I’ll take this out if ever I solve this bug
2010-07-23 18:28:17.249 Logging Sample[35732:20b] -[Logging_SampleAppDelegate applicationDidFinishLaunching:][Line 24] [***ERROR***] Don’t do that: Spilled milk
[/text]

Like most compiler switches, each of the switches discussed in this article is turned on by setting its value to 1 and turned off by setting its value to 0. This can be done via #define statements in your code (or in Logging.h), or via the compiler build setting GCC_PREPROCESSOR_DEFINITIONS in your Xcode build configuration. Using the GCC_PREPROCESSOR_DEFINITIONS Xcode setting is the preferred choice, so that logging can be configured differently for different Xcode targets or build configurations, and particularly to ensure that logging is not accidentally left enabled in production release builds.

Logging Overhead

To address the second issue described above, namely the memory and processing overhead of the logging function calls themselves, the logging functions are implemented here via macros. Disabling logging, either entirely, or at a specific level, completely removes the corresponding log invocations from the compiled code, thereby eliminating both the memory and CPU overhead that the logging calls would add. So, there is absolutely no runtime penalty to be paid by leaving copious quantities of LogTrace calls spread thickly throughout your code, as long as you simply disable either trace logging or all logging by turning off the LOGGING_LEVEL_TRACE or LOGGING_ENABLED compiler switch, respectively, at build time.

Fantastic! Where Can I Get Me Some of That?

You can download this logging framework (a single Logging.h file) in the download area at the top-right of this article. It is distributed under an MIT license, which makes it free for you to use in your projects. However, if you find this code is useful, please remember to make a donation above to help us fund the ongoing development and support of frameworks such as this.

Credit Where Credit is Due

Thanks to Nick Dalton for outlining the underlying ideas for using variadic macros as well as for outputting the code location as part of the log entry.


Posted

in

by

Comments

14 responses to “Flexible iOS Logging”

  1. […] via Flexible iOS Logging « The Brenwill Workshop. […]

  2. saimonx

    Very Useful. Thanks!!!

  3. Jasper Blues

    Love the switch to turn line numbers on/off!

  4. Hilbert V

    A framework that every project shall use. Love It..!

  5. […] Logging, better way to log messages than using NSLog. […]

  6. Krishna Achugatla

    Very good one. I like it.

  7. Very useful. Thanks a lot.
    Is there a way to automatically add a log for every method call? I.e. trace all method call.

  8. […] huge (NSLogger,Lumberjack). All I wanted was something like this. Does a logger really have to be so […]

  9. […] huge (NSLogger,Lumberjack). All I wanted was something like this. Does a logger really have to be so […]

  10. […] LogTraceは、Flexible iOS Loggingというライブラリで定義されているマクロで、ログ出力が不要な場合はプリプロセスで取り除くことができるのだが、そうすると、変数successはどこにも利用されていないことになり、コンパイル時にunused variable警告が出る。 […]

  11. dhnjy Patil

    Does this add this logs to log files on the device??. If not, then how can i achive this functionality?

    1. Hi dhngy…

      Output is ultimately directed to NSLog, which under production should appear in the device logs.

      …Bill

  12. Aaron

    Is this up on GitHub somewhere as I would like to contribute minor modifications. Thanks for this great library by the way!

    1. Hi Aaron…

      This posting is quite old, and the code is not on Github.

      However, you can find a souped-up version of this logging code within the Cocos3D framework on Github.

      …Bill