IOS日志记录

日志

在开发过程中,众所周知,日志记录调试的关键部分,尤其是当产品发布的时候,有用户feedback一些崩溃问题或者是其他问题时,日志就显得尤其重要,通过分析日志可以很快地找出问题的症结所在并快速解决问题。

恰当的记录用户日志是一门艺术。什么样的信息应该写入日志(通常包括用户行为和错误信息,分开记录),写入日志的信息太少不利于调试,而频繁地记录日志则会影响系统的性能,还会使得日志文件迅速膨胀导致难以查找到需要的信息。对于不同的应用,应该记录的信息是不用的,不过还是有一些通用的规则的。关于日志引擎,有以下几点需要注意:

1、在开发环境中,应该将日志写入控制台;而在生产环境中,应该将日志写入文件。在调试代码的时候,不输出到控制台就无法在XCode中看到日志。当最好的方式是同时写入控制台和日志文件。

2、应该分为多种不同的日志级别(错误、警告、信息、详细)。

3、当某个日志级别被禁用时,相应日志函数的调用开销要非常小。

4、向控制台或者文件写日志的时候,不可以阻塞调用者线程。

5、要定期删除日志文件以避免占满磁盘。

6、日志函数的调用要非常方便,通常使用支持变参的C语法,不建议使用Object-C语法。NSLog的调用凡是非常简单,这一点就值得学习。

在加入一条日志的时候,应该想一下这条日志有什么用,这条语句记录的数据是否已经在其他地方被记录过来了。对于不是肯定会被记录的内容,不要浪费计算机资源。

无需多言,错误信息肯定是要被记录进日志文件的。这里要强调的一点是,断言(NSAssert)也要记录进日志文件中而不是直接让程序崩溃(断言应该位于程序崩溃代码之前)

例子:

这里会导致断言失败,那么即使关闭断言程序依然会崩溃。所以要将代码改为下面这样:

这样就好多了,在 NSAssert 崩溃之前先记录下日志。当然,你可以重写一下 NSAssert ,如:MyNSAssert ,把日志记录代码和 NSAssert 封装在一起使用,这样更加方便,推荐使用。

关于记录敏感信息

记录日志通常会牵扯到隐私问题,要谨慎考虑哪些日志信息是不该被记录进日志的,例如用户的用户名和密码或者是信用卡号和密码等。不要忘了记录日志的目的只是为了在程序出现错误的时候很方便的重现和定位到错误位置,仅此而已。

获取日志文件

如果拿不到日志文件,那记录日志也是白搭。获取日志可以通过网络协议让用户上传日志到服务器。另外要注意一点,日志文件可能会比较大,在上传之前应该进行压缩以减少大小。考虑到用户流量情况,最好是在 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 日志并没有直接写入沙盒,我们自己添加一下就好了。强调一点,记得定期清除没用或者已过期的日志文件(可以选择选择先上传到服务器后删掉沙盒中的日志),这点很重要。

日志分析

为了即时拿到用户的崩溃日志并且记录用户的行为,第三方统计会是一个不错的选择。这里就以友盟统计为例:

由上面图片可以看到,日志记录的崩溃信息很是详细,包括错误摘要,版本信息、错误次数和发生时间,最重要的是有记录下 crash 时的堆栈信息,这样找错误就方便很多了。

对于一些比较难懂的错误摘要,如:Application received signal SIGSEGV ,就要用到发生该错误的版本代码和 .DYSM 文件符号化定位到发生错误的位置,具体方法就不赘述了,可以看一下这里,或者是自己Google一下关键词。

IOS日志记录

时间: 2024-11-03 12:38:10

IOS日志记录的相关文章

IOS异常日志记录与展现功能

在平常的APP开发过程中经常碰到程序遇到异常闪退的问题,通过日志可以把相关的详细错误信息进行记录,本实例要记录不管在哪个页面出错都要进行记录,这边使用到的日志记录插件CocoaLumberjack,以文本的形式记录错误信息,然后再去读取各个文本的内容进行展示:当然现在有很多第三方的插件比如友盟也已经集成错误记录的功能: 效果图如下: 1:封装DDLogger的类 MyFileLogger.h文件 #import <Foundation/Foundation.h> #import <Coc

IOS异常日志记录与展现

在平常的APP开发过程中经常碰到程序遇到异常闪退的问题,通过日志可以把相关的详细错误信息进行记录,本实例要记录不管在哪个页面出错都要进行记录,这边使用到的日志记录插件CocoaLumberjack,以文本的形式记录错误信息,然后再去读取各个文本的内容进行展示:当然现在有很多第三方的插件比如友盟也已经集成错误记录的功能: 效果图如下: 1:封装DDLogger的类 MyFileLogger.h文件 #import <Foundation/Foundation.h> #import <Coc

c++日志记录模块

C++ 日志记录模块 该模块从实际项目中产生,通过extern声明的方式,可在代码不同模块中生成日志,日志文件名称为随机码加用户指定名称,采用随机码是为了避免日志文件可能被覆盖的问题. 愿意的话你也能自己构建个人的日志记录模块,本次分享的模块实现方法比较简单,可能有些地方没考虑清楚. 源码: // // Created by jerry on 2/12/16. // #include <iostream> #include <string> #include <fstream

前端学HTTP之日志记录

前面的话 几乎所有的服务器和代理都会记录下它们所处理的HTTP事务摘要.这么做出于一系列的原因:跟踪使用情况.安全性.计费.错误检测等等.本文将谥介绍日志记录 记录内容 大多数情况下,日志的记录出于两种原因:査找服务器或代理中存在的问题(比如,哪些请求失败了),或者是生成Web站点访问方式的统计信息.统计数据对市场营销.计费和容量规划(比如,决定是否需要增加服务器或带宽)都非常有用 可以把一个HTTP事务中所有的首部都记录下来,但对每天要处理数百万个事务的服务器和代理来说,这些数据的体积超大,很

Spring AOP进行日志记录

在java开发中日志的管理有很多种.我一般会使用过滤器,或者是Spring的拦截器进行日志的处理.如果是用过滤器比较简单,只要对所有的.do提交进行拦截,然后获取action的提交路径就可以获取对每个方法的调用.然后进行日志记录.使用过滤器的好处是可以自己选择性的对某一些方法进行过滤,记录日志.但是实现起来有点麻烦. 另外一种就是使用Spring的AOP了.这种方式实现起来非常简单,只要配置一下配置文件就可以了.可是这种方式会拦截下所有的对action的每个操作.使得效率比较低.不过想做详细日志

sqlmap批量扫描burpsuite拦截的日志记录

1.功能上,sqlmap具备对burpsuite拦截的request日志进行批量扫描的能力 python sqlmap.py -l hermes.log --batch -v 3 --batch:会自动选择yes -l : 指定日志文件 -v: 调试信息等级,3级的话,可以看大注入的payload信息 2.如何配置burpsuite拦截扫描对象? 4.其他方式拦截的request日志文件,也可以 5.使用--smart智能探测效果不是很好,还是采用一些配置,加深扫描比较好 python sqlm

巧用CurrentThread.Name来统一标识日志记录

先看下面的日志: 2017/5/21 18:00:01 [OrderQuery_180001914_C72FF]请求支付中心参数:{"order_no":"KB201705210000165","sign":"e6c3559cd4b36458b180f15bfcd9b5a5"} 2017/5/21 18:00:01 [OrderQuery_180001914_C72FF]支付中心验签通过. 2017/5/21 18:00:01

Haproxy 开启日志记录

CentOS 7上yum安装的Haproxy,默认没有记录日志.需要做一下配置才能记录日志.(不知道其他版本是否需要,已经忘记了)主要是用到了Haproxy,以前貌似没有这么麻烦,今天配置出了一些问题查日志才发现原来Haproxy需要自己手工开启日志记录功能.因此作为相关记录! 1. 创建记录日志文件 mkdir /var/log/haproxy chmod a+w /var/log/haproxy 2. 开启rsyslog记录haproxy日志功能 编辑"/etc/rsyslog.conf&q

PHP 错误与异常的日志记录

提到 Nginx + PHP 服务的错误日志,我们通常能想到的有 Nginx 的 access 日志.error 日志以及 PHP 的 error 日志.虽然看起来是个很简单的问题,但里面其实又牵扯到应用配置以及日志记录位置的问题,如果是在 ubuntu 等系统下使用 apt-get 的方式来安装,其自有一套较为合理的的配置文件可用.再者运行的应用程序中的配置也会影响到日志记录的方式及内容. 错误与异常的区别 关于错误与异常,我们可以用一个简单的例子来理解: <?php try { 1 / 0;