iOS Crash異常日誌收集

異常日誌的產生與存儲

任何一款應用在使用過程中出現各種異常問題在所難免,如何能快速還原場景定位異常,對於能否及時修復問題非常重要.在apple的文檔中我們發現了這樣一張圖,完整地描述了應用的整個流程:

  1. 編譯器將源代碼編譯成機器代碼的過程中,會生成調試符號,這些調試符號將生成的二機制文件中的每一條機器指令映射回源代碼行.根據調試信息格式的構建設置(setting builds--Debug Information Format),這些調試符號會存儲在二進制文件中或者附隨的調試文件中(.dSYM).在默認的情況下,應用的調試版本,會將調試符號會被保存在已經編譯的二進制文件中;而在發佈版本中,會單獨生成附隨的調試符號表(.dSYM)以減小二進制文件的大小.應用在每次構建時,都會生成一個用於標記該次構建過程的唯一的uuid,調試符號和應用程序二進制文件通過UUID進行綁定.某次構建過程中生成的調試符號,即使同樣的源代碼,也不能操作非本次構建生成的應用二進制文件.
  2. 當打包應用程序進行分發時,Xcode將收集應用程序二進制文件以及.dSYM文件,並將它們存儲在主文件夾內的某個位置。 可以在Xcode Organizer的“ Archived”部分下找到所有已存檔的應用程序。 有關創建檔案的更多信息,請參閱應用發佈指南。如果想要符號化這些異常日誌,就必須保存每次發佈應用時構建的打包文件(.xcarchive文件)
  3. 如果要通過App Store分發應用程序,或使用Test Flight進行Beta測試,在將應用上傳到iTunes Connect時,可以選擇是否包括dSYM文件。 在提交對話框中,選中“Include app symbols for your application…”。 上傳dSYM文件對於接收從TestFlight用戶和選擇共享診斷數據的客戶收集的崩潰報告是必要的.需要注意的是,及時你上傳了.dysm文件從App Review獲取到的異常日誌也是未符號話的,需要使用Xcode來進行符號化,請參閱使用Xcode來進行符號化;
  4. 當應用在設備上發生異常時,系統會產生異常日誌並存儲在手機設備上;
  5. 用戶可以按照調試已部署的iOS應用中的步驟直接從其設備檢索崩潰報告。 如果已經通過AdHoc或Enterprise方式分發了應用程序,則這是從用戶那裏獲取崩潰報告的唯一方法;
  6. 從移動設備中檢索到的崩潰報告沒有符號化,需要使用Xcode進行符號化。 Xcode使用與應用程序二進制文件關聯的dSYM文件將回溯中的每個地址替換爲其源代碼中的原始位置,得到的結果是一個符號化的崩潰報告;
  7. 如果用戶選擇與Apple共享診斷數據,或者用戶已通過TestFlight安裝了應用程序的Beta版本,則崩潰報告將上傳到App Store;
  8. App Store象徵着崩潰報告,並將其與類似的崩潰報告進行分組。 這種相似的崩潰報告的彙總稱爲崩潰點;
  9. 帶符號的崩潰報告可在Xcode的崩潰管理器中進行查看使用.

在新的iTC後臺中,已經刪除了關於異常的日誌的顯示,只保存了閃退率的統計數據.異常日誌的源頭在發生異常的設備中,所以可以直接從異常設備中進行獲取;另外如果用戶共享了設備診斷數據,或者用戶安裝了TestFlight測試包,可以在Xcode中下載異常日誌;當然也可以通過apple提供的接口實現自定義的異常捕獲.

獲取異常日誌的獲取途徑

從手機設備獲取

手機設備是異常發生的第一現場,iPhone設備在手機上生成並保存了這些異常日誌.可以通過以下方式查看:

  • 直接在手機上查看

在 設置-->隱私-->分析-->分析數據中可以查看到異常,不過這裏的日誌是沒有符號化的.可以選擇右上角的分享,將文件分享到能夠符號化的設備上進行符號化處理:

...
Application Specific Information:
abort() called

Last Exception Backtrace:
(0x1c1e11c30 0x1c1b2c0c8 0x1c1e6a5f4 0x1c1e74414 0x1c1cf85f0 0x1c1ce9e9c 0x100dbbce0 0x1c5e9dd80 0x1c5e9fb90 0x1c5ea5568 0x1c564d710 0x1c5af97e8 0x1c564e248 0x1c564dc78 0x1c564e064 0x1c564d8e8 0x1c5652098 0x1c5b13214 0x1c5a26e90 0x1c5b131cc 0x1c5651db0 0x1c5b130b4 0x1c5651c0c 0x1c54bd630 0x1c54bc0f4 0x1c54bd360 0x1c5ea391c 0x1c5a48d7c 0x1c6f7b014 0x1c6fa1bd0 0x1c6f860f8 0x1c6fa1864 0x1c1ab900c 0x1c1abbd50 0x1c6fc8384 0x1c6fc8030 0x1c6fc859c 0x1c1d8d260 0x1c1d8d1b4 0x1c1d8c920 0x1c1d877ec 0x1c1d87098 0x1cbef1534 0x1c5ea77ac 0x100dbbb68 0x1c1c06f30)

Thread 0 name:  Dispatch queue: com.apple.main-thread

...
  • 連接手機,在Xcode查看.

打開Xcode,在window --> Devices and Simulators --> View Devices Logs 中,

可以選擇查看所有的日常日誌或者該設備上的異常日誌.這裏獲取到的異常日誌是經過Xcode符號化的,所以可以清楚看到異常調用的堆棧信息:

 

從Xcode獲取

如果應用已經通過app store進行發佈,就可以在打包Xcode的設備上通過Xcode進行查看.

打開Xcode在,打開window --> Organizer,選中對應的應用,即可查看不同版本中的異常.

 

從集成第三方統計工具中獲取

除了apple默認的收集方式之外,還可以自定義收集方式.目前市場上很多多成熟的第三方解決方案,例如Umeng,騰訊Bugly等.

以友盟2.1.1版本爲例,

  • 使用cocoapods引入需要的類庫(根據自己的需求也可以手動引入)
    pod 'UMCCommon', '~> 2.1.1'
    pod 'UMCSecurityPlugins'
    pod 'UMCAnalytics'
    pod 'UMCCommonLog'
  • 在應用啓動時進行初始化:
#if DEBUG || ISDEV
    [UMConfigure setLogEnabled:true];
    NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
    [UMConfigure initWithAppkey:@"MOBILE_CLICK_KEY_DEVELOP" channel:bundleIdentifier];
#else
    //關閉日誌打印
    [UMConfigure setLogEnabled:false];
    //使用加密方式上傳日誌
    [UMConfigure setEncryptEnabled:true];
    [UMConfigure initWithAppkey:MOBILE_CLICK_KEY_PRODUCTION channel:@"IOS APP"];
#endif
  • 在發佈版本時,保存並向友盟後臺上傳符號表文件:

  • 每天抽時間去後臺關注一下應用發生了哪些異常,及時定位異常並修復.

從自定義接口中獲取

如果apple自帶的異常收集和第三方都不能滿足產品的需求,就需要自己定義捕獲並收集異常iOS的異常捕獲主要是通過:

//定義異常捕獲函數原型
typedef void NSUncaughtExceptionHandler(NSException *exception);

//註冊捕獲函數
FOUNDATION_EXPORT void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler * _Nullable);

來實現.

NSException內部聲明:

@interface NSException : NSObject <NSCopying, NSSecureCoding> {
    @private
    NSString		*name;
    NSString		*reason;
    NSDictionary	*userInfo;
    id			reserved;
}

+ (NSException *)exceptionWithName:(NSExceptionName)name reason:(nullable NSString *)reason userInfo:(nullable NSDictionary *)userInfo;
- (instancetype)initWithName:(NSExceptionName)aName reason:(nullable NSString *)aReason userInfo:(nullable NSDictionary *)aUserInfo NS_DESIGNATED_INITIALIZER;

@property (readonly, copy) NSExceptionName name;
@property (nullable, readonly, copy) NSString *reason;
@property (nullable, readonly, copy) NSDictionary *userInfo;

@property (readonly, copy) NSArray<NSNumber *> *callStackReturnAddresses API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@property (readonly, copy) NSArray<NSString *> *callStackSymbols API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

- (void)raise;

@end

其中:

  • name:是發生異常的名稱,例如數據訪問越界NSRangeException,參數不合法NSInvalidArgumentException等;
  • reason:是放生異常的原因,是對name的具體解釋信息,例如當試圖在字典中插入nil時
-[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[3]
  • userInfo:用戶信息,一般用於自定義異常時的傳遞信息;
  • callStackSymbols:產生異常的調用堆棧,調用的順序自下往上;
  • raise:用於拋出異常,中斷執行.

大致實現如下:

@interface ExceptionHandler : NSObject
+ (void)registerExceptionHandler;
@end

@implementation ExceptionHandler
+ (void)registerExceptionHandler {
    void(^dealException)(NSException *) = ^(NSException *exception) {
        NSLog(@"name:%@", exception.name);
        NSLog(@"reason:%@", exception.reason);
        NSLog(@"userInfo:%@", exception.userInfo);
        NSLog(@"callStackSymbols:%@", exception.callStackSymbols);
        //保存信息到本地,在需要的時候發送信息到服務器
    };
    IMP uncaughExceptionHandler =  imp_implementationWithBlock(dealException);
    NSSetUncaughtExceptionHandler((void(*)(NSException *))uncaughExceptionHandler);
}

@end

這裏有一個細節問題:系統提供了捕獲系統異常的方法,但是隻有這一個方法,如果應用中有多個捕獲異常的實現,那麼先註冊的異常捕獲方法就會被後來註冊的方法覆蓋掉,造成之前的註冊的異常捕獲方法不能執行,所以負責的註冊方法是在自己註冊方法之前保存之前的捕獲實現,在異常處理方法中,調用原來的異常捕獲實現,從而確保每一個註冊的異常捕獲方法都在可以執行.

@interface ExceptionHandler : NSObject
+ (void)registerExceptionHandler;
@end

@implementation ExceptionHandler
+ (void)registerExceptionHandler {
    static NSUncaughtExceptionHandler *originalExceptionHandler;
    originalExceptionHandler = NSGetUncaughtExceptionHandler();
    void(^dealException)(NSException *) = ^(NSException *exception) {
        if (originalExceptionHandler) {
            originalExceptionHandler(exception);
        }
        NSLog(@"name:%@", exception.name);
        NSLog(@"reason:%@", exception.reason);
        NSLog(@"userInfo:%@", exception.userInfo);
        NSLog(@"callStackSymbols:%@", exception.callStackSymbols);
        //保存信息到本地,在需要的時候發送信息到服務器(實際操作中還需要保存對應的設備類型以確定所使用的指令集等信息)
        
    };
    IMP uncaughExceptionHandler =  imp_implementationWithBlock(dealException);
    NSSetUncaughtExceptionHandler((void(*)(NSException *))uncaughExceptionHandler);
}

@end

從審覈反饋獲取

如果你提交的應用在審覈期間出現了閃退的情況,據說一般審覈人員會把設備上的異常日誌附加在附件中反饋給開發者,不過儘量做好測試工作不要收到這種類型的信息.

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章