/ src / common / mac / GTMLogger.m
GTMLogger.m
  1  //
  2  //  GTMLogger.m
  3  //
  4  //  Copyright 2007-2008 Google LLC
  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