Filename and Line Number with NSLog: Part II

In the previous post I demonstrated a simple debug class that I wrote to wrap some additional code around NSLog. The code allows for displaying additional information beyond the date/time stamp and process ID that NSLog outputs, specifically, the filename which calls the debug routine, and the line number where the call was invoked. I also added a few additional configuration options including an option to disable all debug messages.

The figure below shows the debug routine called from within two separate source files. Notice the filename and line number references:

There is also an option to show the entire file path for the source code where the debug statement was included:

Interface Code:

Let’s start with the interface definition, which is shown below. I begin with the two configurable options: showing the full path to the filename, and enabling/disabling debug output. This is followed by a variadic macro definition which is the debug statement used inside an application to display messages. Continue reading to see an example of how to call/use this macro.

The variadic macro wraps the call to the singleton class DebugOutput, calling the method ‘output’. __FILE__ and __LINE__ are predefined macros in the C language that expand to the current file and line number, respectively.

Notice how I enable/disable all debug messages using the #define DEBUG statement. When debug is off (0), all references to the debug statements expand to “nothing.”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/************************************************************************
 * DebugOutput.h
 *
 * Definitions for DebugOutput class
 ************************************************************************/
 
// Show full path of filename?
#define DEBUG_SHOW_FULLPATH YES
 
// Enable debug (NSLog) wrapper code?
#define DEBUG 1
 
#if DEBUG
  #define debug(format,...) [[DebugOutput sharedDebug] output:__FILE__
                            lineNumber:__LINE__ input:(format), ##__VA_ARGS__]
#else
  #define debug(format,...)
#endif
 
@interface DebugOutput : NSObject
{
}
+ (DebugOutput *) sharedDebug;
-(void)output:(char*)fileName lineNumber:(int)lineNumber
        input:(NSString*)input, ...;
@end

The last few lines define the class interface, including one class and one instance method.

Implementation Code:

The next block of code is the implementation file for DebugOutput.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/************************************************************************
* DebugOutput.m
*
* Singleton class for wrapping NSLog messages, adding functionality
* for displaying filename and line numbers. Also includes configuration
* options to "turn off" all debug (NSLog) messages
************************************************************************/
#import "DebugOutput.h"
 
@implementation DebugOutput
 
static DebugOutput *sharedDebugInstance = nil;
 
/*---------------------------------------------------------------------*/
+ (DebugOutput *) sharedDebug
{
  @synchronized(self)
  {
    if (sharedDebugInstance == nil)
    {
      [[self alloc] init];
    }
  }
  return sharedDebugInstance;
}
 
/*---------------------------------------------------------------------*/
+ (id) allocWithZone:(NSZone *) zone
{
  @synchronized(self)
  {
    if (sharedDebugInstance == nil)
    {
      sharedDebugInstance = [super allocWithZone:zone];
      return sharedDebugInstance;
    }
  }
  return nil;
}
 
/*---------------------------------------------------------------------*/
- (id)copyWithZone:(NSZone *)zone
{
  return self;
}
 
/*---------------------------------------------------------------------*/
- (id)retain
{
  return self;
}
 
/*---------------------------------------------------------------------*/
- (void)release
{
  // No action required...
}
 
/*---------------------------------------------------------------------*/
- (unsigned)retainCount
{
  return UINT_MAX;  // An object that cannot be released
}
 
/*---------------------------------------------------------------------*/
- (id)autorelease
{
  return self;
}
 
/*---------------------------------------------------------------------*/
-(void)output:(char*)fileName lineNumber:(int)lineNumber input:(NSString*)input, ...
{
  va_list argList;
  NSString *filePath, *formatStr;
 
  // Build the path string
  filePath = [[NSString alloc] initWithBytes:fileName length:strlen(fileName)
                               encoding:NSUTF8StringEncoding];
 
  // Process arguments, resulting in a format string
  va_start(argList, input);
  formatStr = [[NSString alloc] initWithFormat:input arguments:argList];
  va_end(argList);
 
  // Call NSLog, prepending the filename and line number
  NSLog(@"File:%s Line:%d %@",[((DEBUG_SHOW_FULLPATH) ? filePath :
           [filePath lastPathComponent]) UTF8String], lineNumber, formatStr);
 
  [filePath release];
  [formatStr release];
}
 
@end

I’ve written this class as a singleton, the specifics for doing so are in lines 12 - 69. The workhorse of this class is the ‘output:’ method near the bottom of the code listing. It’s pretty straight forward. Essentially, using the parameters passed in, create a string that represents the file name, create a format string and call NSLog() to glue everything this together.

The code that I wrote to demonstrate the output (see the figures above) is as follows:

CreatingClasses.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import <Foundation/Foundation.h>
#import "SomeClass.h"
#import "DebugOutput.h"
 
int main (int argc, const char * argv[])
{
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  SomeClass *ptr = [[SomeClass alloc] initWithStrAndDate:"Fubar" date:[NSDate date]];
  NSString *str = [[NSString alloc] initWithString:@"Test string"];
  int x = 99;
 
  debug(@"ptr:%@ x:%d str:%@", ptr, x, str);
 
  [ptr release];
  [str release];
  [pool drain];
  return 0;
}

SomeClass.m

1
2
3
4
5
6
7
8
9
10
11
12
...
-(id)initWithStrAndDate: (NSString *)inString date:(NSDate *)inDate
{
  debug(@"inString:%@ inDate:%@", inString, inDate);
  if (self = [super init])
  {
    [self setStr:inString];
    [self setDate:inDate];
  }
  return self;
}
...

You can download the source code (Xcode project) for this example from here.

I want to thank Mark Dalrymple at Borkware who wrote an article on A Better NSLog() that was the inspiration for my approach.

Cocoa

Comments

3 Responses to “Filename and Line Number with NSLog: Part II”

Leave Comment

(required)

(required)