我们在项目中日志记录这块也算是比较重要的,有时候用户程序出什么问题,光靠服务器的日志还不能准确的找到问题

现在一般记录日志有几种方式:

1、使用第三方工具来记录日志,如腾讯的Bugly,它是只把程序的异常日志,程序崩溃日志,以及一些自定义的操作日志上传到Bugly的后台

2、我们把日志记录到本地,在适合的时候再上传到服务器

这里我要介绍的是第二种方法,第一种和第二种可以一起用。

假如现在有下面这样的日志记录要求

1、日志记录在本地

2、日志最多记录N天,N天之前的都需要清理掉

3、日志可以上传到服务器,由服务器控制是否需要上传

4、上传的日志应该压缩后再上传

实现思路

1、日志记录在本地

也就是把字符串保存到本地,我们可以用 将NSString转换成NSData然后写入本地,但是NSData写入本地会对本地的文件进入覆盖,所以我们只有当文件不存在的时候第一次写入的时候用这种方式,如果要将日志内容追加到日志文件里面,我们可以用NSFleHandle来处理

2、日志最多记录N天,N天之前的都需要清理掉

这个就比较容易了,我们可以将本地日志文件名定成当天日期,每天一个日志文件,这样我们在程序启动后,可以去检测并清理掉过期的日志文件

3、日志可以上传到服务器,由服务器控制是否需要上传

这个功能我们需要后台的配合,后台需要提供两个接口,一个是APP去请求时返回当前应用是否需要上传日志,根据参数来判断,第二个接口就是上传日志的接口

4、上传的日志应该压缩后再上传

一般压缩的功能我们可以使用zip压缩,OC中有开源的插件 ZipArchive 地址:http://code.google.com/p/ziparchive/ (需要FQ)

具体实现代码

我们先将ZipArchive引入到项目中,注意还需要引入系统的 libz.tbd 动态库,如下:

由于ZipArchive是使用C 编写的,是不支持ARC的,所以我们需要在项目中把这个类的ARC关闭掉,不然会编译不通过,如下:

给ZipArchive.mm文件添加一个 -fno-objc-arc 标签就可以了

然后就是代码部分了,创建一个日志工具类,LogManager

//
// LogManager.h
// LogFileDemo
//
// Created by xgao on 17/3/9.
// Copyright © 2017年 xgao. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface LogManager : NSObject

/**
 * 获取单例实例
 *
 * @return 单例实例
 */
  (instancetype) sharedInstance;

#pragma mark - Method

/**
 * 写入日志
 *
 * @param module 模块名称
 * @param logStr 日志信息,动态参数
 */
- (void)logInfo:(NSString*)module logStr:(NSString*)logStr, ...;

/**
 * 清空过期的日志
 */
- (void)clearExpiredLog;

/**
 * 检测日志是否需要上传
 */
- (void)checkLogNeedUpload;
@end
//
// LogManager.m
// LogFileDemo
//
// Created by xgao on 17/3/9.
// Copyright © 2017年 xgao. All rights reserved.
//

#import "LogManager.h"
#import "ZipArchive.h"
#import "XGNetworking.h"

// 日志保留最大天数
static const int LogMaxSaveDay = 7;
// 日志文件保存目录
static const NSString* LogFilePath = @"/Documents/OTKLog/";
// 日志压缩包文件名
static NSString* ZipFileName = @"OTKLog.zip";

@interface LogManager()

// 日期格式化
@property (nonatomic,retain) NSDateFormatter* dateFormatter;
// 时间格式化
@property (nonatomic,retain) NSDateFormatter* timeFormatter;

// 日志的目录路径
@property (nonatomic,copy) NSString* basePath;

@end

@implementation LogManager

/**
 * 获取单例实例
 *
 * @return 单例实例
 */
  (instancetype) sharedInstance{

 static LogManager* instance = nil;

 static dispatch_once_t onceToken;
 dispatch_once(&onceToken, ^{
 if (!instance) {
  instance = [[LogManager alloc]init];
 }
 });
 return instance;
}

// 获取当前时间
  (NSDate*)getCurrDate{

 NSDate *date = [NSDate date];
 NSTimeZone *zone = [NSTimeZone systemTimeZone];
 NSInteger interval = [zone secondsFromGMTForDate: date];
 NSDate *localeDate = [date dateByAddingTimeInterval: interval];
 return localeDate;
}
#pragma mark - Init

- (instancetype)init{
 self = [super init];
 if (self) {

 // 创建日期格式化
 NSDateFormatter* dateFormatter = [[NSDateFormatter alloc]init];
 [dateFormatter setDateFormat:@"yyyy-MM-dd"];
 // 设置时区,解决8小时
 [dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
 self.dateFormatter = dateFormatter;

 // 创建时间格式化
 NSDateFormatter* timeFormatter = [[NSDateFormatter alloc]init];
 [timeFormatter setDateFormat:@"HH:mm:ss"];
 [timeFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
 self.timeFormatter = timeFormatter;

 // 日志的目录路径
 self.basePath = [NSString stringWithFormat:@"%@%@",NSHomeDirectory(),LogFilePath];
 }
 return self;
}

#pragma mark - Method

/**
 * 写入日志
 *
 * @param module 模块名称
 * @param logStr 日志信息,动态参数
 */
- (void)logInfo:(NSString*)module logStr:(NSString*)logStr, ...{

#pragma mark - 获取参数

 NSMutableString* parmaStr = [NSMutableString string];
 // 声明一个参数指针
 va_list paramList;
 // 获取参数地址,将paramList指向logStr
 va_start(paramList, logStr);
 id arg = logStr;
 @try {
 // 遍历参数列表
 while (arg) {
  [parmaStr appendString:arg];
  // 指向下一个参数,后面是参数类似
  arg = va_arg(paramList, NSString*);
 }
 } @catch (NSException *exception) {
 [parmaStr appendString:@"【记录日志异常】"];
 } @finally {
 // 将参数列表指针置空
 va_end(paramList);
 }

#pragma mark - 写入日志

 // 异步执行
 dispatch_async(dispatch_queue_create("writeLog", nil), ^{

 // 获取当前日期做为文件名
 NSString* fileName = [self.dateFormatter stringFromDate:[NSDate date]];
 NSString* filePath = [NSString stringWithFormat:@"%@%@",self.basePath,fileName];

 // [时间]-[模块]-日志内容
 NSString* timeStr = [self.timeFormatter stringFromDate:[LogManager getCurrDate]];
 NSString* writeStr = [NSString stringWithFormat:@"[%@]-[%@]-%@\n",timeStr,module,parmaStr];

 // 写入数据
 [self writeFile:filePath stringData:writeStr];

 NSLog(@"写入日志:%@",filePath);
 });
}

/**
 * 清空过期的日志
 */
- (void)clearExpiredLog{

 // 获取日志目录下的所有文件
 NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.basePath error:nil];
 for (NSString* file in files) {
 NSDate* date = [self.dateFormatter dateFromString:file];
 if (date) {
  NSTimeInterval oldTime = [date timeIntervalSince1970];
  NSTimeInterval currTime = [[LogManager getCurrDate] timeIntervalSince1970];
  NSTimeInterval second = currTime - oldTime;
  int day = (int)second / (24 * 3600);
  if (day >= LogMaxSaveDay) {
  // 删除该文件
  [[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@/%@",self.basePath,file] error:nil];
  NSLog(@"[%@]日志文件已被删除!",file);
  }
 }
 }
}

/**
 * 检测日志是否需要上传
 */
- (void)checkLogNeedUpload{

 __block NSError* error = nil;
 // 获取实体字典
 __block NSDictionary* resultDic = nil;
 // 请求的URL,后台功能需要自己做
 NSString* url = [NSString stringWithFormat:@"%@/common/phone/logs",SERVIERURL];
 // 发起请求,从服务器上获取当前应用是否需要上传日志
 [[XGNetworking sharedInstance] get:url success:^(NSString* jsonData) {
 // 获取实体字典
 NSDictionary* dataDic = [Utilities getDataString:jsonData error:&error];
 resultDic = dataDic.count > 0 ? [dataDic objectForKey:@"data"] : nil;
 if([resultDic isEqual:[NSNull null]]){
  error = [NSError errorWithDomain:[NSString stringWithFormat:@"请求失败,data没有数据!"] code:500 userInfo:nil];
 }
 // 完成后的处理
 if (error == nil) {

  // 处理上传日志
  [self uploadLog:resultDic];
 }else{
  LOGERROR(@"检测日志返回结果有误!data没有数据!");
 }
 } faild:^(NSString *errorInfo) {

 LOGERROR(([NSString stringWithFormat:@"检测日志失败!%@",errorInfo]));
 }];
}

#pragma mark - Private

/**
 * 处理是否需要上传日志
 *
 * @param resultDic 包含获取日期的字典
 */
- (void)uploadLog:(NSDictionary*)resultDic{

 if (!resultDic) {
 return;
 }

 // 0不拉取,1拉取N天,2拉取全部
 int type = [resultDic[@"type"] intValue];
 // 压缩文件是否创建成功
 BOOL created = NO;
 if (type == 1) {
 // 拉取指定日期的

 // "dates": ["2017-03-01", "2017-03-11"]
 NSArray* dates = resultDic[@"dates"];

 // 压缩日志
 created = [self compressLog:dates];
 }else if(type == 2){
 // 拉取全部

 // 压缩日志
 created = [self compressLog:nil];
 }
 if (created) {
 // 上传
 [self uploadLogToServer:^(BOOL boolValue) {
  if (boolValue) {
  LOGINFO(@"日志上传成功---->>");
  // 删除日志压缩文件
  [self deleteZipFile];
  }else{
  LOGERROR(@"日志上传失败!!");
  }
 } errorBlock:^(NSString *errorInfo) {
  LOGERROR(([NSString stringWithFormat:@"日志上传失败!!Error:%@",errorInfo]));
 }];
 }
}

/**
 * 压缩日志
 *
 * @param dates 日期时间段,空代表全部
 *
 * @return 执行结果
 */
- (BOOL)compressLog:(NSArray*)dates{

 // 先清理几天前的日志
 [self clearExpiredLog];

 // 获取日志目录下的所有文件
 NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.basePath error:nil];
 // 压缩包文件路径
 NSString * zipFile = [self.basePath stringByAppendingString:ZipFileName] ;

 ZipArchive* zip = [[ZipArchive alloc] init];
 // 创建一个zip包
 BOOL created = [zip CreateZipFile2:zipFile];
 if (!created) {
 // 关闭文件
 [zip CloseZipFile2];
 return NO;
 }
 if (dates) {
 // 拉取指定日期的
 for (NSString* fileName in files) {
  if ([dates containsObject:fileName]) {
  // 将要被压缩的文件
  NSString *file = [self.basePath stringByAppendingString:fileName];
  // 判断文件是否存在
  if ([[NSFileManager defaultManager] fileExistsAtPath:file]) {
   // 将日志添加到zip包中
   [zip addFileToZip:file newname:fileName];
  }
  }
 }
 }else{
 // 全部
 for (NSString* fileName in files) {
  // 将要被压缩的文件
  NSString *file = [self.basePath stringByAppendingString:fileName];
  // 判断文件是否存在
  if ([[NSFileManager defaultManager] fileExistsAtPath:file]) {
  // 将日志添加到zip包中
  [zip addFileToZip:file newname:fileName];
  }
 }
 }
 // 关闭文件
 [zip CloseZipFile2];
 return YES;
}

/**
 * 上传日志到服务器
 *
 * @param returnBlock 成功回调
 * @param errorBlock 失败回调
 */
- (void)uploadLogToServer:(BoolBlock)returnBlock errorBlock:(ErrorBlock)errorBlock{

 __block NSError* error = nil;
 // 获取实体字典
 __block NSDictionary* resultDic;

 // 访问URL
 NSString* url = [NSString stringWithFormat:@"%@/fileupload/fileupload/logs",SERVIERURL_FILE];

 // 发起请求,这里是上传日志到服务器,后台功能需要自己做
 [[XGNetworking sharedInstance] upload:url fileData:nil fileName:ZipFileName mimeType:@"application/zip" parameters:nil success:^(NSString *jsonData) {

 // 获取实体字典
 resultDic = [Utilities getDataString:jsonData error:&error];

 // 完成后的处理
 if (error == nil) {
  // 回调返回数据
  returnBlock([resultDic[@"state"] boolValue]);
 }else{
  if (errorBlock){
  errorBlock(error.domain);
  }
 }
 } faild:^(NSString *errorInfo) {
 returnBlock(errorInfo);
 }];
}

/**
 * 删除日志压缩文件
 */
- (void)deleteZipFile{

 NSString* zipFilePath = [self.basePath stringByAppendingString:ZipFileName];
 if ([[NSFileManager defaultManager] fileExistsAtPath:zipFilePath]) {
 [[NSFileManager defaultManager] removeItemAtPath:zipFilePath error:nil];
 }
}

/**
 * 写入字符串到指定文件,默认追加内容
 *
 * @param filePath 文件路径
 * @param stringData 待写入的字符串
 */
- (void)writeFile:(NSString*)filePath stringData:(NSString*)stringData{

 // 待写入的数据
 NSData* writeData = [stringData dataUsingEncoding:NSUTF8StringEncoding];

 // NSFileManager 用于处理文件
 BOOL createPathOk = YES;
 if (![[NSFileManager defaultManager] fileExistsAtPath:[filePath stringByDeletingLastPathComponent] isDirectory:&createPathOk]) {
 // 目录不存先创建
 [[NSFileManager defaultManager] createDirectoryAtPath:[filePath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
 }
 if(![[NSFileManager defaultManager] fileExistsAtPath:filePath]){
 // 文件不存在,直接创建文件并写入
 [writeData writeToFile:filePath atomically:NO];
 }else{

 // NSFileHandle 用于处理文件内容
 // 读取文件到上下文,并且是更新模式
 NSFileHandle* fileHandler = [NSFileHandle fileHandleForUpdatingAtPath:filePath];

 // 跳到文件末尾
 [fileHandler seekToEndOfFile];

 // 追加数据
 [fileHandler writeData:writeData];

 // 关闭文件
 [fileHandler closeFile];
 }
}
@end

日志工具的使用

1、记录日志

[[LogManager sharedInstance] logInfo:@"首页" logStr:@"这是日志信息!",@"可以多参数",nil];

2、我们在程序启动后,进行一次检测,看要不要上传日志

// 几秒后检测是否有需要上传的日志
[[LogManager sharedInstance] performSelector:@selector(checkLogNeedUpload) withObject:nil afterDelay:3];

这里可能有人发现我们在记录日志的时候为什么最后面要加上nil,因为这个是OC中动态参数的结束后缀,不加上nil,程序就不知道你有多少个参数,可能有人又要说了,NSString的 stringWithFormat 方法为什么不需要加 nil 也可以呢,那是因为stringWithFormat里面用到了占位符,就是那些 %@ %i 之类的,这样程序就能判断你有多少个参数了,所以就不用加 nil 了

看到这里,可能大家觉得这个记录日志的方法有点长,后面还加要nil,不方便,那能不能再优化一些,让它更简单的调用呢?我可以用到宏来优化,我们这样定义一个宏,如下:

// 记录本地日志
#define LLog(module,...) [[LogManager sharedInstance] logInfo:module logStr:__VA_ARGS__,nil]

这样我们使用的时候就方便了,这样调用就行了。

LLog(@"首页", @"这是日志信息!",@"可以多参数");

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持Devmax!

IOS本地日志记录解决方案的更多相关文章

  1. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  2. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  4. ios – Testflight无法安装应用程序

    我有几个测试人员注册了testflight并连接了他们的设备……他们有不同的ios型号……但是所有这些都有同样的问题.当他们从“safari”或“testflight”应用程序本身单击应用程序的安装按钮时……达到约90%并出现错误消息…

  5. ibm-mobilefirst – 在iOS 7.1上获取“无法安装应用程序,因为证书无效”错误

    当我的客户端将他们的设备更新到iOS7.1,然后尝试从AppCenter更新我们的应用程序时,我收到了上述错误.经过一番搜索,我找到了一个类似问题的帖子here.但是后来因为我在客户端使用AppCenter更新应用程序的环境中,我无法使用USB插件并为他们安装应用程序.在发布支持之前,是否有通过AppCenter进行下载的解决方法?

  6. ios – 视图的简单拖放?

    我正在学习iOS,但我找不到如何向UIView添加拖放行为.我试过了:它说“UIView没有可见的接口声明选择器addTarget”此外,我尝试添加平移手势识别器,但不确定这是否是我需要的它被称为,但不知道如何获得事件的坐标.在iOS中注册移动事件回调/拖放操作的标准简单方法是什么?

  7. ios – 什么控制iTunes中iPhone应用程序支持的语言列表?

    什么控制iPhone应用程序的iTunes页面中支持的语言?

  8. ios – 获得APNs响应BadDeviceToken或Unregistered的可能原因是什么?

    我知道设备令牌在某些时候是有效的.用户如何使其设备令牌变坏?从关于“未注册”的文档:Thedevicetokenisinactiveforthespecifiedtopic.这是否意味着应用程序已被删除?.您应该看到四种分发方法:如果您选择AppStore或Enterprise,您将在后面的对话框中看到Xcode将APNS权利更改为生产:如果选择AdHoc或Development,则aps-environment下的文本将是开发,然后应与后端的配置匹配.

  9. ios – 当我关闭应用程序时,我从调试器获得消息:由于信号15而终止

    我怎么能解决这个问题,我不知道这个链接MypreviousproblemaboutCoredata对我的问题有影响吗?当我cmd应用程序的Q时,将出现此消息.Messagefromdebugger:Terminatedduetosignal15如果谁知道我以前的问题的解决方案,请告诉我.解决方法>来自调试器的消息:每当用户通过CMD-Q(退出)或STOP手动终止应用程序(无论是在iOS模拟器中还是

  10. ios – NSUbiquityIdentityDidChangeNotification和SIGKILL

    当应用程序被发送到后台时,我们会删除观察者吗?我遇到的问题是,当UbiquityToken发生变化时,应用程序终止,因为用户已经更改了iCloud设置.你们如何设法订阅这个通知,如果你不这样做,你会做什么来跟踪当前登录的iCloud用户?

随机推荐

  1. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  2. iOS – genstrings:无法连接到输出目录en.lproj

    使用我桌面上的项目文件夹,我启动终端输入:cd然后将我的项目文件夹拖到终端,它给了我路径.然后我将这行代码粘贴到终端中找.-name*.m|xargsgenstrings-oen.lproj我在终端中收到此错误消息:genstrings:无法连接到输出目录en.lproj它多次打印这行,然后说我的项目是一个目录的路径?没有.strings文件.对我做错了什么的想法?

  3. iOS 7 UIButtonBarItem图像没有色调

    如何确保按钮图标采用全局色调?解决方法只是想将其转换为根注释,以便为“回答”复选标记提供更好的上下文,并提供更好的格式.我能想出这个!

  4. ios – 在自定义相机层的AVFoundation中自动对焦和自动曝光

    为AVFoundation定制图层相机创建精确的自动对焦和曝光的最佳方法是什么?

  5. ios – Xcode找不到Alamofire,错误:没有这样的模块’Alamofire’

    我正在尝试按照github(https://github.com/Alamofire/Alamofire#cocoapods)指令将Alamofire包含在我的Swift项目中.我创建了一个新项目,导航到项目目录并运行此命令sudogeminstallcocoapods.然后我面临以下错误:搜索后我设法通过运行此命令安装cocoapodssudogeminstall-n/usr/local/bin

  6. ios – 在没有iPhone6s或更新的情况下测试ARKit

    我在决定下载Xcode9之前.我想玩新的框架–ARKit.我知道要用ARKit运行app我需要一个带有A9芯片或更新版本的设备.不幸的是我有一个较旧的.我的问题是已经下载了新Xcode的人.在我的情况下有可能运行ARKit应用程序吗?那个或其他任何模拟器?任何想法或我将不得不购买新设备?解决方法任何iOS11设备都可以使用ARKit,但是具有高质量AR体验的全球跟踪功能需要使用A9或更高版本处理器的设备.使用iOS11测试版更新您的设备是必要的.

  7. 将iOS应用移植到Android

    我们制作了一个具有2000个目标c类的退出大型iOS应用程序.我想知道有一个最佳实践指南将其移植到Android?此外,由于我们的应用程序大量使用UINavigation和UIView控制器,我想知道在Android上有类似的模型和实现.谢谢到目前为止,guenter解决方法老实说,我认为你正在计划的只是制作难以维护的糟糕代码.我意识到这听起来像很多工作,但从长远来看它会更容易,我只是将应用程序的概念“移植”到android并从头开始编写.

  8. ios – 在Swift中覆盖Objective C类方法

    我是Swift的初学者,我正在尝试在Swift项目中使用JSONModel.我想从JSONModel覆盖方法keyMapper,但我没有找到如何覆盖模型类中的Objective-C类方法.该方法的签名是:我怎样才能做到这一点?解决方法您可以像覆盖实例方法一样执行此操作,但使用class关键字除外:

  9. ios – 在WKWebView中获取链接URL

    我想在WKWebView中获取tapped链接的url.链接采用自定义格式,可触发应用中的某些操作.例如HTTP://我的网站/帮助#深层链接对讲.我这样使用KVO:这在第一次点击链接时效果很好.但是,如果我连续两次点击相同的链接,它将不报告链接点击.是否有解决方法来解决这个问题,以便我可以检测每个点击并获取链接?任何关于这个的指针都会很棒!解决方法像这样更改addobserver在observeValue函数中,您可以获得两个值

  10. ios – 在Swift的UIView中找到UILabel

    我正在尝试在我的UIViewControllers的超级视图中找到我的UILabels.这是我的代码:这是在Objective-C中推荐的方式,但是在Swift中我只得到UIViews和CALayer.我肯定在提供给这个方法的视图中有UILabel.我错过了什么?我的UIViewController中的调用:解决方法使用函数式编程概念可以更轻松地实现这一目标.

返回
顶部