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