• 经营你的iOS应用日志(二):异常日志


    http://www.cnblogs.com/alario/archive/2012/03/28/2421574.html#2343515

    如果你去4S店修车,给小工说你的车哪天怎么样怎么样了,小工有可能会立即搬出一台电脑,插上行车电脑把日志打出来,然后告诉你你的车发生过什么故障。汽车尚且如此,何况移动互联网应用呢。

    本文第一篇:经营你的iOS应用日志(一):开始编写日志组件


    言归正传。开发iOS应用,解决Crash问题始终是一个难题。Crash分为两 种,一种是由EXC_BAD_ACCESS引起的,原因是访问了不属于本进程的内存地址,有可能是访问已被释放的内存;另一种是未被捕获的 Objective-C异常(NSException),导致程序向自身发送了SIGABRT信号而崩溃。其实对于未捕获的Objective-C异常, 我们是有办法将它记录下来的,如果日志记录得当,能够解决绝大部分崩溃的问题。这里对于UI线程与后台线程分别说明。

    先看UI线程。iOS SDK提供了NSSetUncaughtExceptionHandler函数,用法如:

    NSSetUncaughtExceptionHandler( handleRootException );

    这样在UI线程发生未捕获异常后,进程崩溃之前,handleRootException会被执行。这个函数实现如下

    static void handleRootException( NSException* exception )
    {
    NSString* name = [ exception name ];
    NSString* reason = [ exception reason ];
    NSArray* symbols = [ exception callStackSymbols ]; // 异常发生时的调用栈
    NSMutableString* strSymbols = [ [ NSMutableString alloc ] init ]; // 将调用栈拼成输出日志的字符串
    for ( NSString* item in symbols )
    {
    [ strSymbols appendString: item ];
    [ strSymbols appendString: @"\r\n" ];
    }

    // 写日志,级别为ERROR
    writeCinLog( __FUNCTION__, CinLogLevelError, @"[ Uncaught Exception ]\r\nName: %@, Reason: %@\r\n[ Fe Symbols Start ]\r\n%@[ Fe Symbols End ]", name, reason, strSymbols );
    [ strSymbols release ];

    // 这儿必须Hold住当前线程,等待日志线程将日志成功输出,当前线程再继续运行
    blockingFlushLogs( __FUNCTION__ );

    // 写一个文件,记录此时此刻发生了异常。这个挺有用的哦
    NSDictionary* dict = [ NSDictionary dictionaryWithObjectsAndKeys:
    currentCinLogFileName(), @"LogFile", // 当前日志文件名称
    currentCinLogFileFullPath(), @"LogFileFullPath", // 当前日志文件全路径
    [ NSDate date ], @"TimeStamp", // 异常发生的时刻
    nil ];
    NSString* path = [ NSString stringWithFormat: @"%@/Documents/", NSHomeDirectory() ];
    NSString* lastExceptionLog = [ NSString stringWithFormat: @"%@LastExceptionLog.txt", path ];
    [ dict writeToFile: lastExceptionLog atomically: YES ];

    }

    而我们的日志组件必须实现blockingFlushLogs函数,确保进程在日志完全写入文件后再退出。这个实现应该很简单吧。

    当应用下次启动时,我们可以检查,如果有 LastExceptionLog.txt,则弹窗引导测试人员将日志发过来。如果iPhone上面配置了EMail帐户,可以很简单的调用 MFMailComposeViewController将日志文件作为附件发送,当然也可以想其它办法。

    记得正式发布的版本要将它条件编译去掉哦。

    其中文件中的最后一条ERROR即为导致崩溃的异常,而从ERROR之前的日志可以看出当前程序的运行情况。ERROR如下:

    <- 03-20 17:21:43 ERROR -> [UI] -[CinUIRunLoopActionManager(Protected) handleRootException:]
    [ Uncaught Exception ]
    Name: NSDestinationInvalidException, Reason: *** -[CinThreadRunLoopActionManager performSelector:onThread:withObject:waitUntilDone:modes:]: target thread exited while waiting for the perform
    [ Fe Symbols Start ]
    0 CoreFoundation 0x340c88d7 __exceptionPreprocess + 186
    1 libobjc.A.dylib 0x343181e5 objc_exception_throw + 32
    2 CoreFoundation 0x340c87b9 +[NSException raise:format:] + 0
    3 CoreFoundation 0x340c87db +[NSException raise:format:] + 34
    4 Foundation 0x35a12493 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:] + 998
    5 Foundation 0x35a3afb5 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:] + 108
    6 MyiOSapplication 0x0022b7e9 -[CinThreadRunLoopActionManager(Protected) performAction:] + 144
    13 UIKit 0x374b36b5 -[UIViewController _setViewAppearState:isAnimating:] + 144
    14 UIKit 0x374b38c1 -[UINavigationController viewWillAppear:] + 288
    15 UIKit 0x374b36b5 -[UIViewController _setViewAppearState:isAnimating:] + 144
    16 UIKit 0x3750e61b -[UIViewController beginAppearanceTransition:animated:] + 190
    17 UIKit 0x3750b415 -[UITabBarController transitionFromViewController:toViewController:transition:shouldSetSelected:] + 184
    18 UIKit 0x3750b357 -[UITabBarController transitionFromViewController:toViewController:] + 30
    19 UIKit 0x3750ac91 -[UITabBarController _setSelectedViewController:] + 300
    20 UIKit 0x3750a9c5 -[UITabBarController setSelectedIndex:] + 240
    21 MyiOSapplication 0x0007ef1d +[Utility ResetCurrentTabIndex] + 172
    22 MyiOSapplication 0x001a87bd -[UIViewController(statusBar) dismissModalViewControllerAnimatedEx:] + 416
    23 MyiOSapplication 0x001793fb -[ImageProcessingViewController save:] + 690
    24 CoreFoundation 0x34022435 -[NSObject performSelector:withObject:withObject:] + 52
    25 UIKit 0x3748c9eb -[UIApplication sendAction:to:from:forEvent:] + 62
    26 UIKit 0x3748c9a7 -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 30
    27 UIKit 0x3748c985 -[UIControl sendAction:to:forEvent:] + 44
    28 UIKit 0x3748c6f5 -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 492
    29 UIKit 0x3748d02d -[UIControl touchesEnded:withEvent:] + 476
    30 UIKit 0x3748b50f -[UIWindow _sendTouchesForEvent:] + 318
    31 UIKit 0x3748af01 -[UIWindow sendEvent:] + 380
    32 UIKit 0x374714ed -[UIApplication sendEvent:] + 356
    33 UIKit 0x37470d2d _UIApplicationHandleEvent + 5808
    34 GraphicsServices 0x308a3df3 PurpleEventCallback + 882
    35 CoreFoundation 0x3409c553 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 38
    36 CoreFoundation 0x3409c4f5 __CFRunLoopDoSource1 + 140
    37 CoreFoundation 0x3409b343 __CFRunLoopRun + 1370
    38 CoreFoundation 0x3401e4dd CFRunLoopRunSpecific + 300
    39 CoreFoundation 0x3401e3a5 CFRunLoopRunInMode + 104
    40 GraphicsServices 0x308a2fcd GSEventRunModal + 156
    41 UIKit 0x3749f743 UIApplicationMain + 1090
    42 MyiOSapplication 0x000d4ccb main + 174
    43 MyiOSapplication 0x000039c8 start + 40
    [ Fe Symbols End ]

    可以看到,即使我们没有编译时生成的符号文件,也能够打印出调用栈上的每个函数的名称,只是没有文件名和行号。

    那么,除了UI线程之外,自己创建的后台线程呢?运行NSRunLoop的后台线程的线程函数应该如下:

    - ( void ) threadProc: ( NSString* )threadName
    {
    NSThread* current = [ NSThread currentThread ];
    [ current setName: threadName ];
    NSAutoreleasePool *pool = [ [ NSAutoreleasePool alloc ] init ];

    // 一个没有实际作用的NSTimer,确保NSRunLoop不退出。不知道有没有更好的办法啊
    _dummyTimer = [ [ NSTimer timerWithTimeInterval: 10.0
    target: self
    selector: @selector( dummyTimerProc: )
    userInfo: nil
    repeats: YES ] retain ];

    NSRunLoop *r = [ NSRunLoop currentRunLoop ];
    [ r addTimer: _dummyTimer forMode: NSDefaultRunLoopMode ];
    @try {
    // 启动后台线程的NSRunLoop
    [ r run ];
    }
    @catch ( NSException *exception ) {
    [ self handleRootException: exception ];
    // 一旦在线程根上捕捉到未知异常,记录异常后本线程退出
    }
    @finally {
    [ _dummyTimer invalidate ];
    [ _dummyTimer release ];
    [ pool release ];
    }
    }

    后台线程的handleRootException与UI线程基本一致。不过为了测试人员更加方便,其实只要不是UI线程发生未捕获异常,都可以先引导用户发送日志,再把进程崩溃掉。

    明天继续探讨异常日志的进一步改造

  • 相关阅读:
    搭建SpringCloud之注册中心Eureka
    学习角色管理模块错误总结---基于SpringMVC框架
    【转】Eclipse 单步调试
    [转]MyBatis的foreach语句详解
    解决pom.xml文件 ---- web.xml is missing and <failOnMissingWebXml> is set to true
    解决Dynamic Web Module 3.0 Requires Java 1.6 or newer
    用maven在eclipse用spring建javaweb工程(一)
    【转载】Eclipse 断点调试
    学习大神笔记之“MyBatis学习总结(三)”
    学习大神笔记之“MyBatis学习总结(二)”
  • 原文地址:https://www.cnblogs.com/ligun123/p/2426419.html
Copyright © 2020-2023  润新知