diff options
-rw-r--r-- | Foundation/GTMLogger+ASL.h | 47 | ||||
-rw-r--r-- | Foundation/GTMLogger+ASL.m | 66 | ||||
-rw-r--r-- | Foundation/GTMLogger+ASLTest.m | 79 | ||||
-rw-r--r-- | Foundation/GTMLogger.h | 54 | ||||
-rw-r--r-- | Foundation/GTMLogger.m | 62 | ||||
-rw-r--r-- | Foundation/GTMLoggerTest.m | 46 |
6 files changed, 253 insertions, 101 deletions
diff --git a/Foundation/GTMLogger+ASL.h b/Foundation/GTMLogger+ASL.h index 689a0d9..87a513d 100644 --- a/Foundation/GTMLogger+ASL.h +++ b/Foundation/GTMLogger+ASL.h @@ -6,9 +6,9 @@ // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy // of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -23,14 +23,14 @@ // GTMLogger (GTMLoggerASLAdditions) // -// Adds a convenience creation method that allows you to get a standard +// Adds a convenience creation method that allows you to get a standard // GTMLogger object that is configured to write to ASL (Apple System Log) using // the GTMLogASLWriter (declared below). // @interface GTMLogger (GTMLoggerASLAdditions) // Returns a new autoreleased GTMLogger instance that will log to ASL, using -// the GTMLogStandardFormatter, and the GTMLogLevelFilter filter. +// the GTMLogASLFormatter, and the GTMLogLevelFilter filter. + (id)standardLoggerWithASL; @end @@ -39,10 +39,10 @@ @class GTMLoggerASLClient; // GTMLogASLWriter -// -// A GTMLogWriter implementation that will send log messages to ASL (Apple -// System Log facility). To use with GTMLogger simply set the "writer" for a -// GTMLogger to be an instance of this class. The following example sets the +// +// A GTMLogWriter implementation that will send log messages to ASL (Apple +// System Log facility). To use with GTMLogger simply set the "writer" for a +// GTMLogger to be an instance of this class. The following example sets the // shared system logger to lot to ASL. // // [[GTMLogger sharedLogger] setWriter:[GTMLogASLWriter aslWriter]]; @@ -53,34 +53,51 @@ @interface GTMLogASLWriter : NSObject <GTMLogWriter> { @private __weak Class aslClientClass_; + NSString *facility_; } -// Returns an autoreleased GTMLogASLWriter instance that uses an instance of -// GTMLoggerASLClient. +// Returns an autoreleased GTMLogASLWriter instance that uses an instance of +// GTMLoggerASLClient and the default ASL facility. + (id)aslWriter; +// Returns an autoreleased GTMLogASLWriter instance that uses an instance of +// GTMLoggerASLClient and the supplied facility. See asl_open(3) for a +// discusssion of ASL facility strings. ++ (id)aslWriterWithFacility:(NSString *)facility; + // Designated initializer. Uses instances of the specified |clientClass| to talk -// to the ASL system. This method is typically only useful for testing. Users +// to the ASL system. All logs from this method will use |facility| as the ASL +// log facility. This method is typically only useful for testing. Users // should generally NOT use this method to get an instance. Instead, simply use -// the +aslWriter method to obtain an instance. -- (id)initWithClientClass:(Class)clientClass; +// the +aslWriter or +aslWriterWithFacility: methods to obtain an instance. +- (id)initWithClientClass:(Class)clientClass facility:(NSString *)facility; @end // GTMLogASLWriter +// An ASL-specific log formatter that replicates the same fields as +// GTMLogStandardFormatter except for those (date, process name) that ASL +// records independently. +@interface GTMLogASLFormatter : GTMLogBasicFormatter +@end // GTMLogASLFormatter + + // Helper class used by GTMLogASLWriter to create an ASL client and write to the // ASL log. This class is need to make management/cleanup of the aslclient work // in a multithreaded environment. You'll need one of these GTMLoggerASLClient -// per thread (this is automatically handled by GTMLogASLWriter). +// per thread (this is automatically handled by GTMLogASLWriter). // // This class should rarely (if EVER) be used directly. It's designed to be used -// internally by GTMLogASLWriter, and by some unit tests. It should not be +// internally by GTMLogASLWriter, and by some unit tests. It should not be // used externally. @interface GTMLoggerASLClient : NSObject { @private aslclient client_; } +// Designated initializer, |facility| is supplied to asl_open(). +- (id)initWithFacility:(NSString *)facility; + // Sends the given string to ASL at the specified ASL log |level|. - (void)log:(NSString *)msg level:(int)level; diff --git a/Foundation/GTMLogger+ASL.m b/Foundation/GTMLogger+ASL.m index 90ea7e5..435cec3 100644 --- a/Foundation/GTMLogger+ASL.m +++ b/Foundation/GTMLogger+ASL.m @@ -6,9 +6,9 @@ // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy // of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -25,7 +25,7 @@ + (id)standardLoggerWithASL { id me = [self standardLogger]; [me setWriter:[[[GTMLogASLWriter alloc] init] autorelease]]; - [me setFormatter:[[[GTMLogBasicFormatter alloc] init] autorelease]]; + [me setFormatter:[[[GTMLogASLFormatter alloc] init] autorelease]]; return me; } @@ -35,37 +35,53 @@ @implementation GTMLogASLWriter + (id)aslWriter { - return [[[self alloc] init] autorelease]; + return [[[self alloc] initWithClientClass:nil facility:nil] autorelease]; +} + ++ (id)aslWriterWithFacility:(NSString *)facility { + return [[[self alloc] initWithClientClass:nil facility:facility] autorelease]; } - (id)init { - return [self initWithClientClass:nil]; + return [self initWithClientClass:nil facility:nil]; } -- (id)initWithClientClass:(Class)clientClass { +- (id)initWithClientClass:(Class)clientClass facility:(NSString *)facility { if ((self = [super init])) { aslClientClass_ = clientClass; if (aslClientClass_ == nil) { aslClientClass_ = [GTMLoggerASLClient class]; } + facility_ = [facility copy]; } return self; } +- (void)dealloc { + [facility_ release]; + [super dealloc]; +} + - (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level { - static NSString *const kASLClientKey = @"GTMLoggerASLClientKey"; - + // Because |facility_| is an argument to asl_open() we must store a separate + // one for each facility in thread-local storage. + static NSString *const kASLClientKey = @"GTMLoggerASLClient"; + NSString *key = kASLClientKey; + if (facility_) { + key = [NSString stringWithFormat:@"GTMLoggerASLClient-%@", facility_]; + } + // Lookup the ASL client in the thread-local storage dictionary NSMutableDictionary *tls = [[NSThread currentThread] threadDictionary]; - GTMLoggerASLClient *client = [tls objectForKey:kASLClientKey]; - + GTMLoggerASLClient *client = [tls objectForKey:key]; + // If the ASL client wasn't found (e.g., the first call from this thread), // then create it and store it in the thread-local storage dictionary if (client == nil) { - client = [[[aslClientClass_ alloc] init] autorelease]; - [tls setObject:client forKey:kASLClientKey]; + client = [[[aslClientClass_ alloc] initWithFacility:facility_] autorelease]; + [tls setObject:client forKey:key]; } - + // Map the GTMLoggerLevel level to an ASL level. int aslLevel = ASL_LEVEL_INFO; switch (level) { @@ -81,18 +97,36 @@ aslLevel = ASL_LEVEL_ALERT; break; } - + [client log:msg level:aslLevel]; } @end // GTMLogASLWriter +@implementation GTMLogASLFormatter + +- (NSString *)stringForFunc:(NSString *)func + withFormat:(NSString *)fmt + valist:(va_list)args + level:(GTMLoggerLevel)level { + return [NSString stringWithFormat:@"%@ %@", + [self prettyNameForFunc:func], + [super stringForFunc:func withFormat:fmt valist:args level:level]]; +} + +@end // GTMLogASLFormatter + + @implementation GTMLoggerASLClient - (id)init { + return [self initWithFacility:nil]; +} + +- (id)initWithFacility:(NSString *)facility { if ((self = [super init])) { - client_ = asl_open(NULL, NULL, 0); + client_ = asl_open(NULL, [facility UTF8String], 0); if (client_ == nil) { // COV_NF_START - no real way to test this [self release]; @@ -115,7 +149,7 @@ } #endif -// We don't test this one line because we don't want to pollute actual system +// We don't test this one line because we don't want to pollute actual system // logs with test messages. // COV_NF_START - (void)log:(NSString *)msg level:(int)level { diff --git a/Foundation/GTMLogger+ASLTest.m b/Foundation/GTMLogger+ASLTest.m index cd32484..2756bfc 100644 --- a/Foundation/GTMLogger+ASLTest.m +++ b/Foundation/GTMLogger+ASLTest.m @@ -6,9 +6,9 @@ // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy // of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -19,15 +19,31 @@ #import "GTMLogger+ASL.h" #import "GTMSenTestCase.h" -@interface DummyASLClient : GTMLoggerASLClient +@interface DummyASLClient : GTMLoggerASLClient { + @private + NSString *facility_; +} @end static NSMutableArray *gDummyLog; // weak @implementation DummyASLClient +- (id)initWithFacility:(NSString *)facility { + if ((self = [super initWithFacility:facility])) { + facility_ = [facility copy]; + } + return self; +} + +- (void)dealloc { + [facility_ release]; + [super dealloc]; +} + - (void)log:(NSString *)msg level:(int)level { - NSString *line = [msg stringByAppendingFormat:@"@%d", level]; + NSString *line = [NSString stringWithFormat:@"%@-%@-%d", + (facility_ ? facility_ : @""), msg, level]; [gDummyLog addObject:line]; } @@ -42,7 +58,7 @@ static NSMutableArray *gDummyLog; // weak - (void)testCreation { GTMLogger *aslLogger = [GTMLogger standardLoggerWithASL]; STAssertNotNil(aslLogger, nil); - + GTMLogASLWriter *writer = [GTMLogASLWriter aslWriter]; STAssertNotNil(writer, nil); } @@ -50,12 +66,13 @@ static NSMutableArray *gDummyLog; // weak - (void)testLogWriter { gDummyLog = [[[NSMutableArray alloc] init] autorelease]; GTMLogASLWriter *writer = [[[GTMLogASLWriter alloc] - initWithClientClass:[DummyASLClient class]] + initWithClientClass:[DummyASLClient class] + facility:nil] autorelease]; - + STAssertNotNil(writer, nil); - STAssertTrue([gDummyLog count] == 0, nil); + STAssertEquals([gDummyLog count], (NSUInteger)0, nil); // Log some messages [writer logMessage:@"unknown" level:kGTMLoggerLevelUnknown]; @@ -63,21 +80,45 @@ static NSMutableArray *gDummyLog; // weak [writer logMessage:@"info" level:kGTMLoggerLevelInfo]; [writer logMessage:@"error" level:kGTMLoggerLevelError]; [writer logMessage:@"assert" level:kGTMLoggerLevelAssert]; - - // Inspect the logged message to make sure they were logged correctly. The - // dummy writer will save the messages w/ @level concatenated. The "level" + + // Inspect the logged message to make sure they were logged correctly. The + // dummy writer will save the messages w/ @level concatenated. The "level" // will be the ASL level, not the GTMLogger level. GTMLogASLWriter will log - // all + // all NSArray *expected = [NSArray arrayWithObjects: - @"unknown@5", - @"debug@5", - @"info@5", - @"error@3", - @"assert@1", + @"-unknown-5", + @"-debug-5", + @"-info-5", + @"-error-3", + @"-assert-1", nil]; - + STAssertEqualObjects(gDummyLog, expected, nil); - + [gDummyLog removeAllObjects]; + + // Same test with facility + writer = [[[GTMLogASLWriter alloc] + initWithClientClass:[DummyASLClient class] + facility:@"testfac"] autorelease]; + + + STAssertNotNil(writer, nil); + STAssertEquals([gDummyLog count], (NSUInteger)0, nil); + + [writer logMessage:@"unknown" level:kGTMLoggerLevelUnknown]; + [writer logMessage:@"debug" level:kGTMLoggerLevelDebug]; + [writer logMessage:@"info" level:kGTMLoggerLevelInfo]; + [writer logMessage:@"error" level:kGTMLoggerLevelError]; + [writer logMessage:@"assert" level:kGTMLoggerLevelAssert]; + expected = [NSArray arrayWithObjects: + @"testfac-unknown-5", + @"testfac-debug-5", + @"testfac-info-5", + @"testfac-error-3", + @"testfac-assert-1", + nil]; + STAssertEqualObjects(gDummyLog, expected, nil); + gDummyLog = nil; } diff --git a/Foundation/GTMLogger.h b/Foundation/GTMLogger.h index 6223999..9fd2362 100644 --- a/Foundation/GTMLogger.h +++ b/Foundation/GTMLogger.h @@ -6,9 +6,9 @@ // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy // of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -18,15 +18,15 @@ // Key Abstractions // ---------------- -// -// This file declares multiple classes and protocols that are used by the +// +// This file declares multiple classes and protocols that are used by the // GTMLogger logging system. The 4 main abstractions used in this file are the // following: // // * logger (GTMLogger) - The main logging class that users interact with. It // has methods for logging at different levels and uses a log writer, a log // formatter, and a log filter to get the job done. -// +// // * log writer (GTMLogWriter) - Writes a given string to some log file, where // a "log file" can be a physical file on disk, a POST over HTTP to some URL, // or even some in-memory structure (e.g., a ring buffer). @@ -44,7 +44,7 @@ // flexibility to dynamically enable debug logging in Release builds. // // This file also declares some classes to handle the common log writer, log -// formatter, and log filter cases. Callers can also create their own writers, +// formatter, and log filter cases. Callers can also create their own writers, // formatters, and filters and they can even build them on top of the ones // declared here. Keep in mind that your custom writer/formatter/filter may be // called from multiple threads, so it must be thread-safe. @@ -63,7 +63,7 @@ // sent to a GTMLogger to log a message, the message is formatted using the log // formatter, then the log filter is consulted to see if the message should be // logged, and if so, the message is sent to the log writer to be written out. -// +// // GTMLogger is intended to be a flexible and thread-safe logging solution. Its // flexibility comes from the fact that GTMLogger instances can be customized // with user defined formatters, filters, and writers. And these writers, @@ -71,7 +71,7 @@ // ways to suit the needs at hand. For example, multiple writers can be used at // the same time, and a GTMLogger instance can even be used as another // GTMLogger's writer. This allows for arbitrarily deep logging trees. -// +// // A standard GTMLogger uses a writer that sends messages to standard out, a // formatter that smacks a timestamp and a few other bits of interesting // information on the message, and a filter that filters out debug messages from @@ -79,13 +79,13 @@ // the following: // // 2007-12-30 10:29:24.177 myapp[4588/0xa07d0f60] [lvl=1] foo=<Foo: 0x123> -// +// // The output contains the date and time of the log message, the name of the // process followed by its process ID/thread ID, the log level at which the // message was logged (in the previous example the level was 1: // kGTMLoggerLevelDebug), and finally, the user-specified log message itself (in // this case, the log message was @"foo=%@", foo). -// +// // Multiple instances of GTMLogger can be created, each configured their own // way. Though GTMLogger is not a singleton (in the GoF sense), it does provide // access to a shared (i.e., globally accessible) GTMLogger instance. This makes @@ -107,10 +107,10 @@ // with behavior that many developers are currently used to. Note that this // means that GTMLoggerDebug(@"hi") will be compiled out of Release builds, but // [[GTMLogger sharedLogger] logDebug:@"hi"] will NOT be compiled out. -// +// // Standard loggers are created with the GTMLogLevelFilter log filter, which // filters out certain log messages based on log level, and some other settings. -// +// // In addition to the -logDebug:, -logInfo:, and -logError: methods defined on // GTMLogger itself, there are also C macros that make usage of the shared // GTMLogger instance very convenient. These macros are: @@ -140,7 +140,7 @@ // GTMLogger class directly in order to configure the shared logger, which all // of the code using the macros will be using. Again, this is just the typical // situation. -// +// // To be complete, there are cases where you may want to use GTMLogger directly, // or even create separate GTMLogger instances for some reason. That's fine, // too. @@ -154,14 +154,14 @@ // // GTMLoggerDebug(@"foo = %@", foo); // -// 2. The previous example is similar to the following. The major difference is +// 2. The previous example is similar to the following. The major difference is // that the previous call (example 1) will be compiled out of Release builds // but this statement will not be compiled out. // // [[GTMLogger sharedLogger] logDebug:@"foo = %@", foo]; // // 3. Send all logging output from the shared logger to a file. We do this by -// creating an NSFileHandle for writing associated with a file, and setting +// creating an NSFileHandle for writing associated with a file, and setting // that file handle as the logger's writer. // // NSFileHandle *f = [NSFileHandle fileHandleForWritingAtPath:@"/tmp/f.log" @@ -179,12 +179,12 @@ // 5. Create a logger that writes to stdout and does NOT do any formatting to // the log message. This might be useful, for example, when writing a help // screen for a command-line tool to standard output. -// +// // GTMLogger *logger = [GTMLogger logger]; // [logger logInfo:@"%@ version 0.1 usage", progName]; // -// 6. Send log output to stdout AND to a log file. The trick here is that -// NSArrays function as composite log writers, which means when an array is +// 6. Send log output to stdout AND to a log file. The trick here is that +// NSArrays function as composite log writers, which means when an array is // set as the log writer, it forwards all logging messages to all of its // contained GTMLogWriters. // @@ -192,7 +192,7 @@ // NSArray *writers = [NSArray arrayWithObjects: // [NSFileHandle fileHandleForWritingAtPath:@"/tmp/f.log" create:YES], // [NSFileHandle fileHandleWithStandardOutput], nil]; -// +// // GTMLogger *logger = [GTMLogger standardLogger]; // [logger setWriter:writers]; // [logger logInfo:@"hi"]; // Output goes to stdout and /tmp/f.log @@ -281,7 +281,7 @@ // Accessors // -// Accessor methods for the log writer. If the log writer is set to nil, +// Accessor methods for the log writer. If the log writer is set to nil, // [NSFileHandle fileHandleWithStandardOutput] is used. - (id<GTMLogWriter>)writer; - (void)setWriter:(id<GTMLogWriter>)writer; @@ -300,7 +300,7 @@ @end // GTMLogger -// Helper functions that are used by the convenience GTMLogger*() macros that +// Helper functions that are used by the convenience GTMLogger*() macros that // enable the logging of function names. @interface GTMLogger (GTMLoggerMacroHelpers) - (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ... @@ -359,7 +359,7 @@ typedef enum { // now becomes a valid log writer. Log messages are written to the file handle // with a newline appended. @interface NSFileHandle (GTMFileHandleLogWriter) <GTMLogWriter> -// Opens the file at |path| in append mode, and creates the file with |mode| +// Opens the file at |path| in append mode, and creates the file with |mode| // if it didn't previously exist. + (id)fileHandleForLoggingAtPath:(NSString *)path mode:(mode_t)mode; @end // NSFileHandle @@ -373,7 +373,7 @@ typedef enum { // // This is useful in situations where you would like to send log output to // multiple log writers at the same time. Simply create an NSArray of the log -// writers you wish to use, then set the array as the "writer" for your +// writers you wish to use, then set the array as the "writer" for your // GTMLogger instance. @interface NSArray (GTMArrayCompositeLogWriter) <GTMLogWriter> @end // GTMArrayCompositeLogWriter @@ -384,7 +384,7 @@ typedef enum { // // This is useful when you want to configure a logger to log to a specific // writer with a specific formatter and/or filter. But you want to also compose -// that with a different log writer that may have its own formatter and/or +// that with a different log writer that may have its own formatter and/or // filter. @interface GTMLogger (GTMLoggerLogWriter) <GTMLogWriter> @end // GTMLoggerLogWriter @@ -405,10 +405,14 @@ typedef enum { @end // GTMLogFormatter -// A basic log formatter that formats a string the same way that NSLog (or +// A basic log formatter that formats a string the same way that NSLog (or // printf) would. It does not do anything fancy, nor does it add any data of its // own. @interface GTMLogBasicFormatter : NSObject <GTMLogFormatter> + +// Helper method for prettying C99 __func__ and GCC __PRETTY_FUNCTION__ +- (NSString *)prettyNameForFunc:(NSString *)func; + @end // GTMLogBasicFormatter diff --git a/Foundation/GTMLogger.m b/Foundation/GTMLogger.m index e40defc..ec51107 100644 --- a/Foundation/GTMLogger.m +++ b/Foundation/GTMLogger.m @@ -6,9 +6,9 @@ // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy // of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -36,20 +36,20 @@ - (void)logInternalFunc:(const char *)func format:(NSString *)fmt - valist:(va_list)args + valist:(va_list)args level:(GTMLoggerLevel)level NS_FORMAT_FUNCTION(2, 0); @end -// Reference to the shared GTMLogger instance. This is not a singleton, it's +// Reference to the shared GTMLogger instance. This is not a singleton, it's // just an easy reference to one shared instance. static GTMLogger *gSharedLogger = nil; @implementation GTMLogger -// Returns a pointer to the shared logger instance. If none exists, a standard +// Returns a pointer to the shared logger instance. If none exists, a standard // logger is created and returned. + (id)sharedLogger { @synchronized(self) { @@ -245,16 +245,16 @@ static GTMLogger *gSharedLogger = nil; - (void)logInternalFunc:(const char *)func format:(NSString *)fmt - valist:(va_list)args + valist:(va_list)args level:(GTMLoggerLevel)level { GTMLOGGER_ASSERT(formatter_ != nil); GTMLOGGER_ASSERT(filter_ != nil); GTMLOGGER_ASSERT(writer_ != nil); - + NSString *fname = func ? [NSString stringWithUTF8String:func] : nil; NSString *msg = [formatter_ stringForFunc:fname withFormat:fmt - valist:args + valist:args level:level]; if (msg && [filter_ filterAllowsMessage:msg level:level]) [writer_ logMessage:msg level:level]; @@ -279,7 +279,13 @@ static GTMLogger *gSharedLogger = nil; - (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level { @synchronized(self) { NSString *line = [NSString stringWithFormat:@"%@\n", msg]; - [self writeData:[line dataUsingEncoding:NSUTF8StringEncoding]]; + // Closed pipes should not generate exceptions in our caller + @try { + [self writeData:[line dataUsingEncoding:NSUTF8StringEncoding]]; + } + @catch (NSException *e) { + // Ignored + } } } @@ -306,18 +312,18 @@ static GTMLogger *gSharedLogger = nil; - (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level { switch (level) { case kGTMLoggerLevelDebug: - [self logDebug:@"%@", msg]; + [self logDebug:@"%@", msg]; break; case kGTMLoggerLevelInfo: [self logInfo:@"%@", msg]; break; - case kGTMLoggerLevelError: + case kGTMLoggerLevelError: [self logError:@"%@", msg]; break; case kGTMLoggerLevelAssert: [self logAssert:@"%@", msg]; break; - default: + default: // Ignore the message. break; } @@ -328,12 +334,30 @@ static GTMLogger *gSharedLogger = nil; @implementation GTMLogBasicFormatter +- (NSString *)prettyNameForFunc:(NSString *)func { + NSString *name = [func stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSString *function = @"(unknown)"; + if ([name length]) { + if (// Objective C __func__ and __PRETTY_FUNCTION__ + [name hasPrefix:@"-["] || [name hasPrefix:@"+["] || + // C++ __PRETTY_FUNCTION__ and other preadorned formats + [name hasSuffix:@")"]) { + function = name; + } else { + // Assume C99 __func__ + function = [NSString stringWithFormat:@"%@()", name]; + } + } + return function; +} + - (NSString *)stringForFunc:(NSString *)func withFormat:(NSString *)fmt - valist:(va_list)args + valist:(va_list)args level:(GTMLoggerLevel)level { // Performance note: We may want to do a quick check here to see if |fmt| - // contains a '%', and if not, simply return 'fmt'. + // contains a '%', and if not, simply return 'fmt'. return [[[NSString alloc] initWithFormat:fmt arguments:args] autorelease]; } @@ -361,7 +385,7 @@ static GTMLogger *gSharedLogger = nil; - (NSString *)stringForFunc:(NSString *)func withFormat:(NSString *)fmt - valist:(va_list)args + valist:(va_list)args level:(GTMLoggerLevel)level { GTMLOGGER_ASSERT(dateFormatter_ != nil); NSString *tstamp = nil; @@ -370,7 +394,7 @@ static GTMLogger *gSharedLogger = nil; } return [NSString stringWithFormat:@"%@ %@[%d/%p] [lvl=%d] %@ %@", tstamp, pname_, pid_, pthread_self(), - level, (func ? func : @"(no func)"), + level, [self prettyNameForFunc:func], [super stringForFunc:func withFormat:fmt valist:args level:level]]; } @@ -388,7 +412,7 @@ static BOOL IsVerboseLoggingEnabled(void) { static char *env = NULL; if (env == NULL) env = getenv([kVerboseLoggingKey UTF8String]); - + if (env && env[0]) { return (strtol(env, NULL, 10) != 0); } @@ -403,9 +427,9 @@ static BOOL IsVerboseLoggingEnabled(void) { #if DEBUG return YES; #endif - + BOOL allow = YES; - + switch (level) { case kGTMLoggerLevelDebug: allow = NO; diff --git a/Foundation/GTMLoggerTest.m b/Foundation/GTMLoggerTest.m index 940014f..0dc7689 100644 --- a/Foundation/GTMLoggerTest.m +++ b/Foundation/GTMLoggerTest.m @@ -348,6 +348,38 @@ return msg; } +- (void)testFunctionPrettifier { + GTMLogBasicFormatter *fmtr = [[[GTMLogBasicFormatter alloc] init] + autorelease]; + STAssertNotNil(fmtr, nil); + + // Nil, empty and whitespace + STAssertEqualObjects([fmtr prettyNameForFunc:nil], @"(unknown)", nil); + STAssertEqualObjects([fmtr prettyNameForFunc:@""], @"(unknown)", nil); + STAssertEqualObjects([fmtr prettyNameForFunc:@" \n\t"], @"(unknown)", nil); + + // C99 __func__ + STAssertEqualObjects([fmtr prettyNameForFunc:@"main"], @"main()", nil); + STAssertEqualObjects([fmtr prettyNameForFunc:@"main"], @"main()", nil); + STAssertEqualObjects([fmtr prettyNameForFunc:@" main "], @"main()", nil); + + // GCC Obj-C __func__ and __PRETTY_FUNCTION__ + STAssertEqualObjects([fmtr prettyNameForFunc:@"+[Foo bar]"], @"+[Foo bar]", + nil); + STAssertEqualObjects([fmtr prettyNameForFunc:@" +[Foo bar] "], @"+[Foo bar]", + nil); + STAssertEqualObjects([fmtr prettyNameForFunc:@"-[Foo baz]"], @"-[Foo baz]", + nil); + STAssertEqualObjects([fmtr prettyNameForFunc:@" -[Foo baz] "], @"-[Foo baz]", + nil); + + // GCC C++ __PRETTY_FUNCTION__ + STAssertEqualObjects([fmtr prettyNameForFunc:@"void a::sub(int)"], + @"void a::sub(int)", nil); + STAssertEqualObjects([fmtr prettyNameForFunc:@" void a::sub(int) "], + @"void a::sub(int)", nil); +} + - (void)testBasicFormatter { id<GTMLogFormatter> fmtr = [[[GTMLogBasicFormatter alloc] init] autorelease]; STAssertNotNil(fmtr, nil); @@ -388,16 +420,16 @@ if ([GTMSystemVersion isSnowLeopardOrGreater]) { // E.g. 2009-10-26 22:26:25.086 otest-i386[53200/0xa0438500] [lvl=1] (no func) test kFormatBasePattern = - @"[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3} ((otest-i386)|(otest-x86_64)|(otest-ppc))\\[[0-9]+/0x[0-9a-f]+\\] \\[lvl=[0-3]\\] \\(no func\\) "; + @"[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3} ((otest-i386)|(otest-x86_64)|(otest-ppc))\\[[0-9]+/0x[0-9a-f]+\\] \\[lvl=[0-3]\\] \\(unknown\\) "; } else { // E.g. 2008-01-04 09:16:26.906 otest[5567/0xa07d0f60] [lvl=1] (no func) test kFormatBasePattern = - @"[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3} (otest)\\[[0-9]+/0x[0-9a-f]+\\] \\[lvl=[0-3]\\] \\(no func\\) "; + @"[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3} (otest)\\[[0-9]+/0x[0-9a-f]+\\] \\[lvl=[0-3]\\] \\(unknown\\) "; } #else // GTM_MACOS_SDK // E.g. 2008-01-04 09:16:26.906 otest[5567/0xa07d0f60] [lvl=1] (no func) test kFormatBasePattern = - @"[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3} (GTMiPhoneTest)\\[[0-9]+/0x[0-9a-f]+\\] \\[lvl=[0-3]\\] \\(no func\\) "; + @"[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3} (GTMiPhoneTest)\\[[0-9]+/0x[0-9a-f]+\\] \\[lvl=[0-3]\\] \\(unknown\\) "; #endif // GTM_MACOS_SDK NSString *msg = nil; @@ -405,27 +437,27 @@ msg = [self stringFromFormatter:fmtr level:kGTMLoggerLevelDebug format:@"test"]; - STAssertTrue([msg gtm_matchesPattern:[kFormatBasePattern stringByAppendingString:@"test"]], + STAssertTrue([msg gtm_matchesPattern:[kFormatBasePattern stringByAppendingString:@"test"]], @"msg: %@", msg); msg = [self stringFromFormatter:fmtr level:kGTMLoggerLevelError format:@"test %d", 1]; - STAssertTrue([msg gtm_matchesPattern:[kFormatBasePattern stringByAppendingString:@"test 1"]], + STAssertTrue([msg gtm_matchesPattern:[kFormatBasePattern stringByAppendingString:@"test 1"]], @"msg: %@", msg); msg = [self stringFromFormatter:fmtr level:kGTMLoggerLevelInfo format:@"test %@", @"hi"]; - STAssertTrue([msg gtm_matchesPattern:[kFormatBasePattern stringByAppendingString:@"test hi"]], + STAssertTrue([msg gtm_matchesPattern:[kFormatBasePattern stringByAppendingString:@"test hi"]], @"msg: %@", msg); msg = [self stringFromFormatter:fmtr level:kGTMLoggerLevelUnknown format:@"test"]; - STAssertTrue([msg gtm_matchesPattern:[kFormatBasePattern stringByAppendingString:@"test"]], + STAssertTrue([msg gtm_matchesPattern:[kFormatBasePattern stringByAppendingString:@"test"]], @"msg: %@", msg); } |