446 lines
12 KiB
Objective-C
446 lines
12 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>
|
|
|
|
|
|
// Define a trivial assertion macro to avoid dependencies
|
|
#ifdef DEBUG
|
|
#define GTMLOGGER_ASSERT(expr) assert(expr)
|
|
#else
|
|
#define GTMLOGGER_ASSERT(expr)
|
|
#endif
|
|
|
|
|
|
@interface GTMLogger (PrivateMethods)
|
|
|
|
- (void)logInternalFunc:(const char *)func
|
|
format:(NSString *)fmt
|
|
valist:(va_list)args
|
|
level:(GTMLoggerLevel)level;
|
|
|
|
@end
|
|
|
|
|
|
// 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];
|
|
}
|
|
GTMLOGGER_ASSERT(gSharedLogger != nil);
|
|
}
|
|
return [[gSharedLogger retain] autorelease];
|
|
}
|
|
|
|
+ (void)setSharedLogger:(GTMLogger *)logger {
|
|
@synchronized(self) {
|
|
[gSharedLogger autorelease];
|
|
gSharedLogger = [logger retain];
|
|
}
|
|
}
|
|
|
|
+ (id)standardLogger {
|
|
id<GTMLogWriter> writer = [NSFileHandle fileHandleWithStandardOutput];
|
|
id<GTMLogFormatter> fr = [[[GTMLogStandardFormatter alloc] init] autorelease];
|
|
id<GTMLogFilter> filter = [[[GTMLogLevelFilter alloc] init] autorelease];
|
|
return [self loggerWithWriter:writer formatter:fr filter:filter];
|
|
}
|
|
|
|
+ (id)standardLoggerWithStderr {
|
|
id me = [self standardLogger];
|
|
[me setWriter:[NSFileHandle fileHandleWithStandardError]];
|
|
return me;
|
|
}
|
|
|
|
+ (id)standardLoggerWithPath:(NSString *)path {
|
|
NSFileHandle *fh = [NSFileHandle fileHandleForLoggingAtPath:path mode:0644];
|
|
if (fh == nil) return nil;
|
|
id me = [self standardLogger];
|
|
[me setWriter:fh];
|
|
return me;
|
|
}
|
|
|
|
+ (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];
|
|
GTMLOGGER_ASSERT(formatter_ != nil);
|
|
GTMLOGGER_ASSERT(filter_ != nil);
|
|
GTMLOGGER_ASSERT(writer_ != nil);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
GTMLOGGER_ASSERT(writer_ != nil);
|
|
GTMLOGGER_ASSERT(formatter_ != nil);
|
|
GTMLOGGER_ASSERT(filter_ != nil);
|
|
[writer_ release];
|
|
[formatter_ release];
|
|
[filter_ release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (id<GTMLogWriter>)writer {
|
|
GTMLOGGER_ASSERT(writer_ != nil);
|
|
return [[writer_ retain] autorelease];
|
|
}
|
|
|
|
- (void)setWriter:(id<GTMLogWriter>)writer {
|
|
@synchronized(self) {
|
|
[writer_ autorelease];
|
|
if (writer == nil)
|
|
writer_ = [[NSFileHandle fileHandleWithStandardOutput] retain];
|
|
else
|
|
writer_ = [writer retain];
|
|
}
|
|
GTMLOGGER_ASSERT(writer_ != nil);
|
|
}
|
|
|
|
- (id<GTMLogFormatter>)formatter {
|
|
GTMLOGGER_ASSERT(formatter_ != nil);
|
|
return [[formatter_ retain] autorelease];
|
|
}
|
|
|
|
- (void)setFormatter:(id<GTMLogFormatter>)formatter {
|
|
@synchronized(self) {
|
|
[formatter_ autorelease];
|
|
if (formatter == nil)
|
|
formatter_ = [[GTMLogBasicFormatter alloc] init];
|
|
else
|
|
formatter_ = [formatter retain];
|
|
}
|
|
GTMLOGGER_ASSERT(formatter_ != nil);
|
|
}
|
|
|
|
- (id<GTMLogFilter>)filter {
|
|
GTMLOGGER_ASSERT(filter_ != nil);
|
|
return [[filter_ retain] autorelease];
|
|
}
|
|
|
|
- (void)setFilter:(id<GTMLogFilter>)filter {
|
|
@synchronized(self) {
|
|
[filter_ autorelease];
|
|
if (filter == nil)
|
|
filter_ = [[GTMLogNoFilter alloc] init];
|
|
else
|
|
filter_ = [filter retain];
|
|
}
|
|
GTMLOGGER_ASSERT(filter_ != nil);
|
|
}
|
|
|
|
- (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 {
|
|
GTMLOGGER_ASSERT(formatter_ != nil);
|
|
GTMLOGGER_ASSERT(filter_ != nil);
|
|
GTMLOGGER_ASSERT(writer_ != nil);
|
|
|
|
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];
|
|
}
|
|
|
|
@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) {
|
|
NSString *line = [NSString stringWithFormat:@"%@\n", msg];
|
|
[self writeData:[line dataUsingEncoding:NSUTF8StringEncoding]];
|
|
}
|
|
}
|
|
|
|
@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 *)stringForFunc:(NSString *)func
|
|
withFormat:(NSString *)fmt
|
|
valist:(va_list)args
|
|
level:(GTMLoggerLevel)level {
|
|
// Performance note: since we always have to create a new NSString from the
|
|
// returned CFStringRef, we may want to do a quick check here to see if |fmt|
|
|
// contains a '%', and if not, simply return 'fmt'.
|
|
CFStringRef cfmsg = NULL;
|
|
cfmsg = CFStringCreateWithFormatAndArguments(kCFAllocatorDefault,
|
|
NULL, // format options
|
|
(CFStringRef)fmt,
|
|
args);
|
|
return GTMCFAutorelease(cfmsg);
|
|
}
|
|
|
|
@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];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[dateFormatter_ release];
|
|
[pname_ release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSString *)stringForFunc:(NSString *)func
|
|
withFormat:(NSString *)fmt
|
|
valist:(va_list)args
|
|
level:(GTMLoggerLevel)level {
|
|
GTMLOGGER_ASSERT(dateFormatter_ != nil);
|
|
NSString *tstamp = nil;
|
|
@synchronized (dateFormatter_) {
|
|
tstamp = [dateFormatter_ stringFromDate:[NSDate date]];
|
|
}
|
|
return [NSString stringWithFormat:@"%@ %@[%d/%p] [lvl=%d] %@ %@",
|
|
tstamp, pname_, pid_, pthread_self(),
|
|
level, (func ? func : @"(no func)"),
|
|
[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";
|
|
static char *env = NULL;
|
|
if (env == NULL)
|
|
env = getenv([kVerboseLoggingKey UTF8String]);
|
|
|
|
if (env && env[0]) {
|
|
return (strtol(env, NULL, 10) != 0);
|
|
}
|
|
|
|
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() == YES);
|
|
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
|