In our project, the log recording part is also quite important. Sometimes, when there is something wrong with the user program, the server’s log can not accurately find the problem.

There are several ways to log now:

1. Use third-party tools to record logs, such as Bugly of Tencent. It only uploads abnormal logs, program crash logs and some customized operation logs to the background of Bugly

2. We log locally and upload to the server when appropriate

Here I’m going to introduce the second method, the first and the second can be used together.

Suppose you now have the following logging requirements

1. Logs are recorded locally

2. Logs are recorded for a maximum of N days, and the logs generated before N days need to be cleared

3. Logs can be uploaded to the server. The server controls whether to upload logs

4. The uploaded logs should be compressed before being uploaded

Implementation approach

1. Logs are recorded locally

So to save a string locally, we can convert NSString to NSData and write it locally, but writing NSData locally overwrites the local file, so we only do this when the file doesn’t exist the first time we write it, if we want to append log content to a log file, We can do this with NSFleHandle

2. Logs are recorded for a maximum of N days, and the logs generated before N days need to be cleared

This is easier, we can set the local log file name to the current date, one log file per day, so that we can detect and remove expired log files after the program is started

3. Logs can be uploaded to the server. The server controls whether to upload logs

For this function, we need the cooperation of the background. The background needs to provide two interfaces. One is to return whether the current application needs to upload logs when the APP requests it

4. The uploaded logs should be compressed before being uploaded

General compression function we can use the zip, OC in open source plug-in ZipArchive address: http://code.google.com/p/ziparchive/ (FQ)

Concrete implementation code

We will introduce ZipArchive into the project. Note that we also need to introduce the libz.tbd dynamic library of the system.

Since ZipArchive is written in C++, it does not support ARC, so we need to turn off ARC for this class in the project, otherwise the compilation will fail, as follows:

Add a -fno-objc-arc tag to the ziparchiive. Mm file

Next comes the code, creating a logging utility class, LogManager

// // logManager.h // LogFileDemo // // Created by Xgao on 17/3/9. // Copyright © 2017 Xgao. All rights reserved #import <Foundation/Foundation.h> @interface LogManager : NSObject /** * get singleton ** @return singleton */ + (instanceType) sharedInstance; ** @param module module name * @param logStr log information, dynamic parameters */ - (void)logInfo:(NSString*)module logStr:(NSString*)logStr, ... ; /** * Clear expired logs */ - (void)clearExpiredLog; /** * Check whether logs need to be uploaded */ - (void)checkLogNeedUpload; @endCopy the code
//
//  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 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 - Ini
 
- (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;
             * );
            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: 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;
    ) {
        // 拉取指定日期的
 
        // "dates": ["2017-03-01", "2017-03-11"]
        NSArray* dates = resultDic[@"dates"];
 
        // 压缩日志
        created = [self compressLog:dates];
    }){
        // 拉取全部
 
        // 压缩日志
        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];
    }
}
 
@endCopy the code

Use of logging tools

1. Log

[[LogManager sharedInstance] logInfo:@" home "logStr:@" this is log information!",@" can multiple arguments ",nil];Copy the code

2. After the program is started, we conduct a test to see whether to upload logs

[[LogManager sharedInstance] performSelector:@selector(checkLogNeedUpload) withObject:nil afterDelay:];Copy the code

And somebody might notice here why we put nil at the end when we log, because that’s the end of dynamic arguments in OC, and without nil, the program doesn’t know how many arguments you have, and somebody might say, Why does NSString stringWithFormat not have to add nil, because stringWithFormat uses placeholders, things like %, @ % I, so it can tell how many arguments you have, so it doesn’t have to add nil

Now, if you look at this, you might think that this log method is a little bit long, and you want nil at the end, which is inconvenient, but can you optimize it a little bit and make it easier to call? I can use macros to optimize. Let’s define a macro like this:

#define LLog(module,...) [[LogManager sharedInstance] logInfo:module logStr:__VA_ARGS__,nil]Copy the code

So it’s easy for us to use, just call it that way.

LLog(@" home page ", @" This is log information!" ,@" multiple parameters ");Copy the code

Ok, that this article is over, this is also my work with the share to everyone, the old bird can fly ~ ~ what do not understand the message.

More on the IOS local logging scheme

  1. IOS local logging solution

    In our project, the log recording part is also quite important. Sometimes when there is something wrong with the user program, the server’s log can not accurately find the problem. Now there are several ways to record the log: 1. Use a third-party tool to log, such as Tencent’s Bugly, which only puts the program…

  2. Record and display IOS exception logs

    In the usual APP development process often encountered program encountered abnormal flash back problems, through the log can be related to the detailed error information to record, this example to record no matter in which page error to record, here to use the log plug-in CocoaLumberjack, to…

  3. Faceted programming in C# – monitor logging schemes

    Background: Now the company as a whole in the monitoring platform, the requirements of each part of the details are recorded, displayed on the front page, so now need to do is a monitoring log recording work, today is the example of rendering monitoring log. Status: Current renderer does not log for monitoring…

  4. IOS crash logging tool -CrashlyTics

    http://try.crashlytics.com Crashlytics advantages: 1. Crashlytics basic won’t miss any application crash information 2. Crashlytics to crash log management is very human nature, according to collapse…

  5. Docker container log collection solution (Solution 2 FileBeat + Syslog Local Log collection)

    Docker sudo docker service update –lo…

  6. Docker container log collection solution (Solution 1 fileBeat + Local log collection)

    Filebeat simply scans local disk log files, reads the contents of the files and transmits them remotely. By default, docker container logs are recorded in JSON format: {“…

  7. Four easy steps to use log4net for local logging in C#

    Here, I document the steps I took to log locally using Log4Net in my project. It’s hard and mysterious until you know it, but it’s not so hard once you know it. All things being the same, here’s my experience with log4Net. Step 1: Start with…

  8. Easy 4 steps to log locally using log4net in C# (WPF is a little different)

    Here, I document the steps I took to log locally using Log4Net in my project. It’s hard and mysterious until you know it, but it’s not so hard once you know it. All things being the same, here’s my experience with log4Net. Step 1: Start with…

  9. Java logging

    Common-logging is a common logging interface provided by Apache. Users are free to choose a third-party logging component as their implementation, such as Log4j, or the JDK’s own…

Random recommended

  1. Chrome Dev Tools: Become a more productive developer

    The original source http://blog.jobbole.com/22065/ real-time CSS Style editor to select a Dom, can edit the Dom and operation, real time to modify the CSS Style, at the same time CssStyle can protect…

  2. Database concepts

    1. What are stored procedures? What are its advantages? A: A stored procedure is a set of SQL statements that are compiled. Among its advantages, it allows for modular programming, which means that you only need to create a procedure once and that procedure can be called any number of times in the program. Allow faster execution…

  3. Reference parameters, value parameters, ref,out

    (1) a parameter can change its value only when it is referenced. This is the case (2) a parameter can change its value permanently after being referenced (in the form of a return parameter). (3) Multiple parameters can change their value permanently after being referenced or part of multiple parameters.

  4. The most comprehensive jdbcUtils, there is always a suitable for you

    Additional jars, TxQueryRunner. Java file, dbconfig. Properties configuration file, click the link to download: http://files.cnblogs.com/files/xiaoming…

  5. The shell process queries related commands

    If the same process is deployed multiple times, how do I know which one to delete? Ll /proc/ will display the location

  6. Date and time functions in SQL

    The date and time functions are separate for review because they are quite separate. C# also has a similar date and time function. Write a title while you can remember.

  7. Godaddy host from purchase to launch detailed graphic tutorial (2013)

    http://bbs.zhujiusa.com/thread-10-1-1.html go from purchase to host the opening of the detailed graphic tutorials (latest) 2013 go is the world’s NO. In the domain registrar 1, at the same time also…

  8. Wrapper classes for AjaxHelper’s GET and POST requests

    Recently, when learning Ajax, I found that the code is very repetitive when constantly calling GET and POST requests. Some companies will use their own encapsulation method, which can directly call ajax method, but when using it, we should also learn how to encapsulate it and some primitive things.

  9. Java Classic Case study – “Grade Classification”

    /** * 表 述: A = >=90; B = 60-89; C = < 60;

  10. Data Structures and Algorithms — Linked List (04)

    Given a linked list, swap every two adjacent nodes and return the first node. Today’s problem is an upgraded version of it, as follows: A group of k nodes is given a list, each group of K nodes is flipped, and return the flipped chain.