• 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 = 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:@"[%@]-[%@]-%@
    ",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(@"首页", @"这是日志信息!",@"可以多参数");

    好的,那本文就结束了,这也是将我工作中用的的分享给大家,老鸟就可以飞过了~~有什么看不明白的就留言吧。

  • 相关阅读:
    神经网络加速与模型压缩
    字符串分割函数
    物体检测整理
    deeplab算法解析
    kmeans算法
    封装caffe版的deeplab为库供第三方使用
    进程 守护
    socketserver tcp黏包
    计算机
    网络 协议 2
  • 原文地址:https://www.cnblogs.com/xgao/p/6553334.html
Copyright © 2020-2023  润新知