// // GTMLogger.m // // Copyright 2007-2008 Google Inc. // // 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 // License for the specific language governing permissions and limitations under // the License. // #import "GTMLogger.h" #import "GTMGarbageCollection.h" #import #import #import #import // 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 // 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 // logger is created and returned. + (id)sharedLogger { @synchronized(self) { if (gSharedLogger == nil) { gSharedLogger = [[self standardLogger] retain]; } GTMLOGGER_ASSERT(gSharedLogger != nil); } return [[gSharedLogger retain] autorelease]; } + (void)setSharedLogger:(GTMLogger *)logger { @synchronized(self) { [gSharedLogger autorelease]; gSharedLogger = [logger retain]; } } + (id)standardLogger { id writer = [NSFileHandle fileHandleWithStandardOutput]; id fr = [[[GTMLogStandardFormatter alloc] init] autorelease]; id filter = [[[GTMLogLevelFilter alloc] init] autorelease]; return [self loggerWithWriter:writer formatter:fr filter:filter]; } + (id)standardLoggerWithStderr { id me = [self standardLogger]; [me setWriter:[NSFileHandle fileHandleWithStandardError]]; return me; } + (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; } + (id)loggerWithWriter:(id)writer formatter:(id)formatter filter:(id)filter { return [[[self alloc] initWithWriter:writer formatter:formatter filter:filter] autorelease]; } + (id)logger { return [[[self alloc] init] autorelease]; } - (id)init { return [self initWithWriter:nil formatter:nil filter:nil]; } - (id)initWithWriter:(id)writer formatter:(id)formatter filter:(id)filter { if ((self = [super init])) { [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]; [super dealloc]; } - (id)writer { GTMLOGGER_ASSERT(writer_ != nil); return [[writer_ retain] autorelease]; } - (void)setWriter:(id)writer { @synchronized(self) { [writer_ autorelease]; if (writer == nil) writer_ = [[NSFileHandle fileHandleWithStandardOutput] retain]; else writer_ = [writer retain]; } GTMLOGGER_ASSERT(writer_ != nil); } - (id)formatter { GTMLOGGER_ASSERT(formatter_ != nil); return [[formatter_ retain] autorelease]; } - (void)setFormatter:(id)formatter { @synchronized(self) { [formatter_ autorelease]; if (formatter == nil) formatter_ = [[GTMLogBasicFormatter alloc] init]; else formatter_ = [formatter retain]; } GTMLOGGER_ASSERT(formatter_ != nil); } - (id)filter { GTMLOGGER_ASSERT(filter_ != nil); return [[filter_ retain] autorelease]; } - (void)setFilter:(id)filter { @synchronized(self) { [filter_ autorelease]; if (filter == nil) filter_ = [[GTMLogNoFilter alloc] init]; else filter_ = [filter retain]; } GTMLOGGER_ASSERT(filter_ != nil); } - (void)logDebug:(NSString *)fmt, ... { va_list args; va_start(args, fmt); [self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelDebug]; va_end(args); } - (void)logInfo:(NSString *)fmt, ... { va_list args; va_start(args, fmt); [self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelInfo]; va_end(args); } - (void)logError:(NSString *)fmt, ... { va_list args; va_start(args, fmt); [self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelError]; va_end(args); } - (void)logAssert:(NSString *)fmt, ... { va_list args; va_start(args, fmt); [self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelAssert]; va_end(args); } @end // GTMLogger @implementation GTMLogger (GTMLoggerMacroHelpers) - (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ... { va_list args; va_start(args, fmt); [self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelDebug]; va_end(args); } - (void)logFuncInfo:(const char *)func msg:(NSString *)fmt, ... { va_list args; va_start(args, fmt); [self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelInfo]; va_end(args); } - (void)logFuncError:(const char *)func msg:(NSString *)fmt, ... { va_list args; va_start(args, fmt); [self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelError]; va_end(args); } - (void)logFuncAssert:(const char *)func msg:(NSString *)fmt, ... { va_list args; va_start(args, fmt); [self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelAssert]; va_end(args); } @end // GTMLoggerMacroHelpers @implementation GTMLogger (PrivateMethods) - (void)logInternalFunc:(const char *)func format:(NSString *)fmt 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]; } @end // PrivateMethods @implementation NSFileHandle (GTMFileHandleLogWriter) + (id)fileHandleForLoggingAtPath:(NSString *)path mode:(mode_t)mode { int fd = -1; if (path) { int flags = O_WRONLY | O_APPEND | O_CREAT; fd = open([path fileSystemRepresentation], flags, mode); } if (fd == -1) return nil; return [[[self alloc] initWithFileDescriptor:fd closeOnDealloc:YES] autorelease]; } - (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level { @synchronized(self) { NSString *line = [NSString stringWithFormat:@"%@\n", msg]; [self writeData:[line dataUsingEncoding:NSUTF8StringEncoding]]; } } @end // GTMFileHandleLogWriter @implementation NSArray (GTMArrayCompositeLogWriter) - (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level { @synchronized(self) { id child = nil; GTM_FOREACH_OBJECT(child, self) { if ([child conformsToProtocol:@protocol(GTMLogWriter)]) [child logMessage:msg level:level]; } } } @end // GTMArrayCompositeLogWriter @implementation GTMLogger (GTMLoggerLogWriter) - (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level { switch (level) { case kGTMLoggerLevelDebug: [self logDebug:@"%@", msg]; break; case kGTMLoggerLevelInfo: [self logInfo:@"%@", msg]; break; case kGTMLoggerLevelError: [self logError:@"%@", msg]; break; case kGTMLoggerLevelAssert: [self logAssert:@"%@", msg]; break; default: // Ignore the message. break; } } @end // GTMLoggerLogWriter @implementation GTMLogBasicFormatter - (NSString *)stringForFunc:(NSString *)func withFormat:(NSString *)fmt 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); } @end // GTMLogBasicFormatter @implementation GTMLogStandardFormatter - (id)init { if ((self = [super init])) { dateFormatter_ = [[NSDateFormatter alloc] init]; [dateFormatter_ setFormatterBehavior:NSDateFormatterBehavior10_4]; [dateFormatter_ setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"]; pname_ = [[[NSProcessInfo processInfo] processName] copy]; pid_ = [[NSProcessInfo processInfo] processIdentifier]; } return self; } - (void)dealloc { [dateFormatter_ release]; [pname_ release]; [super dealloc]; } - (NSString *)stringForFunc:(NSString *)func withFormat:(NSString *)fmt 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]]; } @end // GTMLogStandardFormatter @implementation GTMLogLevelFilter // Check the environment and the user preferences for the GTMVerboseLogging key // to see if verbose logging has been enabled. The environment variable will // override the defaults setting, so check the environment first. // 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); } return [[NSUserDefaults standardUserDefaults] boolForKey:kVerboseLoggingKey]; } // COV_NF_END // In DEBUG builds, log everything. If we're not in a debug build we'll assume // that we're in a Release build. - (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level { #if DEBUG return YES; #endif BOOL allow = YES; switch (level) { case kGTMLoggerLevelDebug: allow = NO; break; case kGTMLoggerLevelInfo: allow = (IsVerboseLoggingEnabled() == YES); break; case kGTMLoggerLevelError: allow = YES; break; case kGTMLoggerLevelAssert: allow = YES; break; default: allow = YES; break; } return allow; } @end // GTMLogLevelFilter @implementation GTMLogNoFilter - (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level { return YES; // Allow everything through } @end // GTMLogNoFilter