aboutsummaryrefslogtreecommitdiff
path: root/src/common/mac/GTMLogger.m
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/mac/GTMLogger.m')
-rw-r--r--src/common/mac/GTMLogger.m371
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__
+