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