Home | History | Annotate | Download | only in mac
      1 //
      2 //  GTMLogger.m
      3 //
      4 //  Copyright 2007-2008 Google Inc.
      5 //
      6 //  Licensed under the Apache License, Version 2.0 (the "License"); you may not
      7 //  use this file except in compliance with the License.  You may obtain a copy
      8 //  of the License at
      9 //
     10 //  http://www.apache.org/licenses/LICENSE-2.0
     11 //
     12 //  Unless required by applicable law or agreed to in writing, software
     13 //  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     14 //  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
     15 //  License for the specific language governing permissions and limitations under
     16 //  the License.
     17 //
     18 
     19 #import "GTMLogger.h"
     20 #import <fcntl.h>
     21 #import <unistd.h>
     22 #import <stdlib.h>
     23 #import <pthread.h>
     24 
     25 
     26 #if !defined(__clang__) && (__GNUC__*10+__GNUC_MINOR__ >= 42)
     27 // Some versions of GCC (4.2 and below AFAIK) aren't great about supporting
     28 // -Wmissing-format-attribute
     29 // when the function is anything more complex than foo(NSString *fmt, ...).
     30 // You see the error inside the function when you turn ... into va_args and
     31 // attempt to call another function (like vsprintf for example).
     32 // So we just shut off the warning for this file. We reenable it at the end.
     33 #pragma GCC diagnostic ignored "-Wmissing-format-attribute"
     34 #endif  // !__clang__
     35 
     36 // Reference to the shared GTMLogger instance. This is not a singleton, it's
     37 // just an easy reference to one shared instance.
     38 static GTMLogger *gSharedLogger = nil;
     39 
     40 
     41 @implementation GTMLogger
     42 
     43 // Returns a pointer to the shared logger instance. If none exists, a standard
     44 // logger is created and returned.
     45 + (id)sharedLogger {
     46   @synchronized(self) {
     47     if (gSharedLogger == nil) {
     48       gSharedLogger = [[self standardLogger] retain];
     49     }
     50   }
     51   return [[gSharedLogger retain] autorelease];
     52 }
     53 
     54 + (void)setSharedLogger:(GTMLogger *)logger {
     55   @synchronized(self) {
     56     [gSharedLogger autorelease];
     57     gSharedLogger = [logger retain];
     58   }
     59 }
     60 
     61 + (id)standardLogger {
     62   // Don't trust NSFileHandle not to throw
     63   @try {
     64     id<GTMLogWriter> writer = [NSFileHandle fileHandleWithStandardOutput];
     65     id<GTMLogFormatter> fr = [[[GTMLogStandardFormatter alloc] init]
     66                                  autorelease];
     67     id<GTMLogFilter> filter = [[[GTMLogLevelFilter alloc] init] autorelease];
     68     return [[[self alloc] initWithWriter:writer
     69                                formatter:fr
     70                                   filter:filter] autorelease];
     71   }
     72   @catch (id e) {
     73     // Ignored
     74   }
     75   return nil;
     76 }
     77 
     78 + (id)standardLoggerWithStderr {
     79   // Don't trust NSFileHandle not to throw
     80   @try {
     81     id me = [self standardLogger];
     82     [me setWriter:[NSFileHandle fileHandleWithStandardError]];
     83     return me;
     84   }
     85   @catch (id e) {
     86     // Ignored
     87   }
     88   return nil;
     89 }
     90 
     91 + (id)standardLoggerWithStdoutAndStderr {
     92   // We're going to take advantage of the GTMLogger to GTMLogWriter adaptor
     93   // and create a composite logger that an outer "standard" logger can use
     94   // as a writer. Our inner loggers should apply no formatting since the main
     95   // logger does that and we want the caller to be able to change formatters
     96   // or add writers without knowing the inner structure of our composite.
     97 
     98   // Don't trust NSFileHandle not to throw
     99   @try {
    100     GTMLogBasicFormatter *formatter = [[[GTMLogBasicFormatter alloc] init] 
    101                                           autorelease];
    102     GTMLogger *stdoutLogger =
    103         [self loggerWithWriter:[NSFileHandle fileHandleWithStandardOutput]
    104                      formatter:formatter
    105                         filter:[[[GTMLogMaximumLevelFilter alloc]
    106                                   initWithMaximumLevel:kGTMLoggerLevelInfo]
    107                                       autorelease]];
    108     GTMLogger *stderrLogger =
    109         [self loggerWithWriter:[NSFileHandle fileHandleWithStandardError]
    110                      formatter:formatter
    111                         filter:[[[GTMLogMininumLevelFilter alloc]
    112                                   initWithMinimumLevel:kGTMLoggerLevelError]
    113                                       autorelease]];
    114     GTMLogger *compositeWriter =
    115         [self loggerWithWriter:[NSArray arrayWithObjects:
    116                                    stdoutLogger, stderrLogger, nil]
    117                      formatter:formatter
    118                         filter:[[[GTMLogNoFilter alloc] init] autorelease]];
    119     GTMLogger *outerLogger = [self standardLogger];
    120     [outerLogger setWriter:compositeWriter];
    121     return outerLogger;
    122   }
    123   @catch (id e) {
    124     // Ignored
    125   }
    126   return nil;
    127 }
    128 
    129 + (id)standardLoggerWithPath:(NSString *)path {
    130   @try {
    131     NSFileHandle *fh = [NSFileHandle fileHandleForLoggingAtPath:path mode:0644];
    132     if (fh == nil) return nil;
    133     id me = [self standardLogger];
    134     [me setWriter:fh];
    135     return me;
    136   }
    137   @catch (id e) {
    138     // Ignored
    139   }
    140   return nil;
    141 }
    142 
    143 + (id)loggerWithWriter:(id<GTMLogWriter>)writer
    144              formatter:(id<GTMLogFormatter>)formatter
    145                 filter:(id<GTMLogFilter>)filter {
    146   return [[[self alloc] initWithWriter:writer
    147                              formatter:formatter
    148                                 filter:filter] autorelease];
    149 }
    150 
    151 + (id)logger {
    152   return [[[self alloc] init] autorelease];
    153 }
    154 
    155 - (id)init {
    156   return [self initWithWriter:nil formatter:nil filter:nil];
    157 }
    158 
    159 - (id)initWithWriter:(id<GTMLogWriter>)writer
    160            formatter:(id<GTMLogFormatter>)formatter
    161               filter:(id<GTMLogFilter>)filter {
    162   if ((self = [super init])) {
    163     [self setWriter:writer];
    164     [self setFormatter:formatter];
    165     [self setFilter:filter];
    166   }
    167   return self;
    168 }
    169 
    170 - (void)dealloc {
    171   // Unlikely, but |writer_| may be an NSFileHandle, which can throw
    172   @try {
    173     [formatter_ release];
    174     [filter_ release];
    175     [writer_ release];
    176   }
    177   @catch (id e) {
    178     // Ignored
    179   }
    180   [super dealloc];
    181 }
    182 
    183 - (id<GTMLogWriter>)writer {
    184   return [[writer_ retain] autorelease];
    185 }
    186 
    187 - (void)setWriter:(id<GTMLogWriter>)writer {
    188   @synchronized(self) {
    189     [writer_ autorelease];
    190     writer_ = nil;
    191     if (writer == nil) {
    192       // Try to use stdout, but don't trust NSFileHandle
    193       @try {
    194         writer_ = [[NSFileHandle fileHandleWithStandardOutput] retain];
    195       }
    196       @catch (id e) {
    197         // Leave |writer_| nil
    198       }
    199     } else {
    200       writer_ = [writer retain];
    201     }
    202   }
    203 }
    204 
    205 - (id<GTMLogFormatter>)formatter {
    206   return [[formatter_ retain] autorelease];
    207 }
    208 
    209 - (void)setFormatter:(id<GTMLogFormatter>)formatter {
    210   @synchronized(self) {
    211     [formatter_ autorelease];
    212     formatter_ = nil;
    213     if (formatter == nil) {
    214       @try {
    215         formatter_ = [[GTMLogBasicFormatter alloc] init];
    216       }
    217       @catch (id e) {
    218         // Leave |formatter_| nil
    219       }
    220     } else {
    221       formatter_ = [formatter retain];
    222     }
    223   }
    224 }
    225 
    226 - (id<GTMLogFilter>)filter {
    227   return [[filter_ retain] autorelease];
    228 }
    229 
    230 - (void)setFilter:(id<GTMLogFilter>)filter {
    231   @synchronized(self) {
    232     [filter_ autorelease];
    233     filter_ = nil;
    234     if (filter == nil) {
    235       @try {
    236         filter_ = [[GTMLogNoFilter alloc] init];
    237       }
    238       @catch (id e) {
    239         // Leave |filter_| nil
    240       }
    241     } else {
    242       filter_ = [filter retain];
    243     }
    244   }
    245 }
    246 
    247 - (void)logDebug:(NSString *)fmt, ... {
    248   va_list args;
    249   va_start(args, fmt);
    250   [self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelDebug];
    251   va_end(args);
    252 }
    253 
    254 - (void)logInfo:(NSString *)fmt, ... {
    255   va_list args;
    256   va_start(args, fmt);
    257   [self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelInfo];
    258   va_end(args);
    259 }
    260 
    261 - (void)logError:(NSString *)fmt, ... {
    262   va_list args;
    263   va_start(args, fmt);
    264   [self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelError];
    265   va_end(args);
    266 }
    267 
    268 - (void)logAssert:(NSString *)fmt, ... {
    269   va_list args;
    270   va_start(args, fmt);
    271   [self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelAssert];
    272   va_end(args);
    273 }
    274 
    275 @end  // GTMLogger
    276 
    277 @implementation GTMLogger (GTMLoggerMacroHelpers)
    278 
    279 - (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ... {
    280   va_list args;
    281   va_start(args, fmt);
    282   [self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelDebug];
    283   va_end(args);
    284 }
    285 
    286 - (void)logFuncInfo:(const char *)func msg:(NSString *)fmt, ... {
    287   va_list args;
    288   va_start(args, fmt);
    289   [self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelInfo];
    290   va_end(args);
    291 }
    292 
    293 - (void)logFuncError:(const char *)func msg:(NSString *)fmt, ... {
    294   va_list args;
    295   va_start(args, fmt);
    296   [self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelError];
    297   va_end(args);
    298 }
    299 
    300 - (void)logFuncAssert:(const char *)func msg:(NSString *)fmt, ... {
    301   va_list args;
    302   va_start(args, fmt);
    303   [self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelAssert];
    304   va_end(args);
    305 }
    306 
    307 @end  // GTMLoggerMacroHelpers
    308 
    309 @implementation GTMLogger (PrivateMethods)
    310 
    311 - (void)logInternalFunc:(const char *)func
    312                  format:(NSString *)fmt
    313                  valist:(va_list)args
    314                   level:(GTMLoggerLevel)level {
    315   // Primary point where logging happens, logging should never throw, catch
    316   // everything.
    317   @try {
    318     NSString *fname = func ? [NSString stringWithUTF8String:func] : nil;
    319     NSString *msg = [formatter_ stringForFunc:fname
    320                                    withFormat:fmt
    321                                        valist:args
    322                                         level:level];
    323     if (msg && [filter_ filterAllowsMessage:msg level:level])
    324       [writer_ logMessage:msg level:level];
    325   }
    326   @catch (id e) {
    327     // Ignored
    328   }
    329 }
    330 
    331 @end  // PrivateMethods
    332 
    333 
    334 @implementation NSFileHandle (GTMFileHandleLogWriter)
    335 
    336 + (id)fileHandleForLoggingAtPath:(NSString *)path mode:(mode_t)mode {
    337   int fd = -1;
    338   if (path) {
    339     int flags = O_WRONLY | O_APPEND | O_CREAT;
    340     fd = open([path fileSystemRepresentation], flags, mode);
    341   }
    342   if (fd == -1) return nil;
    343   return [[[self alloc] initWithFileDescriptor:fd
    344                                 closeOnDealloc:YES] autorelease];
    345 }
    346 
    347 - (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
    348   @synchronized(self) {
    349     // Closed pipes should not generate exceptions in our caller. Catch here
    350     // as well [GTMLogger logInternalFunc:...] so that an exception in this
    351     // writer does not prevent other writers from having a chance.
    352     @try {
    353       NSString *line = [NSString stringWithFormat:@"%@\n", msg];
    354       [self writeData:[line dataUsingEncoding:NSUTF8StringEncoding]];
    355     }
    356     @catch (id e) {
    357       // Ignored
    358     }
    359   }
    360 }
    361 
    362 @end  // GTMFileHandleLogWriter
    363 
    364 
    365 @implementation NSArray (GTMArrayCompositeLogWriter)
    366 
    367 - (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
    368   @synchronized(self) {
    369     id<GTMLogWriter> child = nil;
    370     GTM_FOREACH_OBJECT(child, self) {
    371       if ([child conformsToProtocol:@protocol(GTMLogWriter)])
    372         [child logMessage:msg level:level];
    373     }
    374   }
    375 }
    376 
    377 @end  // GTMArrayCompositeLogWriter
    378 
    379 
    380 @implementation GTMLogger (GTMLoggerLogWriter)
    381 
    382 - (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
    383   switch (level) {
    384     case kGTMLoggerLevelDebug:
    385       [self logDebug:@"%@", msg];
    386       break;
    387     case kGTMLoggerLevelInfo:
    388       [self logInfo:@"%@", msg];
    389       break;
    390     case kGTMLoggerLevelError:
    391       [self logError:@"%@", msg];
    392       break;
    393     case kGTMLoggerLevelAssert:
    394       [self logAssert:@"%@", msg];
    395       break;
    396     default:
    397       // Ignore the message.
    398       break;
    399   }
    400 }
    401 
    402 @end  // GTMLoggerLogWriter
    403 
    404 
    405 @implementation GTMLogBasicFormatter
    406 
    407 - (NSString *)prettyNameForFunc:(NSString *)func {
    408   NSString *name = [func stringByTrimmingCharactersInSet:
    409                      [NSCharacterSet whitespaceAndNewlineCharacterSet]];
    410   NSString *function = @"(unknown)";
    411   if ([name length]) {
    412     if (// Objective C __func__ and __PRETTY_FUNCTION__
    413         [name hasPrefix:@"-["] || [name hasPrefix:@"+["] ||
    414         // C++ __PRETTY_FUNCTION__ and other preadorned formats
    415         [name hasSuffix:@")"]) {
    416       function = name;
    417     } else {
    418       // Assume C99 __func__
    419       function = [NSString stringWithFormat:@"%@()", name];
    420     }
    421   }
    422   return function;
    423 }
    424 
    425 - (NSString *)stringForFunc:(NSString *)func
    426                  withFormat:(NSString *)fmt
    427                      valist:(va_list)args
    428                       level:(GTMLoggerLevel)level {
    429   // Performance note: We may want to do a quick check here to see if |fmt|
    430   // contains a '%', and if not, simply return 'fmt'.
    431   if (!(fmt && args)) return nil;
    432   return [[[NSString alloc] initWithFormat:fmt arguments:args] autorelease];
    433 }
    434 
    435 @end  // GTMLogBasicFormatter
    436 
    437 
    438 @implementation GTMLogStandardFormatter
    439 
    440 - (id)init {
    441   if ((self = [super init])) {
    442     dateFormatter_ = [[NSDateFormatter alloc] init];
    443     [dateFormatter_ setFormatterBehavior:NSDateFormatterBehavior10_4];
    444     [dateFormatter_ setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"];
    445     pname_ = [[[NSProcessInfo processInfo] processName] copy];
    446     pid_ = [[NSProcessInfo processInfo] processIdentifier];
    447     if (!(dateFormatter_ && pname_)) {
    448       [self release];
    449       return nil;
    450     }
    451   }
    452   return self;
    453 }
    454 
    455 - (void)dealloc {
    456   [dateFormatter_ release];
    457   [pname_ release];
    458   [super dealloc];
    459 }
    460 
    461 - (NSString *)stringForFunc:(NSString *)func
    462                  withFormat:(NSString *)fmt
    463                      valist:(va_list)args
    464                       level:(GTMLoggerLevel)level {
    465   NSString *tstamp = nil;
    466   @synchronized (dateFormatter_) {
    467     tstamp = [dateFormatter_ stringFromDate:[NSDate date]];
    468   }
    469   return [NSString stringWithFormat:@"%@ %@[%d/%p] [lvl=%d] %@ %@",
    470            tstamp, pname_, pid_, pthread_self(),
    471            level, [self prettyNameForFunc:func],
    472            // |super| has guard for nil |fmt| and |args|
    473            [super stringForFunc:func withFormat:fmt valist:args level:level]];
    474 }
    475 
    476 @end  // GTMLogStandardFormatter
    477 
    478 
    479 @implementation GTMLogLevelFilter
    480 
    481 // Check the environment and the user preferences for the GTMVerboseLogging key
    482 // to see if verbose logging has been enabled. The environment variable will
    483 // override the defaults setting, so check the environment first.
    484 // COV_NF_START
    485 static BOOL IsVerboseLoggingEnabled(void) {
    486   static NSString *const kVerboseLoggingKey = @"GTMVerboseLogging";
    487   NSString *value = [[[NSProcessInfo processInfo] environment]
    488                         objectForKey:kVerboseLoggingKey];
    489   if (value) {
    490     // Emulate [NSString boolValue] for pre-10.5
    491     value = [value stringByTrimmingCharactersInSet:
    492                 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
    493     if ([[value uppercaseString] hasPrefix:@"Y"] ||
    494         [[value uppercaseString] hasPrefix:@"T"] ||
    495         [value intValue]) {
    496       return YES;
    497     } else {
    498       return NO;
    499     }
    500   }
    501   return [[NSUserDefaults standardUserDefaults] boolForKey:kVerboseLoggingKey];
    502 }
    503 // COV_NF_END
    504 
    505 // In DEBUG builds, log everything. If we're not in a debug build we'll assume
    506 // that we're in a Release build.
    507 - (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level {
    508 #if defined(DEBUG) && DEBUG
    509   return YES;
    510 #endif
    511 
    512   BOOL allow = YES;
    513 
    514   switch (level) {
    515     case kGTMLoggerLevelDebug:
    516       allow = NO;
    517       break;
    518     case kGTMLoggerLevelInfo:
    519       allow = IsVerboseLoggingEnabled();
    520       break;
    521     case kGTMLoggerLevelError:
    522       allow = YES;
    523       break;
    524     case kGTMLoggerLevelAssert:
    525       allow = YES;
    526       break;
    527     default:
    528       allow = YES;
    529       break;
    530   }
    531 
    532   return allow;
    533 }
    534 
    535 @end  // GTMLogLevelFilter
    536 
    537 
    538 @implementation GTMLogNoFilter
    539 
    540 - (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level {
    541   return YES;  // Allow everything through
    542 }
    543 
    544 @end  // GTMLogNoFilter
    545 
    546 
    547 @implementation GTMLogAllowedLevelFilter
    548 
    549 // Private designated initializer
    550 - (id)initWithAllowedLevels:(NSIndexSet *)levels {
    551   self = [super init];
    552   if (self != nil) {
    553     allowedLevels_ = [levels retain];
    554     // Cap min/max level
    555     if (!allowedLevels_ ||
    556         // NSIndexSet is unsigned so only check the high bound, but need to
    557         // check both first and last index because NSIndexSet appears to allow
    558         // wraparound.
    559         ([allowedLevels_ firstIndex] > kGTMLoggerLevelAssert) ||
    560         ([allowedLevels_ lastIndex] > kGTMLoggerLevelAssert)) {
    561       [self release];
    562       return nil;
    563     }
    564   }
    565   return self;
    566 }
    567 
    568 - (id)init {
    569   // Allow all levels in default init
    570   return [self initWithAllowedLevels:[NSIndexSet indexSetWithIndexesInRange:
    571              NSMakeRange(kGTMLoggerLevelUnknown,
    572                  (kGTMLoggerLevelAssert - kGTMLoggerLevelUnknown + 1))]];
    573 }
    574 
    575 - (void)dealloc {
    576   [allowedLevels_ release];
    577   [super dealloc];
    578 }
    579 
    580 - (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level {
    581   return [allowedLevels_ containsIndex:level];
    582 }
    583 
    584 @end  // GTMLogAllowedLevelFilter
    585 
    586 
    587 @implementation GTMLogMininumLevelFilter
    588 
    589 - (id)initWithMinimumLevel:(GTMLoggerLevel)level {
    590   return [super initWithAllowedLevels:[NSIndexSet indexSetWithIndexesInRange:
    591              NSMakeRange(level,
    592                          (kGTMLoggerLevelAssert - level + 1))]];
    593 }
    594 
    595 @end  // GTMLogMininumLevelFilter
    596 
    597 
    598 @implementation GTMLogMaximumLevelFilter
    599 
    600 - (id)initWithMaximumLevel:(GTMLoggerLevel)level {
    601   return [super initWithAllowedLevels:[NSIndexSet indexSetWithIndexesInRange:
    602              NSMakeRange(kGTMLoggerLevelUnknown, level + 1)]];
    603 }
    604 
    605 @end  // GTMLogMaximumLevelFilter
    606 
    607 #if !defined(__clang__) && (__GNUC__*10+__GNUC_MINOR__ >= 42)
    608 // See comment at top of file.
    609 #pragma GCC diagnostic error "-Wmissing-format-attribute"
    610 #endif  // !__clang__
    611 
    612