美文网首页iOS常用
ios 本地日志, 日志上传服务器

ios 本地日志, 日志上传服务器

作者: FM_0138 | 来源:发表于2019-05-22 11:26 被阅读0次

    原文地址: http://www.cnblogs.com/xgao/p/6553334.html

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

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

    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,不方便,那能不能再优化一些,让它更简单的调用呢?我可以用到宏来优化,我们这样定义一个宏,如下:

    // 记录本地日志

    #defineLLog(module,...) [[LogManager sharedInstance] logInfo:module logStr:__VA_ARGS__,nil]

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

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

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

    相关文章

      网友评论

        本文标题:ios 本地日志, 日志上传服务器

        本文链接:https://www.haomeiwen.com/subject/jgeyzqtx.html