diff options
Diffstat (limited to 'src/common/mac/GTMLogger.m')
-rw-r--r-- | src/common/mac/GTMLogger.m | 371 |
1 files changed, 269 insertions, 102 deletions
diff --git a/src/common/mac/GTMLogger.m b/src/common/mac/GTMLogger.m index de941d2e..4b40747b 100644 --- a/src/common/mac/GTMLogger.m +++ b/src/common/mac/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 @@ -24,39 +24,30 @@ #import <pthread.h> -// Define a trivial assertion macro to avoid dependencies -#ifdef DEBUG - #define GTMLOGGER_ASSERT(expr) assert(expr) -#else - #define GTMLOGGER_ASSERT(expr) -#endif - - -@interface GTMLogger (PrivateMethods) - -- (void)logInternalFunc:(const char *)func - format:(NSString *)fmt - valist:(va_list)args - level:(GTMLoggerLevel)level; - -@end - +#if !defined(__clang__) && (__GNUC__*10+__GNUC_MINOR__ >= 42) +// Some versions of GCC (4.2 and below AFAIK) aren't great about supporting +// -Wmissing-format-attribute +// when the function is anything more complex than foo(NSString *fmt, ...). +// You see the error inside the function when you turn ... into va_args and +// attempt to call another function (like vsprintf for example). +// So we just shut off the warning for this file. We reenable it at the end. +#pragma GCC diagnostic ignored "-Wmissing-format-attribute" +#endif // !__clang__ -// 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) { if (gSharedLogger == nil) { gSharedLogger = [[self standardLogger] retain]; } - GTMLOGGER_ASSERT(gSharedLogger != nil); } return [[gSharedLogger retain] autorelease]; } @@ -69,24 +60,85 @@ static GTMLogger *gSharedLogger = nil; } + (id)standardLogger { - id<GTMLogWriter> writer = [NSFileHandle fileHandleWithStandardOutput]; - id<GTMLogFormatter> fr = [[[GTMLogStandardFormatter alloc] init] autorelease]; - id<GTMLogFilter> filter = [[[GTMLogLevelFilter alloc] init] autorelease]; - return [self loggerWithWriter:writer formatter:fr filter:filter]; + // Don't trust NSFileHandle not to throw + @try { + id<GTMLogWriter> writer = [NSFileHandle fileHandleWithStandardOutput]; + id<GTMLogFormatter> fr = [[[GTMLogStandardFormatter alloc] init] + autorelease]; + id<GTMLogFilter> filter = [[[GTMLogLevelFilter alloc] init] autorelease]; + return [[[self alloc] initWithWriter:writer + formatter:fr + filter:filter] autorelease]; + } + @catch (id e) { + // Ignored + } + return nil; } + (id)standardLoggerWithStderr { - id me = [self standardLogger]; - [me setWriter:[NSFileHandle fileHandleWithStandardError]]; - return me; + // Don't trust NSFileHandle not to throw + @try { + id me = [self standardLogger]; + [me setWriter:[NSFileHandle fileHandleWithStandardError]]; + return me; + } + @catch (id e) { + // Ignored + } + return nil; +} + ++ (id)standardLoggerWithStdoutAndStderr { + // We're going to take advantage of the GTMLogger to GTMLogWriter adaptor + // and create a composite logger that an outer "standard" logger can use + // as a writer. Our inner loggers should apply no formatting since the main + // logger does that and we want the caller to be able to change formatters + // or add writers without knowing the inner structure of our composite. + + // Don't trust NSFileHandle not to throw + @try { + GTMLogBasicFormatter *formatter = [[[GTMLogBasicFormatter alloc] init] + autorelease]; + GTMLogger *stdoutLogger = + [self loggerWithWriter:[NSFileHandle fileHandleWithStandardOutput] + formatter:formatter + filter:[[[GTMLogMaximumLevelFilter alloc] + initWithMaximumLevel:kGTMLoggerLevelInfo] + autorelease]]; + GTMLogger *stderrLogger = + [self loggerWithWriter:[NSFileHandle fileHandleWithStandardError] + formatter:formatter + filter:[[[GTMLogMininumLevelFilter alloc] + initWithMinimumLevel:kGTMLoggerLevelError] + autorelease]]; + GTMLogger *compositeWriter = + [self loggerWithWriter:[NSArray arrayWithObjects: + stdoutLogger, stderrLogger, nil] + formatter:formatter + filter:[[[GTMLogNoFilter alloc] init] autorelease]]; + GTMLogger *outerLogger = [self standardLogger]; + [outerLogger setWriter:compositeWriter]; + return outerLogger; + } + @catch (id e) { + // Ignored + } + return nil; } + (id)standardLoggerWithPath:(NSString *)path { - NSFileHandle *fh = [NSFileHandle fileHandleForLoggingAtPath:path mode:0644]; - if (fh == nil) return nil; - id me = [self standardLogger]; - [me setWriter:fh]; - return me; + @try { + NSFileHandle *fh = [NSFileHandle fileHandleForLoggingAtPath:path mode:0644]; + if (fh == nil) return nil; + id me = [self standardLogger]; + [me setWriter:fh]; + return me; + } + @catch (id e) { + // Ignored + } + return nil; } + (id)loggerWithWriter:(id<GTMLogWriter>)writer @@ -112,69 +164,85 @@ static GTMLogger *gSharedLogger = nil; [self setWriter:writer]; [self setFormatter:formatter]; [self setFilter:filter]; - GTMLOGGER_ASSERT(formatter_ != nil); - GTMLOGGER_ASSERT(filter_ != nil); - GTMLOGGER_ASSERT(writer_ != nil); } return self; } - (void)dealloc { - GTMLOGGER_ASSERT(writer_ != nil); - GTMLOGGER_ASSERT(formatter_ != nil); - GTMLOGGER_ASSERT(filter_ != nil); - [writer_ release]; - [formatter_ release]; - [filter_ release]; + // Unlikely, but |writer_| may be an NSFileHandle, which can throw + @try { + [formatter_ release]; + [filter_ release]; + [writer_ release]; + } + @catch (id e) { + // Ignored + } [super dealloc]; } - (id<GTMLogWriter>)writer { - GTMLOGGER_ASSERT(writer_ != nil); return [[writer_ retain] autorelease]; } - (void)setWriter:(id<GTMLogWriter>)writer { @synchronized(self) { [writer_ autorelease]; - if (writer == nil) - writer_ = [[NSFileHandle fileHandleWithStandardOutput] retain]; - else + writer_ = nil; + if (writer == nil) { + // Try to use stdout, but don't trust NSFileHandle + @try { + writer_ = [[NSFileHandle fileHandleWithStandardOutput] retain]; + } + @catch (id e) { + // Leave |writer_| nil + } + } else { writer_ = [writer retain]; + } } - GTMLOGGER_ASSERT(writer_ != nil); } - (id<GTMLogFormatter>)formatter { - GTMLOGGER_ASSERT(formatter_ != nil); return [[formatter_ retain] autorelease]; } - (void)setFormatter:(id<GTMLogFormatter>)formatter { @synchronized(self) { [formatter_ autorelease]; - if (formatter == nil) - formatter_ = [[GTMLogBasicFormatter alloc] init]; - else + formatter_ = nil; + if (formatter == nil) { + @try { + formatter_ = [[GTMLogBasicFormatter alloc] init]; + } + @catch (id e) { + // Leave |formatter_| nil + } + } else { formatter_ = [formatter retain]; + } } - GTMLOGGER_ASSERT(formatter_ != nil); } - (id<GTMLogFilter>)filter { - GTMLOGGER_ASSERT(filter_ != nil); return [[filter_ retain] autorelease]; } - (void)setFilter:(id<GTMLogFilter>)filter { @synchronized(self) { [filter_ autorelease]; - if (filter == nil) - filter_ = [[GTMLogNoFilter alloc] init]; - else + filter_ = nil; + if (filter == nil) { + @try { + filter_ = [[GTMLogNoFilter alloc] init]; + } + @catch (id e) { + // Leave |filter_| nil + } + } else { filter_ = [filter retain]; + } } - GTMLOGGER_ASSERT(filter_ != nil); } - (void)logDebug:(NSString *)fmt, ... { @@ -207,7 +275,6 @@ static GTMLogger *gSharedLogger = nil; @end // GTMLogger - @implementation GTMLogger (GTMLoggerMacroHelpers) - (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ... { @@ -240,24 +307,26 @@ static GTMLogger *gSharedLogger = nil; @end // GTMLoggerMacroHelpers - @implementation GTMLogger (PrivateMethods) - (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 - level:level]; - if (msg && [filter_ filterAllowsMessage:msg level:level]) - [writer_ logMessage:msg level:level]; + // Primary point where logging happens, logging should never throw, catch + // everything. + @try { + NSString *fname = func ? [NSString stringWithUTF8String:func] : nil; + NSString *msg = [formatter_ stringForFunc:fname + withFormat:fmt + valist:args + level:level]; + if (msg && [filter_ filterAllowsMessage:msg level:level]) + [writer_ logMessage:msg level:level]; + } + @catch (id e) { + // Ignored + } } @end // PrivateMethods @@ -278,8 +347,16 @@ 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. Catch here + // as well [GTMLogger logInternalFunc:...] so that an exception in this + // writer does not prevent other writers from having a chance. + @try { + NSString *line = [NSString stringWithFormat:@"%@\n", msg]; + [self writeData:[line dataUsingEncoding:NSUTF8StringEncoding]]; + } + @catch (id e) { + // Ignored + } } } @@ -306,18 +383,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,19 +405,32 @@ 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: since we always have to create a new NSString from the - // returned CFStringRef, we may want to do a quick check here to see if |fmt| - // contains a '%', and if not, simply return 'fmt'. - CFStringRef cfmsg = NULL; - cfmsg = CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, - NULL, // format options - (CFStringRef)fmt, - args); - return GTMCFAutorelease(cfmsg); + // Performance note: We may want to do a quick check here to see if |fmt| + // contains a '%', and if not, simply return 'fmt'. + if (!(fmt && args)) return nil; + return [[[NSString alloc] initWithFormat:fmt arguments:args] autorelease]; } @end // GTMLogBasicFormatter @@ -355,6 +445,10 @@ static GTMLogger *gSharedLogger = nil; [dateFormatter_ setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"]; pname_ = [[[NSProcessInfo processInfo] processName] copy]; pid_ = [[NSProcessInfo processInfo] processIdentifier]; + if (!(dateFormatter_ && pname_)) { + [self release]; + return nil; + } } return self; } @@ -367,17 +461,17 @@ 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; @synchronized (dateFormatter_) { tstamp = [dateFormatter_ stringFromDate:[NSDate date]]; } return [NSString stringWithFormat:@"%@ %@[%d/%p] [lvl=%d] %@ %@", - tstamp, pname_, pid_, pthread_self(), - level, (func ? func : @"(no func)"), - [super stringForFunc:func withFormat:fmt valist:args level:level]]; + tstamp, pname_, pid_, pthread_self(), + level, [self prettyNameForFunc:func], + // |super| has guard for nil |fmt| and |args| + [super stringForFunc:func withFormat:fmt valist:args level:level]]; } @end // GTMLogStandardFormatter @@ -391,14 +485,20 @@ static GTMLogger *gSharedLogger = nil; // COV_NF_START static BOOL IsVerboseLoggingEnabled(void) { static NSString *const kVerboseLoggingKey = @"GTMVerboseLogging"; - static char *env = NULL; - if (env == NULL) - env = getenv([kVerboseLoggingKey UTF8String]); - - if (env && env[0]) { - return (strtol(env, NULL, 10) != 0); + NSString *value = [[[NSProcessInfo processInfo] environment] + objectForKey:kVerboseLoggingKey]; + if (value) { + // Emulate [NSString boolValue] for pre-10.5 + value = [value stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if ([[value uppercaseString] hasPrefix:@"Y"] || + [[value uppercaseString] hasPrefix:@"T"] || + [value intValue]) { + return YES; + } else { + return NO; + } } - return [[NSUserDefaults standardUserDefaults] boolForKey:kVerboseLoggingKey]; } // COV_NF_END @@ -409,15 +509,15 @@ static BOOL IsVerboseLoggingEnabled(void) { #if DEBUG return YES; #endif - + BOOL allow = YES; - + switch (level) { case kGTMLoggerLevelDebug: allow = NO; break; case kGTMLoggerLevelInfo: - allow = (IsVerboseLoggingEnabled() == YES); + allow = IsVerboseLoggingEnabled(); break; case kGTMLoggerLevelError: allow = YES; @@ -443,3 +543,70 @@ static BOOL IsVerboseLoggingEnabled(void) { } @end // GTMLogNoFilter + + +@implementation GTMLogAllowedLevelFilter + +// Private designated initializer +- (id)initWithAllowedLevels:(NSIndexSet *)levels { + self = [super init]; + if (self != nil) { + allowedLevels_ = [levels retain]; + // Cap min/max level + if (!allowedLevels_ || + // NSIndexSet is unsigned so only check the high bound, but need to + // check both first and last index because NSIndexSet appears to allow + // wraparound. + ([allowedLevels_ firstIndex] > kGTMLoggerLevelAssert) || + ([allowedLevels_ lastIndex] > kGTMLoggerLevelAssert)) { + [self release]; + return nil; + } + } + return self; +} + +- (id)init { + // Allow all levels in default init + return [self initWithAllowedLevels:[NSIndexSet indexSetWithIndexesInRange: + NSMakeRange(kGTMLoggerLevelUnknown, + (kGTMLoggerLevelAssert - kGTMLoggerLevelUnknown + 1))]]; +} + +- (void)dealloc { + [allowedLevels_ release]; + [super dealloc]; +} + +- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level { + return [allowedLevels_ containsIndex:level]; +} + +@end // GTMLogAllowedLevelFilter + + +@implementation GTMLogMininumLevelFilter + +- (id)initWithMinimumLevel:(GTMLoggerLevel)level { + return [super initWithAllowedLevels:[NSIndexSet indexSetWithIndexesInRange: + NSMakeRange(level, + (kGTMLoggerLevelAssert - level + 1))]]; +} + +@end // GTMLogMininumLevelFilter + + +@implementation GTMLogMaximumLevelFilter + +- (id)initWithMaximumLevel:(GTMLoggerLevel)level { + return [super initWithAllowedLevels:[NSIndexSet indexSetWithIndexesInRange: + NSMakeRange(kGTMLoggerLevelUnknown, level + 1)]]; +} + +@end // GTMLogMaximumLevelFilter + +#if !defined(__clang__) && (__GNUC__*10+__GNUC_MINOR__ >= 42) +// See comment at top of file. +#pragma GCC diagnostic error "-Wmissing-format-attribute" +#endif // !__clang__ + |