標籤:ios 日誌 log
日誌
在開發過程中,眾所周知,日誌記錄調試的關鍵區段,尤其是當產品發布的時候,有使用者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 -#undefCC#define CC( ... )[[BeeLogger sharedInstance] level:BeeLogLevelNone format:__VA_ARGS__];#undefINFO#define INFO( ... )[[BeeLogger sharedInstance] level:BeeLogLevelInfo format:__VA_ARGS__];#undefPERF#define PERF( ... )[[BeeLogger sharedInstance] level:BeeLogLevelPerf format:__VA_ARGS__];#undefWARN#define WARN( ... )[[BeeLogger sharedInstance] level:BeeLogLevelWarn format:__VA_ARGS__];#undefERROR#define ERROR( ... )[[BeeLogger sharedInstance] level:BeeLogLevelError format:__VA_ARGS__];#undefPROGRESS#define PROGRESS( ... )[[BeeLogger sharedInstance] level:BeeLogLevelProgress format:__VA_ARGS__];#undefVAR_DUMP#define VAR_DUMP( __obj )[[BeeLogger sharedInstance] level:BeeLogLevelNone format:[__obj description]];#undefOBJ_DUMP#define OBJ_DUMP( __obj )[[BeeLogger sharedInstance] level:BeeLogLevelNone format:[__obj objectToDictionary]];#undefTODO#define TODO( desc, ... )#pragma mark -@interface BeeBacklog : NSObject@property (nonatomic, assign) BeeLogLevellevel;@property (nonatomic, retain) NSDate *time;@property (nonatomic, retain) NSString *text;@end#pragma mark -@interface BeeLogger : NSObjectAS_SINGLETON( BeeLogger );@property (nonatomic, assign) BOOLenabled;@property (nonatomic, assign) BOOLbacklog;@property (nonatomic, retain) NSMutableArray *backlogs;@property (nonatomic, assign) NSUIntegerindentTabs;- (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 __cplusplusextern "C" {#endifvoid 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 -#undefMAX_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 BeeLoggerDEF_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;// formattingNSString * 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 consolefprintf( stderr, [text UTF8String], NULL );fprintf( stderr, "\n", NULL );// back logif ( _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__)}@endextern "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一下關鍵詞。