IOS本地日志记录方案
我们在项目中日志记录这块也算是比较重要的,有时候用户程序出什么问题,光靠服务器的日志还不能准确的找到问题
现在一般记录日志有几种方式:
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/ (需要翻墙)
具体实现代码
我们先将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 = ;
// 日志文件保存目录
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 / ( * );
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 > ? [dataDic objectForKey:@"data"] : nil; if([resultDic isEqual:[NSNull null]]){
error = [NSError errorWithDomain:[NSString stringWithFormat:@"请求失败,data没有数据!"] code: 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 == ) {
// 拉取指定日期的 // "dates": ["2017-03-01", "2017-03-11"]
NSArray* dates = resultDic[@"dates"]; // 压缩日志
created = [self compressLog:dates];
}else if(type == ){
// 拉取全部 // 压缩日志
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:];
这里可能有人发现我们在记录日志的时候为什么最后面要加上nil,因为这个是OC中动态参数的结束后缀,不加上nil,程序就不知道你有多少个参数,可能有人又要说了,NSString的 stringWithFormat 方法为什么不需要加 nil 也可以呢,那是因为stringWithFormat里面用到了占位符,就是那些 %@ %i 之类的,这样程序就能判断你有多少个参数了,所以就不用加 nil 了
看到这里,可能大家觉得这个记录日志的方法有点长,后面还加要nil,不方便,那能不能再优化一些,让它更简单的调用呢?我可以用到宏来优化,我们这样定义一个宏,如下:
// 记录本地日志
#define LLog(module,...) [[LogManager sharedInstance] logInfo:module logStr:__VA_ARGS__,nil]
这样我们使用的时候就方便了,这样调用就行了。
LLog(@"首页", @"这是日志信息!",@"可以多参数");
好的,那本文就结束了,这也是将我工作中用的的分享给大家,老鸟就可以飞过了~~有什么看不明白的就留言吧。
IOS本地日志记录方案的更多相关文章
- IOS本地日志记录解决方案
我们在项目中日志记录这块也算是比较重要的,有时候用户程序出什么问题,光靠服务器的日志还不能准确的找到问题 现在一般记录日志有几种方式: 1.使用第三方工具来记录日志,如腾讯的Bugly,它是只把程序的 ...
- iOS 本地缓存实现 方案借鉴
在手机应用程序开发中,为了减少与服务端的交互次数,加快用户的响应速度,一般都会在iOS设备中加一个缓存的机制,前面一篇文章介绍了iOS设备的内存缓存,这篇文章将设计一个本地缓存的机制. 功能需求 这个 ...
- IOS异常日志记录与展现功能
在平常的APP开发过程中经常碰到程序遇到异常闪退的问题,通过日志可以把相关的详细错误信息进行记录,本实例要记录不管在哪个页面出错都要进行记录,这边使用到的日志记录插件CocoaLumberjack,以 ...
- C# 面向切面编程--监控日志记录方案
背景:现在公司整体在做监控平台,要求把各个部分的细节都记录下来,在前台页面上有所显示,所以现在需要做的就是一个监控日志的记录工作,今天讲的就是渲染监控日志的例子. 现状:当前的渲染程序没有为监控日志记 ...
- Python中的日志记录方案-logging模块&loguru模块
原文链接 原创: 崔庆才 在 Python 中,一般情况下我们可能直接用自带的 logging 模块来记录日志,包括我之前的时候也是一样.在使用时我们需要配置一些 Handler.Formatter ...
- Python 中更优雅的日志记录方案
在 Python 中,一般情况下我们可能直接用自带的 logging 模块来记录日志,包括我之前的时候也是一样.在使用时我们需要配置一些 Handler.Formatter 来进行一些处理,比如把日志 ...
- FastAPI logger日志记录方案 loguru模块
实现方式: 采用 loguru 模块.跟flask直接挂载到app上有区别,当然也可以尝试去这样做. 但是 好像没有这个必要.要的就是个快速.整那些子虚乌有的东西完全木有意义. 1.首先是去项目git ...
- iOS崩溃日志记录工具--CrashlyTics
http://try.crashlytics.com Crashlytics优势: 1.Crashlytics基本不会漏掉任何应用崩溃的信息 2.Crashlytics对崩溃日志管理很人性化,会根据崩 ...
- tcp通信客户端本地日志查看
最近有一个需求,app要接sdk,只涉及到客户端tcp通信,不涉及服务端接口调用.本文主要从adb环境准备.android/ios本地日志查看实战,进行分析整理. 一.adb查看Android本地日志 ...
随机推荐
- HTML5 & CSS3初学者指南(3) – HTML5新特性
介绍 本文介绍了 HTML5 的一些新特性.主要包含以下几个方面: Web 存储 地理位置 拖放 服务器发送事件 Web存储 HTML5 Web 存储的设计与构想是一个更好的机制来存储客户端的网络数据 ...
- Js让光标停在输入框input框最后面
var val = $(this) val(); 1.$(this). focus(); 2.$(this).val(""); 3.$(this).val(val); 1. ...
- js实现360度图片旋转
▓▓▓▓▓▓ 大致介绍 这次是一个简单的效果,就是思路的问题 效果: ▓▓▓▓▓▓ 思路 旋转的效果就是根据鼠标的的移动距离来显示不同的图片,形成视觉差,仿佛就是在正真的旋转 由于效果是根据鼠标的移动 ...
- Tooltip浮动提示框效果(掌握里面的小知识)
使用原生JavaScript设计和实现Tooltip浮动提示框特效,了解代码简化.事件绑定.事件冒泡等技巧和知识. 特效四个关键点:显示:鼠标移到ToolTip超链接上时,ToolTip提示框可以显示 ...
- sed 命令详解
sed 用于筛选和转换文本的流编辑器 描述: sed是一个流编辑器,流编辑器对一个输入流执行基本的文本转换(输入流来自文件或者管道行).虽然在某些方面类似于很多可运行脚本的编辑器,但是sed的工作方式 ...
- XmlHepler(拿去就能用)
[系列目录](./XmlCatalog.html) 前言 本篇是这个系列的最后一篇.也是本人第一次写系列文章,虽然系列中大部分内容都是参考网络,但是自己都有敲代码验证.自己再编写过程中也学习到了很多知 ...
- 爬虫之爬取网贷之家在档P2P平台基本数据并存入数据库
python 版本 :3.5.2 Jupyter Notebook 使用库: reuqests (For human) json (用来加载JSON数据) datetime (用来记录抓取所花时间,也 ...
- Git学习之路(5)- 同步到远程仓库及多人协作问题
▓▓▓▓▓▓ 大致介绍 我们写好文件后添加到版本库,但是这样还没有做完,我们还需要将它同步到GitHub的远程仓库上,这里就以我们刚开始的drag项目为例,我们在Git学习之路(2)-安装GIt和创建 ...
- 探索Javascript设计模式---单例模式
最近打算系统的学习javascript设计模式,以便自己在开发中遇到问题可以按照设计模式提供的思路进行封装,这样可以提高开发效率并且可以预先规避很多未知的问题. 先从最基本的单例模式开始. 什么是单例 ...
- jQuery ajax() 参数,回调函数,数据类型,发送数据到服务器,高级选项
$.ajax({ options:/*类型:Object; 可选.AJAX 请求设置.所有选项都是可选的.*/ async:/*类型:Boolean; 默认值: true.默认设置下,所有请求均为异 ...