经营你的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 + 1861 libobjc.A.dylib 0x343181e5 objc_exception_throw + 322 CoreFoundation 0x340c87b9 +[NSException raise:format:] + 03 CoreFoundation 0x340c87db +[NSException raise:format:] + 344 Foundation 0x35a12493 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:] + 9985 Foundation 0x35a3afb5 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:] + 1086 MyiOSapplication 0x0022b7e9 -[CinThreadRunLoopActionManager(Protected) performAction:] + 14413 UIKit 0x374b36b5 -[UIViewController _setViewAppearState:isAnimating:] + 14414 UIKit 0x374b38c1 -[UINavigationController viewWillAppear:] + 28815 UIKit 0x374b36b5 -[UIViewController _setViewAppearState:isAnimating:] + 14416 UIKit 0x3750e61b -[UIViewController beginAppearanceTransition:animated:] + 19017 UIKit 0x3750b415 -[UITabBarController transitionFromViewController:toViewController:transition:shouldSetSelected:] + 18418 UIKit 0x3750b357 -[UITabBarController transitionFromViewController:toViewController:] + 3019 UIKit 0x3750ac91 -[UITabBarController _setSelectedViewController:] + 30020 UIKit 0x3750a9c5 -[UITabBarController setSelectedIndex:] + 24021 MyiOSapplication 0x0007ef1d +[Utility ResetCurrentTabIndex] + 17222 MyiOSapplication 0x001a87bd -[UIViewController(statusBar) dismissModalViewControllerAnimatedEx:] + 41623 MyiOSapplication 0x001793fb -[ImageProcessingViewController save:] + 69024 CoreFoundation 0x34022435 -[NSObject performSelector:withObject:withObject:] + 5225 UIKit 0x3748c9eb -[UIApplication sendAction:to:from:forEvent:] + 6226 UIKit 0x3748c9a7 -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 3027 UIKit 0x3748c985 -[UIControl sendAction:to:forEvent:] + 4428 UIKit 0x3748c6f5 -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 49229 UIKit 0x3748d02d -[UIControl touchesEnded:withEvent:] + 47630 UIKit 0x3748b50f -[UIWindow _sendTouchesForEvent:] + 31831 UIKit 0x3748af01 -[UIWindow sendEvent:] + 38032 UIKit 0x374714ed -[UIApplication sendEvent:] + 35633 UIKit 0x37470d2d _UIApplicationHandleEvent + 580834 GraphicsServices 0x308a3df3 PurpleEventCallback + 88235 CoreFoundation 0x3409c553 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 3836 CoreFoundation 0x3409c4f5 __CFRunLoopDoSource1 + 14037 CoreFoundation 0x3409b343 __CFRunLoopRun + 137038 CoreFoundation 0x3401e4dd CFRunLoopRunSpecific + 30039 CoreFoundation 0x3401e3a5 CFRunLoopRunInMode + 10440 GraphicsServices 0x308a2fcd GSEventRunModal + 15641 UIKit 0x3749f743 UIApplicationMain + 109042 MyiOSapplication 0x000d4ccb main + 17443 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线程发生未捕获异常,都可以先引导用户发送日志,再把进程崩溃掉。
明天继续探讨异常日志的进一步改造
转载于:https://www.cnblogs.com/ligun123/archive/2012/03/31/2426419.html
经营你的iOS应用日志(二):异常日志相关推荐
- 如何使用SpringBoot AOP 记录操作日志、异常日志?
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者:咫尺的梦想_w cnblogs.com/wm-dv/ ...
- SpringBoot AOP 记录操作日志、异常日志
使用SpringBoot AOP 记录操作日志.异常日志 我们在做项目时经常需要对一些重要功能操作记录日志,方便以后跟踪是谁在操作此功能.在操作某些功能时也有可能会发生异常,但是每次发生异常要定位原因 ...
- ssm 项目记录用户操作日志和异常日志
ssm 项目记录用户操作日志和异常日志 参考文章: (1)ssm 项目记录用户操作日志和异常日志 (2)https://www.cnblogs.com/mei-m/p/10231792.html (3 ...
- 使用SpringBoot AOP 记录操作日志、异常日志
https://www.cnblogs.com/wm-dv/p/11735828.html
- iOS应用日志:开始编写日志组件与异常日志
应用日志(一):开始编写日志组件 对于那些做后端开发的工程师来说,看 LOG解Bug应该是理所当然的事,但我接触到的移动应用开发的工程师里面,很多人并没有这个意识,查Bug时总是一遍一遍的试图重现,试 ...
- 熟读《阿里巴巴java开发手册》(二、异常日志)
目录 (一) 异常处理 (二) 日志规约 (一) 异常处理 1. [强制] Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方式来处理, ...
- 无异常日志,就不能排查问题了???
众所周知,日志是排查问题的重要手段.关于日志设计,以及怎么根据从[用户报障]环节开始到秒级定位问题这个我们下一期说(绝非套路),这一期,主要讲一下,在没有异常日志的情况下,如何定位问题.没有日志当真能 ...
- angular代码分析之异常日志设计
angular代码分析之异常日志设计 错误异常是面向对象开发中的记录提示程序执行问题的一种重要机制,在程序执行发生问题的条件下,异常会在中断程序执行,同时会沿着代码的执行路径一步一步的向上抛出异常,最 ...
- AOP基本概念、AOP底层实现原理、AOP经典应用【事务管理、异常日志处理、方法审计】...
1 什么是AOP AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件 ...
最新文章
- mysql 查看数据库占用空间的大小
- 配置MySQL数据库单机多实例
- 华为鸿蒙os系统有哪些黑科技,华为首款搭载鸿蒙os智慧产品除了鸿蒙系统还有这黑科技...
- 单词九连猜python编程_python实现猜单词游戏
- java mssql jdbc_从零开始学JAVA(05)-连接数据库MSSQL(JDBC代码篇)
- Java 获取集合元素的值
- 《Java编程的逻辑》终于上市了!,java开发面试笔试题
- 深入Node.js的模块机制
- 7.数据中台 --- 数据开发:数据体系建设
- 一个很小的 截图 库。 只需要依赖 jQuery
- Android Activity防劫持方案
- Java 苹果支付applepay服务端验证
- 康托尔悖论:大全集不存在,即包含一切集合的集合是否存在
- 即时聊天系统(IM)发送文件语音视频的两种方式比较
- 递归实现部门树形数据结构
- shiny 服务器未响应,shiny-server 安装过程出现问题总结
- 苹果电脑为什么要换 CPU:Intel 与 ARM 的战争
- 基于微信小程序的教学辅导平台设计与实现
- 艾美捷—如何选择合适的SAM甲基转移酶活性分析试剂盒?
- 【干货收藏】数据分析师必备的20种分析思维
热门文章
- 固态和机械硬盘组raid_电脑是固态硬盘好还是机械硬盘
- 【java】为什么HashMap桶中节点个数超过8才转为红黑树?
- 【Flink】Flink AscendingTimestampExtractor - Timestamp monotony violated
- 【算法】归并排序 小和 问题
- 【Elasticsearch】Elasticsearch 集群 运维 高性能 架构设计 高负载
- 【java】ASM代理方式 Byte-Buddy代理方式 Javassist代理方式
- 【缓存】缓存,这么用才真正达到缓存的效果
- 【ES】ES Attempted to send a bulk request to elasticsearch but Elasticsearch appears to be unreachabl
- 【JVM】JVM 内联优化
- 【flume】flume读取web应用某个文件夹下日志到hdfs