6c23134e47
[UPDATED] Google+ SDK to 1.1.0.
613 lines
17 KiB
Objective-C
613 lines
17 KiB
Objective-C
//
|
|
// GTMLogger.m
|
|
//
|
|
// Copyright 2007-2008 Google Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
// use this file except in compliance with the License. You may obtain a copy
|
|
// of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
// License for the specific language governing permissions and limitations under
|
|
// the License.
|
|
//
|
|
|
|
#import "GTMLogger.h"
|
|
#import "GTMGarbageCollection.h"
|
|
#import <fcntl.h>
|
|
#import <unistd.h>
|
|
#import <stdlib.h>
|
|
#import <pthread.h>
|
|
|
|
|
|
#if !defined(__clang__) && (__GNUC__*10+__GNUC_MINOR__ >= 42)
|
|
// Some versions of GCC (4.2 and below AFAIK) aren't great about supporting
|
|
// -Wmissing-format-attribute
|
|
// when the function is anything more complex than foo(NSString *fmt, ...).
|
|
// You see the error inside the function when you turn ... into va_args and
|
|
// attempt to call another function (like vsprintf for example).
|
|
// So we just shut off the warning for this file. We reenable it at the end.
|
|
#pragma GCC diagnostic ignored "-Wmissing-format-attribute"
|
|
#endif // !__clang__
|
|
|
|
// Reference to the shared GTMLogger instance. This is not a singleton, it's
|
|
// just an easy reference to one shared instance.
|
|
static GTMLogger *gSharedLogger = nil;
|
|
|
|
|
|
@implementation GTMLogger
|
|
|
|
// Returns a pointer to the shared logger instance. If none exists, a standard
|
|
// logger is created and returned.
|
|
+ (id)sharedLogger {
|
|
@synchronized(self) {
|
|
if (gSharedLogger == nil) {
|
|
gSharedLogger = [[self standardLogger] retain];
|
|
}
|
|
}
|
|
return [[gSharedLogger retain] autorelease];
|
|
}
|
|
|
|
+ (void)setSharedLogger:(GTMLogger *)logger {
|
|
@synchronized(self) {
|
|
[gSharedLogger autorelease];
|
|
gSharedLogger = [logger retain];
|
|
}
|
|
}
|
|
|
|
+ (id)standardLogger {
|
|
// Don't trust NSFileHandle not to throw
|
|
@try {
|
|
id<GTMLogWriter> writer = [NSFileHandle fileHandleWithStandardOutput];
|
|
id<GTMLogFormatter> fr = [[[GTMLogStandardFormatter alloc] init]
|
|
autorelease];
|
|
id<GTMLogFilter> filter = [[[GTMLogLevelFilter alloc] init] autorelease];
|
|
return [[[self alloc] initWithWriter:writer
|
|
formatter:fr
|
|
filter:filter] autorelease];
|
|
}
|
|
@catch (id e) {
|
|
// Ignored
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
+ (id)standardLoggerWithStderr {
|
|
// Don't trust NSFileHandle not to throw
|
|
@try {
|
|
id me = [self standardLogger];
|
|
[me setWriter:[NSFileHandle fileHandleWithStandardError]];
|
|
return me;
|
|
}
|
|
@catch (id e) {
|
|
// Ignored
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
+ (id)standardLoggerWithStdoutAndStderr {
|
|
// We're going to take advantage of the GTMLogger to GTMLogWriter adaptor
|
|
// and create a composite logger that an outer "standard" logger can use
|
|
// as a writer. Our inner loggers should apply no formatting since the main
|
|
// logger does that and we want the caller to be able to change formatters
|
|
// or add writers without knowing the inner structure of our composite.
|
|
|
|
// Don't trust NSFileHandle not to throw
|
|
@try {
|
|
GTMLogBasicFormatter *formatter = [[[GTMLogBasicFormatter alloc] init]
|
|
autorelease];
|
|
GTMLogger *stdoutLogger =
|
|
[self loggerWithWriter:[NSFileHandle fileHandleWithStandardOutput]
|
|
formatter:formatter
|
|
filter:[[[GTMLogMaximumLevelFilter alloc]
|
|
initWithMaximumLevel:kGTMLoggerLevelInfo]
|
|
autorelease]];
|
|
GTMLogger *stderrLogger =
|
|
[self loggerWithWriter:[NSFileHandle fileHandleWithStandardError]
|
|
formatter:formatter
|
|
filter:[[[GTMLogMininumLevelFilter alloc]
|
|
initWithMinimumLevel:kGTMLoggerLevelError]
|
|
autorelease]];
|
|
GTMLogger *compositeWriter =
|
|
[self loggerWithWriter:[NSArray arrayWithObjects:
|
|
stdoutLogger, stderrLogger, nil]
|
|
formatter:formatter
|
|
filter:[[[GTMLogNoFilter alloc] init] autorelease]];
|
|
GTMLogger *outerLogger = [self standardLogger];
|
|
[outerLogger setWriter:compositeWriter];
|
|
return outerLogger;
|
|
}
|
|
@catch (id e) {
|
|
// Ignored
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
+ (id)standardLoggerWithPath:(NSString *)path {
|
|
@try {
|
|
NSFileHandle *fh = [NSFileHandle fileHandleForLoggingAtPath:path mode:0644];
|
|
if (fh == nil) return nil;
|
|
id me = [self standardLogger];
|
|
[me setWriter:fh];
|
|
return me;
|
|
}
|
|
@catch (id e) {
|
|
// Ignored
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
+ (id)loggerWithWriter:(id<GTMLogWriter>)writer
|
|
formatter:(id<GTMLogFormatter>)formatter
|
|
filter:(id<GTMLogFilter>)filter {
|
|
return [[[self alloc] initWithWriter:writer
|
|
formatter:formatter
|
|
filter:filter] autorelease];
|
|
}
|
|
|
|
+ (id)logger {
|
|
return [[[self alloc] init] autorelease];
|
|
}
|
|
|
|
- (id)init {
|
|
return [self initWithWriter:nil formatter:nil filter:nil];
|
|
}
|
|
|
|
- (id)initWithWriter:(id<GTMLogWriter>)writer
|
|
formatter:(id<GTMLogFormatter>)formatter
|
|
filter:(id<GTMLogFilter>)filter {
|
|
if ((self = [super init])) {
|
|
[self setWriter:writer];
|
|
[self setFormatter:formatter];
|
|
[self setFilter:filter];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
// Unlikely, but |writer_| may be an NSFileHandle, which can throw
|
|
@try {
|
|
[formatter_ release];
|
|
[filter_ release];
|
|
[writer_ release];
|
|
}
|
|
@catch (id e) {
|
|
// Ignored
|
|
}
|
|
[super dealloc];
|
|
}
|
|
|
|
- (id<GTMLogWriter>)writer {
|
|
return [[writer_ retain] autorelease];
|
|
}
|
|
|
|
- (void)setWriter:(id<GTMLogWriter>)writer {
|
|
@synchronized(self) {
|
|
[writer_ autorelease];
|
|
writer_ = nil;
|
|
if (writer == nil) {
|
|
// Try to use stdout, but don't trust NSFileHandle
|
|
@try {
|
|
writer_ = [[NSFileHandle fileHandleWithStandardOutput] retain];
|
|
}
|
|
@catch (id e) {
|
|
// Leave |writer_| nil
|
|
}
|
|
} else {
|
|
writer_ = [writer retain];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (id<GTMLogFormatter>)formatter {
|
|
return [[formatter_ retain] autorelease];
|
|
}
|
|
|
|
- (void)setFormatter:(id<GTMLogFormatter>)formatter {
|
|
@synchronized(self) {
|
|
[formatter_ autorelease];
|
|
formatter_ = nil;
|
|
if (formatter == nil) {
|
|
@try {
|
|
formatter_ = [[GTMLogBasicFormatter alloc] init];
|
|
}
|
|
@catch (id e) {
|
|
// Leave |formatter_| nil
|
|
}
|
|
} else {
|
|
formatter_ = [formatter retain];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (id<GTMLogFilter>)filter {
|
|
return [[filter_ retain] autorelease];
|
|
}
|
|
|
|
- (void)setFilter:(id<GTMLogFilter>)filter {
|
|
@synchronized(self) {
|
|
[filter_ autorelease];
|
|
filter_ = nil;
|
|
if (filter == nil) {
|
|
@try {
|
|
filter_ = [[GTMLogNoFilter alloc] init];
|
|
}
|
|
@catch (id e) {
|
|
// Leave |filter_| nil
|
|
}
|
|
} else {
|
|
filter_ = [filter retain];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)logDebug:(NSString *)fmt, ... {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelDebug];
|
|
va_end(args);
|
|
}
|
|
|
|
- (void)logInfo:(NSString *)fmt, ... {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelInfo];
|
|
va_end(args);
|
|
}
|
|
|
|
- (void)logError:(NSString *)fmt, ... {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelError];
|
|
va_end(args);
|
|
}
|
|
|
|
- (void)logAssert:(NSString *)fmt, ... {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelAssert];
|
|
va_end(args);
|
|
}
|
|
|
|
@end // GTMLogger
|
|
|
|
@implementation GTMLogger (GTMLoggerMacroHelpers)
|
|
|
|
- (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ... {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelDebug];
|
|
va_end(args);
|
|
}
|
|
|
|
- (void)logFuncInfo:(const char *)func msg:(NSString *)fmt, ... {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelInfo];
|
|
va_end(args);
|
|
}
|
|
|
|
- (void)logFuncError:(const char *)func msg:(NSString *)fmt, ... {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelError];
|
|
va_end(args);
|
|
}
|
|
|
|
- (void)logFuncAssert:(const char *)func msg:(NSString *)fmt, ... {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelAssert];
|
|
va_end(args);
|
|
}
|
|
|
|
@end // GTMLoggerMacroHelpers
|
|
|
|
@implementation GTMLogger (PrivateMethods)
|
|
|
|
- (void)logInternalFunc:(const char *)func
|
|
format:(NSString *)fmt
|
|
valist:(va_list)args
|
|
level:(GTMLoggerLevel)level {
|
|
// Primary point where logging happens, logging should never throw, catch
|
|
// everything.
|
|
@try {
|
|
NSString *fname = func ? [NSString stringWithUTF8String:func] : nil;
|
|
NSString *msg = [formatter_ stringForFunc:fname
|
|
withFormat:fmt
|
|
valist:args
|
|
level:level];
|
|
if (msg && [filter_ filterAllowsMessage:msg level:level])
|
|
[writer_ logMessage:msg level:level];
|
|
}
|
|
@catch (id e) {
|
|
// Ignored
|
|
}
|
|
}
|
|
|
|
@end // PrivateMethods
|
|
|
|
|
|
@implementation NSFileHandle (GTMFileHandleLogWriter)
|
|
|
|
+ (id)fileHandleForLoggingAtPath:(NSString *)path mode:(mode_t)mode {
|
|
int fd = -1;
|
|
if (path) {
|
|
int flags = O_WRONLY | O_APPEND | O_CREAT;
|
|
fd = open([path fileSystemRepresentation], flags, mode);
|
|
}
|
|
if (fd == -1) return nil;
|
|
return [[[self alloc] initWithFileDescriptor:fd
|
|
closeOnDealloc:YES] autorelease];
|
|
}
|
|
|
|
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
|
@synchronized(self) {
|
|
// Closed pipes should not generate exceptions in our caller. Catch here
|
|
// as well [GTMLogger logInternalFunc:...] so that an exception in this
|
|
// writer does not prevent other writers from having a chance.
|
|
@try {
|
|
NSString *line = [NSString stringWithFormat:@"%@\n", msg];
|
|
[self writeData:[line dataUsingEncoding:NSUTF8StringEncoding]];
|
|
}
|
|
@catch (id e) {
|
|
// Ignored
|
|
}
|
|
}
|
|
}
|
|
|
|
@end // GTMFileHandleLogWriter
|
|
|
|
|
|
@implementation NSArray (GTMArrayCompositeLogWriter)
|
|
|
|
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
|
@synchronized(self) {
|
|
id<GTMLogWriter> child = nil;
|
|
GTM_FOREACH_OBJECT(child, self) {
|
|
if ([child conformsToProtocol:@protocol(GTMLogWriter)])
|
|
[child logMessage:msg level:level];
|
|
}
|
|
}
|
|
}
|
|
|
|
@end // GTMArrayCompositeLogWriter
|
|
|
|
|
|
@implementation GTMLogger (GTMLoggerLogWriter)
|
|
|
|
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
|
switch (level) {
|
|
case kGTMLoggerLevelDebug:
|
|
[self logDebug:@"%@", msg];
|
|
break;
|
|
case kGTMLoggerLevelInfo:
|
|
[self logInfo:@"%@", msg];
|
|
break;
|
|
case kGTMLoggerLevelError:
|
|
[self logError:@"%@", msg];
|
|
break;
|
|
case kGTMLoggerLevelAssert:
|
|
[self logAssert:@"%@", msg];
|
|
break;
|
|
default:
|
|
// Ignore the message.
|
|
break;
|
|
}
|
|
}
|
|
|
|
@end // GTMLoggerLogWriter
|
|
|
|
|
|
@implementation GTMLogBasicFormatter
|
|
|
|
- (NSString *)prettyNameForFunc:(NSString *)func {
|
|
NSString *name = [func stringByTrimmingCharactersInSet:
|
|
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
NSString *function = @"(unknown)";
|
|
if ([name length]) {
|
|
if (// Objective C __func__ and __PRETTY_FUNCTION__
|
|
[name hasPrefix:@"-["] || [name hasPrefix:@"+["] ||
|
|
// C++ __PRETTY_FUNCTION__ and other preadorned formats
|
|
[name hasSuffix:@")"]) {
|
|
function = name;
|
|
} else {
|
|
// Assume C99 __func__
|
|
function = [NSString stringWithFormat:@"%@()", name];
|
|
}
|
|
}
|
|
return function;
|
|
}
|
|
|
|
- (NSString *)stringForFunc:(NSString *)func
|
|
withFormat:(NSString *)fmt
|
|
valist:(va_list)args
|
|
level:(GTMLoggerLevel)level {
|
|
// Performance note: We may want to do a quick check here to see if |fmt|
|
|
// contains a '%', and if not, simply return 'fmt'.
|
|
if (!(fmt && args)) return nil;
|
|
return [[[NSString alloc] initWithFormat:fmt arguments:args] autorelease];
|
|
}
|
|
|
|
@end // GTMLogBasicFormatter
|
|
|
|
|
|
@implementation GTMLogStandardFormatter
|
|
|
|
- (id)init {
|
|
if ((self = [super init])) {
|
|
dateFormatter_ = [[NSDateFormatter alloc] init];
|
|
[dateFormatter_ setFormatterBehavior:NSDateFormatterBehavior10_4];
|
|
[dateFormatter_ setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"];
|
|
pname_ = [[[NSProcessInfo processInfo] processName] copy];
|
|
pid_ = [[NSProcessInfo processInfo] processIdentifier];
|
|
if (!(dateFormatter_ && pname_)) {
|
|
[self release];
|
|
return nil;
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[dateFormatter_ release];
|
|
[pname_ release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSString *)stringForFunc:(NSString *)func
|
|
withFormat:(NSString *)fmt
|
|
valist:(va_list)args
|
|
level:(GTMLoggerLevel)level {
|
|
NSString *tstamp = nil;
|
|
@synchronized (dateFormatter_) {
|
|
tstamp = [dateFormatter_ stringFromDate:[NSDate date]];
|
|
}
|
|
return [NSString stringWithFormat:@"%@ %@[%d/%p] [lvl=%d] %@ %@",
|
|
tstamp, pname_, pid_, pthread_self(),
|
|
level, [self prettyNameForFunc:func],
|
|
// |super| has guard for nil |fmt| and |args|
|
|
[super stringForFunc:func withFormat:fmt valist:args level:level]];
|
|
}
|
|
|
|
@end // GTMLogStandardFormatter
|
|
|
|
|
|
@implementation GTMLogLevelFilter
|
|
|
|
// Check the environment and the user preferences for the GTMVerboseLogging key
|
|
// to see if verbose logging has been enabled. The environment variable will
|
|
// override the defaults setting, so check the environment first.
|
|
// COV_NF_START
|
|
static BOOL IsVerboseLoggingEnabled(void) {
|
|
static NSString *const kVerboseLoggingKey = @"GTMVerboseLogging";
|
|
NSString *value = [[[NSProcessInfo processInfo] environment]
|
|
objectForKey:kVerboseLoggingKey];
|
|
if (value) {
|
|
// Emulate [NSString boolValue] for pre-10.5
|
|
value = [value stringByTrimmingCharactersInSet:
|
|
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
if ([[value uppercaseString] hasPrefix:@"Y"] ||
|
|
[[value uppercaseString] hasPrefix:@"T"] ||
|
|
[value intValue]) {
|
|
return YES;
|
|
} else {
|
|
return NO;
|
|
}
|
|
}
|
|
return [[NSUserDefaults standardUserDefaults] boolForKey:kVerboseLoggingKey];
|
|
}
|
|
// COV_NF_END
|
|
|
|
// In DEBUG builds, log everything. If we're not in a debug build we'll assume
|
|
// that we're in a Release build.
|
|
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
|
#if DEBUG
|
|
return YES;
|
|
#endif
|
|
|
|
BOOL allow = YES;
|
|
|
|
switch (level) {
|
|
case kGTMLoggerLevelDebug:
|
|
allow = NO;
|
|
break;
|
|
case kGTMLoggerLevelInfo:
|
|
allow = IsVerboseLoggingEnabled();
|
|
break;
|
|
case kGTMLoggerLevelError:
|
|
allow = YES;
|
|
break;
|
|
case kGTMLoggerLevelAssert:
|
|
allow = YES;
|
|
break;
|
|
default:
|
|
allow = YES;
|
|
break;
|
|
}
|
|
|
|
return allow;
|
|
}
|
|
|
|
@end // GTMLogLevelFilter
|
|
|
|
|
|
@implementation GTMLogNoFilter
|
|
|
|
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
|
return YES; // Allow everything through
|
|
}
|
|
|
|
@end // GTMLogNoFilter
|
|
|
|
|
|
@implementation GTMLogAllowedLevelFilter
|
|
|
|
// Private designated initializer
|
|
- (id)initWithAllowedLevels:(NSIndexSet *)levels {
|
|
self = [super init];
|
|
if (self != nil) {
|
|
allowedLevels_ = [levels retain];
|
|
// Cap min/max level
|
|
if (!allowedLevels_ ||
|
|
// NSIndexSet is unsigned so only check the high bound, but need to
|
|
// check both first and last index because NSIndexSet appears to allow
|
|
// wraparound.
|
|
([allowedLevels_ firstIndex] > kGTMLoggerLevelAssert) ||
|
|
([allowedLevels_ lastIndex] > kGTMLoggerLevelAssert)) {
|
|
[self release];
|
|
return nil;
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id)init {
|
|
// Allow all levels in default init
|
|
return [self initWithAllowedLevels:[NSIndexSet indexSetWithIndexesInRange:
|
|
NSMakeRange(kGTMLoggerLevelUnknown,
|
|
(kGTMLoggerLevelAssert - kGTMLoggerLevelUnknown + 1))]];
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[allowedLevels_ release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
|
return [allowedLevels_ containsIndex:level];
|
|
}
|
|
|
|
@end // GTMLogAllowedLevelFilter
|
|
|
|
|
|
@implementation GTMLogMininumLevelFilter
|
|
|
|
- (id)initWithMinimumLevel:(GTMLoggerLevel)level {
|
|
return [super initWithAllowedLevels:[NSIndexSet indexSetWithIndexesInRange:
|
|
NSMakeRange(level,
|
|
(kGTMLoggerLevelAssert - level + 1))]];
|
|
}
|
|
|
|
@end // GTMLogMininumLevelFilter
|
|
|
|
|
|
@implementation GTMLogMaximumLevelFilter
|
|
|
|
- (id)initWithMaximumLevel:(GTMLoggerLevel)level {
|
|
return [super initWithAllowedLevels:[NSIndexSet indexSetWithIndexesInRange:
|
|
NSMakeRange(kGTMLoggerLevelUnknown, level + 1)]];
|
|
}
|
|
|
|
@end // GTMLogMaximumLevelFilter
|
|
|
|
#if !defined(__clang__) && (__GNUC__*10+__GNUC_MINOR__ >= 42)
|
|
// See comment at top of file.
|
|
#pragma GCC diagnostic error "-Wmissing-format-attribute"
|
|
#endif // !__clang__
|
|
|