You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

648 lines
18 KiB

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