IOS异常日志记录与展现功能
在平常的APP开发过程中经常碰到程序遇到异常闪退的问题,通过日志可以把相关的详细错误信息进行记录,本实例要记录不管在哪个页面出错都要进行记录,这边使用到的日志记录插件CocoaLumberjack,以文本的形式记录错误信息,然后再去读取各个文本的内容进行展示;当然现在有很多第三方的插件比如友盟也已经集成错误记录的功能;
效果图如下:
1:封装DDLogger的类
MyFileLogger.h文件
#import <Foundation/Foundation.h>
#import <CocoaLumberjack.h>
@interface MyFileLogger : NSObject
@property (nonatomic, strong, readwrite) DDFileLogger *fileLogger;
+(MyFileLogger *)sharedManager;
@end
MyFileLogger.m文件
#import "MyFileLogger.h"
@implementation MyFileLogger
#pragma mark - Inititlization
- (instancetype)init
{
self = [super init];
if (self) {
[self configureLogging];
}
return self;
}
#pragma mark 单例模式
static MyFileLogger *sharedManager=nil;
+(MyFileLogger *)sharedManager
{
static dispatch_once_t once;
dispatch_once(&once, ^{
sharedManager=[[self alloc]init];
});
return sharedManager;
}
#pragma mark - 配记日志类型
- (void)configureLogging
{
#ifdef DEBUG
[DDLog addLogger:[DDASLLogger sharedInstance]];
[DDLog addLogger:[DDTTYLogger sharedInstance]];
#endif
[DDLog addLogger:self.fileLogger];
}
#pragma mark - 初始化文件记录类型
- (DDFileLogger *)fileLogger
{
if (!_fileLogger) {
DDFileLogger *fileLogger = [[DDFileLogger alloc] init];
fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling
fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
_fileLogger = fileLogger;
}
return _fileLogger;
}
@end
这边是设置24小时进行记录一个文件
2:出进异常进行记录
MyExceptionHandler.h文件
#import <Foundation/Foundation.h>
#import <CocoaLumberjack.h>
@interface MyExceptionHandler : NSObject
+ (void)setDefaultHandler;
+ (NSUncaughtExceptionHandler *)getHandler;
+ (void)TakeException:(NSException *) exception;
@end
#import "MyExceptionHandler.h"
void UncaughtExceptionHandler(NSException * exception)
{
NSArray * arr = [exception callStackSymbols];
NSString * reason = [exception reason];
NSString * name = [exception name];
NSString * url = [NSString stringWithFormat:@"========异常错误报告========\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@",name,reason,[arr componentsJoinedByString:@"\n"]];
DDLogError(@"%@\n\n",url);
}
@implementation MyExceptionHandler
+ (void)setDefaultHandler
{
NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
}
+ (NSUncaughtExceptionHandler *)getHandler
{
return NSGetUncaughtExceptionHandler();
}
+ (void)TakeException:(NSException *)exception
{
NSArray * arr = [exception callStackSymbols];
NSString * reason = [exception reason];
NSString * name = [exception name];
NSString * url = [NSString stringWithFormat:@"========异常错误报告========\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@",name,reason,[arr componentsJoinedByString:@"\n"]];
DDLogError(@"%@",url);
}
@end
这个文件也是当出现异常会执行
3:AppDelegate配置的内容
AppDelegate.h文件内容
#import <UIKit/UIKit.h>
#import <DDLog.h>
#import <CocoaLumberjack.h>
#import "MyExceptionHandler.h"
#import "MyFileLogger.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic, strong) MyFileLogger *logger;
@end
AppDelegate.m文件:
#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
static const int ddLogLevel = LOG_LEVEL_VERBOSE;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//初始化
[MyExceptionHandler setDefaultHandler];
self.logger=[[MyFileLogger alloc]init];
NSString *path = NSHomeDirectory();//主目录
NSLog(@"当前项目的路径:%@",path);
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
}
- (void)applicationWillTerminate:(UIApplication *)application {
}
@end
这边重点是设置DDLOG的记录等级ddLogLevel,以及上面两个文件的初始化[MyExceptionHandler setDefaultHandler];self.logger=[[MyFileLogger alloc]init];
实现上面的代码已经能够记录异常的内容;
4:创建一个错误的异常代码
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *test=@[@"123",@"444"];
id testID=test[5];
}
执行到这段代码便记录异常的内容,接着进行内容的展示,是以一个列表展示每个日志文件,然后一个详细页面进行展示;
5:日志列表
loggerTableViewController.h文件
#import <UIKit/UIKit.h>
#import <CocoaLumberjack.h>
#import "AppDelegate.h"
#import "LoggerDetailViewController.h"
@interface loggerTableViewController : UIViewController
@end
#import "loggerTableViewController.h"
#define BLSRecyclingRecordViewController_CellIdentifier @"MyTablecell"
@interface loggerTableViewController ()<UITableViewDataSource, UITableViewDelegate>
@property (strong, nonatomic) UITableView *myTableView;
@property (nonatomic, strong) NSDateFormatter *dateFormatter;
@property (nonatomic, weak) DDFileLogger *fileLogger;
@property (nonatomic, strong) NSArray *logFiles;
@end
@implementation loggerTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
//加载日志文件
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
_fileLogger = delegate.logger.fileLogger;
[self loadLogFiles];
if (!_myTableView) {
_myTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height) style:UITableViewStyleGrouped];
_myTableView.showsVerticalScrollIndicator = NO;
_myTableView.showsHorizontalScrollIndicator=NO;
_myTableView.dataSource = self;
_myTableView.delegate = self;
[_myTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:BLSRecyclingRecordViewController_CellIdentifier];
[self.view addSubview:_myTableView];
}
}
//读取日志的文件个数
- (void)loadLogFiles
{
self.logFiles = self.fileLogger.logFileManager.sortedLogFileInfos;
}
//时间格式
- (NSDateFormatter *)dateFormatter
{
if (_dateFormatter) {
return _dateFormatter;
}
_dateFormatter = [[NSDateFormatter alloc] init];
[_dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
return _dateFormatter;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - Table view data source
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
if (section==0) {
return 40;
}
return 10;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return 1;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0) {
return self.logFiles.count;
}
return 1;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *headView=[[UIView alloc]initWithFrame:CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.width, 30)];
if (section==0) {
UILabel *myLabel=[[UILabel alloc]initWithFrame:CGRectMake(10, 10, [[UIScreen mainScreen] bounds].size.width, 30)];
myLabel.text=@"日记列表";
[headView addSubview:myLabel];
}
return headView;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:BLSRecyclingRecordViewController_CellIdentifier];
if (indexPath.section == 0) {
DDLogFileInfo *logFileInfo = (DDLogFileInfo *)self.logFiles[indexPath.row];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = indexPath.row == 0 ? NSLocalizedString(@"当前", @"") : [self.dateFormatter stringFromDate:logFileInfo.creationDate];
cell.textLabel.textAlignment = NSTextAlignmentLeft;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
cell.textLabel.textAlignment = NSTextAlignmentCenter;
cell.textLabel.text = NSLocalizedString(@"清理旧的记录", @"");
}
return cell;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (indexPath.section == 0) {
DDLogFileInfo *logFileInfo = (DDLogFileInfo *)self.logFiles[indexPath.row];
NSData *logData = [NSData dataWithContentsOfFile:logFileInfo.filePath];
NSString *logText = [[NSString alloc] initWithData:logData encoding:NSUTF8StringEncoding];
LoggerDetailViewController *detailViewController = [[LoggerDetailViewController alloc] initWithLog:logText
forDateString:[self.dateFormatter stringFromDate:logFileInfo.creationDate]];
[self.navigationController pushViewController:detailViewController animated:YES];
} else {
for (DDLogFileInfo *logFileInfo in self.logFiles) {
//除了当前 其它进行清除
if (logFileInfo.isArchived) {
[[NSFileManager defaultManager] removeItemAtPath:logFileInfo.filePath error:nil];
}
}
[self loadLogFiles];
[self.myTableView reloadData];
}
}
@end
这边把表格分成两部分,一部分是日志文件的列表,以及一个清除功能,清除功能主要是对先前的文件进行删除的操作,读取日志的个数及日志时间,日志详细内容
DDLogFileInfo
6:异常的详细信息页面
LoggerDetailViewController.h代码
#import <UIKit/UIKit.h>
@interface LoggerDetailViewController : UIViewController
- (id)initWithLog:(NSString *)logText forDateString:(NSString *)logDate;
@end
LoggerDetailViewController.m文件
#import "LoggerDetailViewController.h"
@interface LoggerDetailViewController ()
@property (nonatomic, strong) NSString *logText;
@property (nonatomic, strong) NSString *logDate;
@property (nonatomic, strong) UITextView *textView;
@end
@implementation LoggerDetailViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.textView = [[UITextView alloc] initWithFrame:self.view.bounds];
self.textView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.textView.editable = NO;
self.textView.text = self.logText;
[self.view addSubview:self.textView];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (id)initWithLog:(NSString *)logText forDateString:(NSString *)logDate
{
self = [super initWithNibName:nil bundle:nil];
if (self) {
_logText = logText;
_logDate = logDate;
self.title = logDate;
}
return self;
}
@end
7:如果有第三方其它记录异常的使用,最好用DEBUG模式本地记录异常日志,不然日志只会被记录在一个地方
//友盟统计加载 [HYBUMAnalyticsHelper UMAnalyticStart]; #ifdef DEBUG //日志框架(放在其它SDK下面 MyExceptionHandler) [MyExceptionHandler setDefaultHandler]; #else #endif [MyFileLogger sharedManager];
这样便可以实现不管在哪个页面出出异常都可以进行记录,因为实例比较小,如果要源代码可以留下邮箱统一进行发送;