IOS日志记录
日志
在开发过程中。众所周知。日志记录调试的关键部分,尤其是当产品公布的时候,实用户feedback一些崩溃问题或者是其它问题时,日志就显得尤其重要。通过分析日志能够非常快地找出问题的症结所在并高速解决这个问题。
恰当的记录用户日志是一门艺术。什么样的信息应该写入日志(通常包含用户行为和错误信息,分开记录),写入日志的信息太少不利于调试,而频繁地记录日志则会影响系统的性能,还会使得日志文件迅速膨胀导致难以查找到须要的信息。对于不同的应用,应该记录的信息是不用的,只是还是有一些通用的规则的。关于日志引擎,有下面几点须要注意:
1、在开发环境中,应该将日志写入控制台;而在生产环境中,应该将日志写入文件。在调试代码的时候。不输出到控制台就无法在XCode中看到日志。
当最好的方式是同一时候写入控制台和日志文件。
2、应该分为多种不同的日志级别(错误、警告、信息、具体)。
3、当某个日志级别被禁用时,对应日志函数的调用开销要很小。
4、向控制台或者文件写日志的时候。不能够堵塞调用者线程。
5、要定期删除日志文件以避免占满磁盘。
6、日志函数的调用要很方便,通常使用支持变參的C语法,不建议使用Object-C语法。
NSLog的调用凡是很easy。这一点就值得学习。
在增加一条日志的时候。应该想一下这条日志有什么用,这条语句记录的数据是否已经在其它地方被记录过来了。
对于不是肯定会被记录的内容。不要浪费计算机资源。
无需多言,错误信息肯定是要被记录进日志文件的。这里要强调的一点是,断言(NSAssert)也要记录进日志文件里而不是直接让程序崩溃(断言应该位于程序崩溃代码之前)
样例:
这里会导致断言失败,那么即使关闭断言程序依旧会崩溃。所以要将代码改为以下这样:
这样就好多了。在 NSAssert 崩溃之前先记录下日志。
当然,你能够重写一下 NSAssert ,如:MyNSAssert 。把日志记录代码和 NSAssert 封装在一起使用。这样更加方便,推荐使用。
关于记录敏感信息
记录日志一般会牵扯到隐私问题,要慎重考虑哪些日志信息是不该被记录进日志的,比如用户的username和password或者是信用卡号和password等。不要忘了记录日志的目的仅仅是为了在程序出现错误的时候非常方便的重现和定位到错误位置。仅此而已。
获取日志文件
假设拿不到日志文件。那记录日志也是白搭。
获取日志能够通过网络协议让用户上传日志到server。另外要注意一点,日志文件可能会比較大,在上传之前应该进行压缩以降低大小。考虑到用户流量情况,最好是在 WIFI 情况下静默上传日志文件。
何时上传日志
非程序崩溃情况下地日志。最好是选择在用户闲暇时间段且 WIFI 情况下上传;而崩溃情况下的日志。考虑到程序在崩溃的时候会处于奇怪且未知的状态。最好是选择在程序重新启动的时候(而不是崩溃期间)上传 crash 报告。在程序崩溃期间尽量什么都不要做。
项目中用到的 Bee 框架的 Log 日志写得很不错,就直接贴出来给大家学习学习:
//
//	 ______    ______    ______
//	/\  __ \  /\  ___\  /\  ___\
//	\ \  __<  \ \  __\_ \ \  __\_
//	 \ \_____\ \ \_____\ \ \_____\
//	  \/_____/  \/_____/  \/_____/
//
//
//	Copyright (c) 2013-2014, {Bee} open source community
//	http://www.bee-framework.com
//
//
//	Permission is hereby granted, free of charge, to any person obtaining a
//	copy of this software and associated documentation files (the "Software"),
//	to deal in the Software without restriction, including without limitation
//	the rights to use, copy, modify, merge, publish, distribute, sublicense,
//	and/or sell copies of the Software, and to permit persons to whom the
//	Software is furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
//	IN THE SOFTWARE.
//
#import "Bee_Precompile.h"
#import "Bee_Singleton.h"
#pragma mark -
typedef enum
{
	BeeLogLevelNone			= 0,
	BeeLogLevelInfo			= 100,
	BeeLogLevelPerf			= 100 + 1,
	BeeLogLevelProgress		= 100 + 2,
	BeeLogLevelWarn			= 200,
	BeeLogLevelError		= 300
} BeeLogLevel;
#pragma mark -
#undef	CC
#define CC( ... )			[[BeeLogger sharedInstance] level:BeeLogLevelNone format:__VA_ARGS__];
#undef	INFO
#define INFO( ... )			[[BeeLogger sharedInstance] level:BeeLogLevelInfo format:__VA_ARGS__];
#undef	PERF
#define PERF( ... )			[[BeeLogger sharedInstance] level:BeeLogLevelPerf format:__VA_ARGS__];
#undef	WARN
#define WARN( ... )			[[BeeLogger sharedInstance] level:BeeLogLevelWarn format:__VA_ARGS__];
#undef	ERROR
#define ERROR( ... )		[[BeeLogger sharedInstance] level:BeeLogLevelError format:__VA_ARGS__];
#undef	PROGRESS
#define PROGRESS( ... )		[[BeeLogger sharedInstance] level:BeeLogLevelProgress format:__VA_ARGS__];
#undef	VAR_DUMP
#define VAR_DUMP( __obj )	[[BeeLogger sharedInstance] level:BeeLogLevelNone format:[__obj description]];
#undef	OBJ_DUMP
#define OBJ_DUMP( __obj )	[[BeeLogger sharedInstance] level:BeeLogLevelNone format:[__obj objectToDictionary]];
#undef	TODO
#define TODO( desc, ... )
#pragma mark -
@interface BeeBacklog : NSObject
@property (nonatomic, assign) BeeLogLevel		level;
@property (nonatomic, retain) NSDate *			time;
@property (nonatomic, retain) NSString *		text;
@end
#pragma mark -
@interface BeeLogger : NSObject
AS_SINGLETON( BeeLogger );
@property (nonatomic, assign) BOOL				enabled;
@property (nonatomic, assign) BOOL				backlog;
@property (nonatomic, retain) NSMutableArray *	backlogs;
@property (nonatomic, assign) NSUInteger		indentTabs;
- (void)toggle;
- (void)enable;
- (void)disable;
- (void)indent;
- (void)indent:(NSUInteger)tabs;
- (void)unindent;
- (void)unindent:(NSUInteger)tabs;
- (void)level:(BeeLogLevel)level format:(NSString *)format, ...;
- (void)level:(BeeLogLevel)level format:(NSString *)format args:(va_list)args;
@end
#pragma mark -
#if __cplusplus
extern "C" {
#endif
	void BeeLog( NSString * format, ... );
	
#if __cplusplus
};
#endif
//
//	 ______    ______    ______
//	/\  __ \  /\  ___\  /\  ___\
//	\ \  __<  \ \  __\_ \ \  __\_
//	 \ \_____\ \ \_____\ \ \_____\
//	  \/_____/  \/_____/  \/_____/
//
//
//	Copyright (c) 2013-2014, {Bee} open source community
//	http://www.bee-framework.com
//
//
//	Permission is hereby granted, free of charge, to any person obtaining a
//	copy of this software and associated documentation files (the "Software"),
//	to deal in the Software without restriction, including without limitation
//	the rights to use, copy, modify, merge, publish, distribute, sublicense,
//	and/or sell copies of the Software, and to permit persons to whom the
//	Software is furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
//	IN THE SOFTWARE.
//
#import "Bee_Log.h"
#import "Bee_UnitTest.h"
#import "Bee_Sandbox.h"
#import "NSArray+BeeExtension.h"
// ----------------------------------
// Source code
// ----------------------------------
#pragma mark -
#undef	MAX_BACKLOG
#define MAX_BACKLOG	(50)
#pragma mark -
@implementation BeeBacklog
@synthesize level = _level;
@synthesize time = _time;
@synthesize text = _text;
- (id)init
{
	self = [super init];
	if ( self )
	{
		self.level = BeeLogLevelNone;
		self.time = [NSDate date];
		self.text = nil;
	}
	return self;
}
- (void)dealloc
{
	self.time = nil;
	self.text = nil;
	
	[super dealloc];
}
@end
#pragma mark -
@interface BeeLogger()
{
	BOOL				_enabled;
	BOOL				_backlog;
	NSMutableArray *	_backlogs;
	NSUInteger			_indentTabs;
}
@end
#pragma mark -
@implementation BeeLogger
DEF_SINGLETON( BeeLogger );
@synthesize enabled = _enabled;
@synthesize backlog = _backlog;
@synthesize backlogs = _backlogs;
@synthesize indentTabs = _indentTabs;
- (id)init
{
	self = [super init];
	if ( self )
	{
		self.enabled = YES;
		self.backlog = YES;
		self.backlogs = [NSMutableArray array];
		self.indentTabs = 0;
	}
	return self;
}
- (void)dealloc
{
	self.backlogs = nil;
	
	[super dealloc];
}
- (void)toggle
{
	_enabled = _enabled ? NO : YES;
}
- (void)enable
{
	_enabled = YES;
}
- (void)disable
{
	_enabled = YES;
}
- (void)indent
{
	_indentTabs += 1;
}
- (void)indent:(NSUInteger)tabs
{
	_indentTabs += tabs;
}
- (void)unindent
{
	if ( _indentTabs > 0 )
	{
		_indentTabs -= 1;
	}
}
- (void)unindent:(NSUInteger)tabs
{
	if ( _indentTabs < tabs )
	{
		_indentTabs = 0;
	}
	else
	{
		_indentTabs -= tabs;
	}
}
- (void)level:(BeeLogLevel)level format:(NSString *)format, ...
{
#if (__ON__ == __BEE_LOG__)
	
	if ( nil == format || NO == [format isKindOfClass:[NSString class]] )
		return;
	va_list args;
	va_start( args, format );
	
	[self level:level format:format args:args];
	va_end( args );
	
#endif	// #if (__ON__ == __BEE_LOG__)
}
- (void)level:(BeeLogLevel)level format:(NSString *)format args:(va_list)args
{
#if (__ON__ == __BEE_LOG__)
	
	if ( NO == _enabled )
		return;
	
	// formatting
	
	NSString * prefix = nil;
	
	if ( BeeLogLevelInfo == level )
	{
		prefix = @"INFO";
	}
	else if ( BeeLogLevelPerf == level )
	{
		prefix = @"PERF";
	}
	else if ( BeeLogLevelWarn == level )
	{
		prefix = @"WARN";
	}
	else if ( BeeLogLevelError == level )
	{
		prefix = @"ERROR";
	}
	
	if ( prefix )
	{
		prefix = [NSString stringWithFormat:@"[%@]", prefix];
		prefix = [prefix stringByPaddingToLength:8 withString:@" " startingAtIndex:0];
	}
	
	NSMutableString * tabs = nil;
	NSMutableString * text = nil;
	
	if ( _indentTabs > 0 )
	{
		tabs = [NSMutableString string];
		
		for ( int i = 0; i < _indentTabs; ++i )
		{
			[tabs appendString:@"\t"];
		}
	}
	
	text = [NSMutableString string];
	
	if ( prefix && prefix.length )
	{
		[text appendString:prefix];
	}
	
	if ( tabs && tabs.length )
	{
		[text appendString:tabs];
	}
	
	if ( BeeLogLevelProgress == level )
	{
		NSString *	name = [format stringByPaddingToLength:32 withString:@" " startingAtIndex:0];
		NSString *	state = va_arg( args, NSString * );
		
		[text appendFormat:@"%@\t\t\t\t[%@]", name, state];
	}
	else
	{
		NSString * content = [[[NSString alloc] initWithFormat:(NSString *)format arguments:args] autorelease];
		if ( content && content.length )
		{
			[text appendString:content];
		}
	}
	
	if ( [text rangeOfString:@"\n"].length )
	{
		[text replaceOccurrencesOfString:@"\n"
							  withString:[NSString stringWithFormat:@"\n%@", tabs ? tabs : @"\t\t"]
								 options:NSCaseInsensitiveSearch
								   range:NSMakeRange( 0, text.length )];
	}
	
	// print to console
	
	fprintf( stderr, [text UTF8String], NULL );
	fprintf( stderr, "\n", NULL );
	
	// back log
	
	if ( _backlog )
	{
		BeeBacklog * log = [[[BeeBacklog alloc] init] autorelease];
		log.level = level;
		log.text = text;
		
		[_backlogs pushTail:log];
		[_backlogs keepTail:MAX_BACKLOG];
	}
	
#endif	// #if (__ON__ == __BEE_LOG__)
}
@end
extern "C" void BeeLog( NSString * format, ... )
{
#if (__ON__ == __BEE_LOG__)
	
	if ( nil == format || NO == [format isKindOfClass:[NSString class]] )
		return;
	
	va_list args;
	va_start( args, format );
	
	[[BeeLogger sharedInstance] level:BeeLogLevelInfo format:format args:args];
	
	va_end( args );
	
#endif	// #if (__ON__ == __BEE_LOG__)
}
// ----------------------------------
// Unit test
// ----------------------------------
#if defined(__BEE_UNITTEST__) && __BEE_UNITTEST__
TEST_CASE( BeeLog )
{
	TIMES( 3 )
	{
		HERE( "output log", {
			CC( nil );
			CC( @"" );
			CC( @"format %@", @"" );
		});
		
		HERE( "test info", {
			INFO( nil );
			INFO( nil, nil );
			INFO( nil, @"" );
			INFO( nil, @"format %@", @"" );
			
			INFO( @"a", nil );
			INFO( @"a", @"" );
			INFO( @"a", @"format %@", @"" );
		});
		HERE( "test warn", {
			WARN( nil );
			WARN( nil, nil );
			WARN( nil, @"" );
			WARN( nil, @"format %@", @"" );
			WARN( @"a", nil );
			WARN( @"a", @"" );
			WARN( @"a", @"format %@", @"" );
		});
		
		HERE( "test error", {
			ERROR( nil );
			ERROR( nil, nil );
			ERROR( nil, @"" );
			ERROR( nil, @"format %@", @"" );
			
			ERROR( @"a", nil );
			ERROR( @"a", @"" );
			ERROR( @"a", @"format %@", @"" );
		});
	}
}
TEST_CASE_END
#endif	// #if defined(__BEE_UNITTEST__) && __BEE_UNITTEST__
有点可惜的是,Bee 框架的 log 日志并没有直接写入沙盒,我们自己加入一下就好了。强调一点,记得定期清除没用或者已过期的日志文件(能够选择选择先上传到server后删掉沙盒中的日志),这点非常重要。
日志分析
为了即时拿到用户的崩溃日志而且记录用户的行为,第三方统计会是一个不错的选择。这里就以友盟统计为例:
由上面图片能够看到。日志记录的崩溃信息非常是具体。包含错误摘要。版本号信息、错误次数和发生时间,最重要的是有记录下 crash 时的堆栈信息,这样找错误就方便非常多了。
对于一些比較难懂的错误摘要,如:Application received signal SIGSEGV ,就要用到发生该错误的版本号代码和 .DYSM 文件符号化定位到错误发生的位置。详细方法就不赘述了。能够看一下这里,或者是自己Google一下关键词。
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号