iOS OC版轻量日志的实现

iOS项目记录日志的需求

  1. 可以在代码上控制日志的级别,以便打印不同层级的日志
  2. 需要同时在控制台、文件写入
  3. 需要和NSLog一样的调用格式,方便替换
  4. 需要保证顺序写入,不能发生由于并发写入错误的问题
  5. 以后可以扩展输入到别的地方,比如浏览器、app内其他UI上显示日志等
  6. 日志文件大小限定、设定清理N天前的日志

解决思路

  1. 设计LogLevel,能够实现 类似LogCat的效果。预计只设计NONE、ERROR、INFO、DEBUG。分别表示

    -  NONE      什么都不打印
    -  ERROR     只打印ERROR
    -  INFO      打印INFO和ERROR
    -  DEBUG     全部打印
    
  2. 利用iOS的本地广播通知的功能,在打印日志的接口里,将最终的字符串从广播发出去。在不同的文件日志类、控制台类、其他UI界面注册此通知,分别处理字符串。这同时解决了扩展性的问题。

  3. 实现OC语言风格的可变参数 以及 C语言风格的日志打印接口

  4. 本地广播,在接收的对象处理的时候天然就是顺序的了,不会有多线程的竞争问题

  5. 每次写完文件日志,检查日志文件大小,超过重新创建新文件。每次启动对象检查是否有过期文件,有则删除

知识点

  1. 本地广播的发送API
  2. 可变参数方法:写文件 vfprintf、控制台 vprintf
  3. NSString 转 char*
  4. GCD串行队列
  5. OC类实现类似NSLog风格的API,并且也能够被C语言调用
  6. 日志存储路径 Document 以及查询日志文件的修改日期、大小等API

实现代码:

参考了 Log4OC ,改造成我的版本。

注意,可以监听本地广播,将日志内容做额外处理或者显示到UI等

Log4OC.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface Log4OC : NSObject

#define MYLOG_NotificationName @"com.MyLog.Notification"

//日志级别
typedef NS_ENUM(NSUInteger,LogLevel) {
    LogLevel_ERROR    = 0b00001111,  //全部打印
    LogLevel_DEBUG    = 0b00000111,  //打印WARN、INFO、DEBUG 
    LogLevel_WARN     = 0b00000011,  //打印INFO和WARN
    LogLevel_INFO     = 0b00000001,  //只打印INFO的日志
    LogLevel_NONE     = 0b00000000   //不打印
};

//获取当前的日志文件路径
FOUNDATION_EXPORT NSString* getLogDir(void);
//设置日志级别
FOUNDATION_EXPORT void setLogLevel(LogLevel level);
//设置]保留多少天的日志
FOUNDATION_EXPORT void setRollBackDay(int days);

FOUNDATION_EXPORT void HXLog_ERROR(NSString *message,...) NS_FORMAT_FUNCTION(1,2);
FOUNDATION_EXPORT void HXLog_DEBUG(NSString *message,...) NS_FORMAT_FUNCTION(1,2);
FOUNDATION_EXPORT void HXLog_WARN(NSString *message,...) NS_FORMAT_FUNCTION(1,2);
FOUNDATION_EXPORT void HXLog_INFO(NSString *message,...) NS_FORMAT_FUNCTION(1,2);

@end

Log4OC.m

//
//  Log4OC.m
#import "Log4OC.h"

@implementation Log4OC
static LogLevel __LogLevel = LogLevel_DEBUG;
static int __fileDays = 15;//15day
static dispatch_queue_t __serialQueue;
static NSDateFormatter *__LogFormatter;
static NSString * __filePath = @"";
static NSString * __dirc = @"";
static FILE *__logOutp;

void setLogLevel(LogLevel level){
    __LogLevel = level;
}
void setRollBackDay(int days){
    __fileDays = days;
}

+(void)load {

    __LogFormatter = [[NSDateFormatter alloc] init];
    __LogFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss.sss";
    
    //创建日志目录,只做一次
    creatLogDir();
    
    //删除失效的文件
    deleteFiles(__dirc);
    
    //创建日志文件
    creatLogFile(__dirc);
    
    NSLog(@"LOG日志路径:%@",__filePath);
    //打开输出文件
    __logOutp = fopen([__filePath UTF8String],"a+");
    setvbuf(__logOutp, NULL, _IONBF, 0);
    __serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);//创建串行队列 
}

inline NSString* getLogDir(void){
    return __dirc;
}

static inline void creatLogDir (){
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *dirc = [documentsDirectory stringByAppendingPathComponent:@"LogLog"];
    NSFileManager *fm = [NSFileManager defaultManager];
    if (![fm fileExistsAtPath:dirc]) {
        [fm createDirectoryAtPath:dirc withIntermediateDirectories:YES attributes:nil error:nil];
    }
    __dirc = dirc;
}

static inline void creatLogFile (NSString* path){
    NSFileManager *fm = [NSFileManager defaultManager];
    NSDateFormatter *fmt = [[NSDateFormatter alloc]init];
    fmt.dateFormat = @"yyyy_MM_dd";
    NSDate *nowDay = [NSDate new];
    NSString *timeStr = [fmt stringFromDate:nowDay];
    NSString *filePath = [path stringByAppendingString:[NSString stringWithFormat:@"/%@.log",timeStr]];
    if (![fm fileExistsAtPath:filePath]) {
        [fm createFileAtPath:filePath contents:nil attributes:nil];
    }
    __filePath = filePath;
}

static inline void openNextDateFile () {
    //删除失效的文件
    deleteFiles(__dirc);
    //创建日志文件
    creatLogFile(__dirc);
    
    NSLog(@"LOG日志路径:%@",__filePath);
    if (__logOutp) {
         fclose(__logOutp);
    }
    
    //打开输出文件
    __logOutp = fopen([__filePath UTF8String],"a+");
    setvbuf(__logOutp, NULL, _IONBF, 0);
}


static inline int compareOneDay(NSDate *oneDay,NSDate *anotherDay)
{
    NSString *oneDayStr = [__LogFormatter stringFromDate:oneDay];
    NSString *anotherDayStr = [__LogFormatter stringFromDate:anotherDay];
    NSDate *dateA = [__LogFormatter dateFromString:oneDayStr];
    NSDate *dateB = [__LogFormatter dateFromString:anotherDayStr];
    NSComparisonResult result = [dateA compare:dateB];
//    NSLog(@"oneDay : %@, anotherDay : %@", oneDay, anotherDay);
    if (result == NSOrderedDescending) {
        //在指定时间前面 过了指定时间 过期
//        NSLog(@"oneDay is in the future");
        return 1;
    }
    else if (result == NSOrderedAscending){
        //没过指定时间 没过期
        // NSLog(@"Date1 is in the past");
        return -1;
    }
    //刚好时间一样.
    //NSLog(@"Both dates are the same");
    return 0;
}


static void deleteFiles(NSString* path){
    NSFileManager *fm = [NSFileManager defaultManager];
    NSDate *nowDay = [NSDate new];
    NSTimeInterval day = 24 * 60 * 60 * __fileDays;
    NSDate *day10before = [nowDay dateByAddingTimeInterval:-day];
    //遍历文件夹
    NSError *error = nil;
    NSArray *dirArray = [fm contentsOfDirectoryAtPath:path error:&error];
    if (dirArray) {
        NSLog(@"日志目录下的文件:");
        NSString *oneFilePath = nil;
        for(int i = 0; i < dirArray.count;i++){
            oneFilePath = [dirArray objectAtIndex:i];
            
           
            NSString *realFilePath = [path stringByAppendingString:[NSString stringWithFormat:@"/%@",oneFilePath]];
            NSDictionary *fileAttributes = [fm attributesOfItemAtPath:realFilePath error:&error];
            
            if (fileAttributes) {
                NSNumber *fileSize;
                NSString *creationDate;
                NSDate *fileModDate;
                //文件大小
                if ((fileSize = [fileAttributes objectForKey:NSFileSize])) {
                    NSLog(@"    %@:(size=%qi)\n",oneFilePath ,[fileSize unsignedLongLongValue]);
                }
                
                //文件修改日期
                if ((fileModDate = [fileAttributes objectForKey:NSFileModificationDate])) {
                    //NSLog(@"Modification date: %@\n", fileModDate);
                    if (compareOneDay(day10before,fileModDate)>0) {
                        BOOL blDele= [fm removeItemAtPath:realFilePath error:nil];
                        if (!blDele) {
                            NSLog(@"删除已过期日志文件失败");
                        }
                    }
                }
                //文件创建日期
                else if ((creationDate = [fileAttributes objectForKey:NSFileCreationDate])) {
                    //NSLog(@"File creationDate: %@\n", creationDate);
                    if (compareOneDay(day10before,fileModDate)>0) {
                        BOOL blDele= [fm removeItemAtPath:realFilePath error:nil];
                        if (!blDele) {
                            NSLog(@"删除已过期日志文件失败");
                        }
                    }
                }
            }else{
                NSLog(@"ERROR : %@\n", error);
            }
        }
    }
}


void privateLog(NSString *string){
    dispatch_async(__serialQueue,^{
        //发送本地通知
        [[NSNotificationCenter defaultCenter] postNotificationName:HXLOG_NotificationName object:string];
        
        NSDate *nowDay = [NSDate new];
        NSDateFormatter *fmt = [[NSDateFormatter alloc]init];
        fmt.dateFormat = @"yyyy_MM_dd";
        NSString *oneDayStr = [fmt stringFromDate:nowDay];
        if (![__filePath containsString:oneDayStr]) {
            openNextDateFile();
        }
        
        const char *cString = [string UTF8String];
        //写文件
        fprintf(__logOutp,"%s",cString);
        
        //写控制台
        printf("%s",cString);
        
        
    });
}

void HXLog_ERROR(NSString *message,...){
    
    if ((__LogLevel & LogLevel_ERROR) != LogLevel_ERROR) {
        return;
    }
    va_list argList;
    va_start(argList, message);
    NSString *string = [NSString stringWithFormat:@"%@ [ERROR] : %@\n",[__LogFormatter stringFromDate:[NSDate date]],[[NSString alloc] initWithFormat:message arguments:argList]];
    va_end(argList);
    
    privateLog(string);
}

void HXLog_DEBUG(NSString *message,...){
    
    if ((__LogLevel & LogLevel_DEBUG) != LogLevel_DEBUG) {
        return;
    }
    va_list argList;
    va_start(argList, message);
    NSString *string = [NSString stringWithFormat:@"%@ [DEBUG] : %@\n",[__LogFormatter stringFromDate:[NSDate date]],[[NSString alloc] initWithFormat:message arguments:argList]];
    va_end(argList);
    
    privateLog(string);
}


void HXLog_WARN(NSString *message,...){
    if ((__LogLevel & LogLevel_WARN) != LogLevel_WARN) {
        return;
    }
    va_list argList;
    va_start(argList, message);
    NSString *string = [NSString stringWithFormat:@"%@ [ WARN] : %@\n",[__LogFormatter stringFromDate:[NSDate date]],[[NSString alloc] initWithFormat:message arguments:argList]];
    va_end(argList);
    
    privateLog(string);
}

void HXLog_INFO(NSString *message,...){
    if ((__LogLevel & LogLevel_INFO) != LogLevel_INFO) {
        return;
    }
    va_list argList;
    va_start(argList, message);
    NSString *string = [NSString stringWithFormat:@"%@ [ INFO] : %@\n",[__LogFormatter stringFromDate:[NSDate date]],[[NSString alloc] initWithFormat:message arguments:argList]];
    va_end(argList);
    
    privateLog(string);
}
@end

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值