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. 38static 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 485static 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