亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > iOS > 正文

IOS本地日志記錄解決方案

2020-07-26 02:51:58
字體:
來源:轉載
供稿:網友

我們在項目中日志記錄這塊也算是比較重要的,有時候用戶程序出什么問題,光靠服務器的日志還不能準確的找到問題

現在一般記錄日志有幾種方式:

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"http:// 日志保留最大天數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(@"首頁", @"這是日志信息!",@"可以多參數");

以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持武林網!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美成人午夜影院| 2019最新中文字幕| 亚洲精品一二区| 成人免费在线视频网站| 亚洲最大福利网| 97久久国产精品| 国产精品专区第二| 久久精品国产96久久久香蕉| 国产主播精品在线| 久久91超碰青草是什么| 国产成+人+综合+亚洲欧洲| 精品福利视频导航| 亚洲国内精品在线| 精品亚洲夜色av98在线观看| 久久久久久香蕉网| 国模gogo一区二区大胆私拍| 美女国内精品自产拍在线播放| 日韩av一区在线| 91av在线播放| 色综合亚洲精品激情狠狠| 欧美床上激情在线观看| 国产mv免费观看入口亚洲| 欧美日韩成人黄色| 2023亚洲男人天堂| 亚洲国产精品va在看黑人| 欧美性猛交xxxx黑人猛交| 精品露脸国产偷人在视频| 国产一区二区精品丝袜| 国模私拍视频一区| 亚洲精品成a人在线观看| 九九热视频这里只有精品| 亚洲欧洲在线看| 久久久97精品| 精品亚洲国产视频| 久久免费精品视频| 97福利一区二区| 国产精品美女在线观看| 日韩在线欧美在线国产在线| 亚洲视频777| 欧美视频一二三| 国产精品视频大全| 欧美成人免费小视频| 日韩在线一区二区三区免费视频| 欧美视频中文字幕在线| 亚洲视频综合网| 最近2019好看的中文字幕免费| 成人免费观看49www在线观看| 欧美一区二区三区四区在线| 日韩av在线免播放器| 亚洲国产精品久久91精品| 国产午夜一区二区| 亚洲美腿欧美激情另类| 亚洲国产成人精品久久久国产成人一区| 17婷婷久久www| 搡老女人一区二区三区视频tv| 国产69精品久久久久9999| 欧美性xxxxx极品| 国产精品欧美一区二区三区奶水| 久久99久久亚洲国产| 亚洲精品乱码久久久久久按摩观| 日韩在线视频观看正片免费网站| 在线播放国产一区中文字幕剧情欧美| 亚洲风情亚aⅴ在线发布| 国产精品久久999| 日韩高清电影免费观看完整| 国产视频在线观看一区二区| 成人欧美一区二区三区黑人| 中文字幕亚洲在线| 精品亚洲永久免费精品| 九九九久久久久久| 欧美大尺度电影在线观看| 国产欧美精品久久久| 亚洲成人a级网| 久久99亚洲精品| 国产日韩欧美视频| 亚洲一级片在线看| 97国产精品人人爽人人做| 国产精品高潮粉嫩av| 亚洲日本中文字幕免费在线不卡| 久久久av一区| 中文字幕亚洲一区| 欧美人与物videos| 91av在线免费观看视频| 欧美孕妇与黑人孕交| 久久精品久久久久电影| 午夜剧场成人观在线视频免费观看| 色香阁99久久精品久久久| 日韩精品欧美国产精品忘忧草| 日韩电影免费在线观看中文字幕| 久久精品青青大伊人av| 欧美日韩另类字幕中文| 另类专区欧美制服同性| 日韩av中文字幕在线播放| 国产亚洲视频在线| 亚洲天堂精品在线| 91精品视频在线看| 久久综合五月天| 色婷婷av一区二区三区在线观看| 精品露脸国产偷人在视频| 亚洲国内高清视频| 欧美大片欧美激情性色a∨久久| 精品国产成人av| 在线播放国产精品| 亚洲资源在线看| 国产精品久久久久久久一区探花| 久久久久国产精品免费网站| 欧美日韩午夜视频在线观看| 亚洲精品国产综合区久久久久久久| 国产精品女主播视频| 欧美三级欧美成人高清www| 91久久久久久久| www.亚洲人.com| 国语自产精品视频在线看一大j8| 久久久久久久久久婷婷| 羞羞色国产精品| 久久中文字幕视频| 色综合男人天堂| 国产在线98福利播放视频| 成人黄色av网站| 孩xxxx性bbbb欧美| 欧美色xxxx| 成人a免费视频| 欧美日韩一二三四五区| 日韩在线免费观看视频| 日韩精品在线电影| 久久久久女教师免费一区| 欧美一级在线亚洲天堂| 国产精品成人av性教育| 97视频在线看| 久久久免费在线观看| 亚洲精品免费网站| 91久久精品国产91久久性色| 日韩av高清不卡| 欧美丰满老妇厨房牲生活| 另类视频在线观看| 永久免费看mv网站入口亚洲| 色综合导航网站| 92看片淫黄大片看国产片| 国产精品影片在线观看| 欧美亚洲另类制服自拍| 久久在线免费视频| 国产成人精品亚洲精品| 亚洲福利视频在线| 久久亚洲精品毛片| 91在线免费网站| 91精品在线观看视频| 亚洲国产精品va在线看黑人动漫| 久久久精品免费| 91久久久久久久久久| 日韩大陆欧美高清视频区| 欧美亚洲国产日本| 国产一区二区三区在线视频| 成人免费观看49www在线观看| 欧美激情视频一区二区三区不卡| 96精品久久久久中文字幕| 亚洲欧洲自拍偷拍| 精品免费在线观看| 久久久久久亚洲精品| 色偷偷av一区二区三区| 亚洲欧洲日产国码av系列天堂| 国产精品久久久久久av下载红粉| 久久人91精品久久久久久不卡| 久久视频中文字幕| 欧美大成色www永久网站婷|