iOS 基于CocoaLumberjack構(gòu)建自己的Log系統(tǒng)

前言

對(duì)于一個(gè)已經(jīng)上線的產(chǎn)品,如果項(xiàng)目沒有自己的Log系統(tǒng)纽什,產(chǎn)品在線上出現(xiàn)問題措嵌,那就只能抓瞎了,所以項(xiàng)目中有一套自己成熟的Log系統(tǒng)是至關(guān)重要的芦缰。本文主要利用CocoaLumberjack來教大家如何去搭建自己的Log系統(tǒng)企巢。當(dāng)然,每個(gè)項(xiàng)目有自己不同的業(yè)務(wù)邏輯让蕾,你們也可以根據(jù)自己項(xiàng)目的業(yè)務(wù)邏輯來修改就好浪规。

CocoaLumberjack是一個(gè)可以在iOS和Mac開發(fā)中使用的日志庫,使用很簡(jiǎn)單探孝,但很功能很強(qiáng)大笋婿。在這里就不多作介紹了,大家可以去官網(wǎng)上看 -->CocoaLumberjack


好了,現(xiàn)在該說說如何去構(gòu)建自己的Log系統(tǒng)了。

Log系統(tǒng)原理圖

先介紹一下主要構(gòu)成Log系統(tǒng)的四大類:

1兼砖、XZLogFormatter

該類主要是根據(jù)我們自己設(shè)定的格式輸出我們需要保存的log颈娜。

  • 主要實(shí)現(xiàn)DDLogFormatter協(xié)議
  • .m文件中重寫- (NSString *)formatLogMessage:(DDLogMessage *)logMessage方法。這個(gè)方法返回值是 NSString,就是最終 log 的消息體字符串。參數(shù) logMessage 是由 logger 發(fā)的一個(gè) DDLogMessage 對(duì)象。下面貼出方法內(nèi)部實(shí)現(xiàn)
/**
 *  這里的格式: log打印時(shí)間 -> log等級(jí) -> app版本 -> 打印位置 -> log描述
 *
 */
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage{
    NSMutableDictionary *logDict = [NSMutableDictionary dictionary];
    
    //取得文件名
    NSString *locationString;
    NSArray *parts = [logMessage->_file componentsSeparatedByString:@"/"];
    if ([parts count] > 0)
        locationString = [parts lastObject];
    if ([locationString length] == 0)
        locationString = @"No file";
    
    logDict[@"logTime"] = [self timestampToNSString:[self getCurrentDate] formatter:@"MM/dd HH:mm:ss"];
    logDict[@"logLevel"] = [self getLogLevelWithDDLogLevel:logMessage.flag];
    logDict[@"location"] = [NSString stringWithFormat:@"%@:%lu(%@)", locationString, (unsigned long)logMessage.line, logMessage.function];
    logDict[@"des"] = logMessage.message;
    // app版本
    NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString *app_Version = [infoDictionary objectForKey:@"CFBundleShortVersionString"];
    
    NSString *logStr = [NSString stringWithFormat:@"%@ %@ version:%@ location:%@\nDes:%@\n",logDict[@"logTime"],logDict[@"logLevel"],app_Version,logDict[@"location"],logDict[@"des"]];

    return logStr;
}
2捞慌、XZLogger
  • 自定義 logger,繼承DDAbstractDatabaseLogger柬批。在初始化方法中啸澡,先設(shè)定一些基本參數(shù),并且添加一個(gè)UIApplicationWillResignActiveNotification的觀察者萝快,這主要作用是app切換到后臺(tái)會(huì)有通知锻霎,保存日志到沙盒。
- (instancetype)init {
    self = [super init];
    if (self) {
        self.deleteInterval = 0;
        self.maxAge = 0;
        self.deleteOnEverySave = NO;
        self.saveInterval = 60;
        self.saveThreshold = 500;//當(dāng)未保存的log達(dá)到500條時(shí)揪漩,會(huì)調(diào)用db_save方法保存
        
        //注冊(cè)app切換到后臺(tái)通知旋恼,保存日志到沙盒
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(saveLog)
                                                     name:@"UIApplicationWillResignActiveNotification"
                                                   object:nil];
    }
    return self;
}

- (void)saveLog {
    dispatch_async(_loggerQueue, ^{
        [self db_save];
    });
}
  • 重寫- (BOOL)db_log:(DDLogMessage *)logMessage方法,每次打 log 時(shí)奄容,db_log就會(huì)被調(diào)用冰更。該方法主要內(nèi)容是將 log 發(fā)給 formatter,將返回的 log 消息體字符串保存在緩存中昂勒。 db_log 的返回值告訴 DDLog 該條 log 是否成功保存進(jìn)緩存蜀细。
/**
 *  每次打 log 時(shí),db_log會(huì)被調(diào)用
 *
 */
- (BOOL)db_log:(DDLogMessage *)logMessage
{
    if (!_logFormatter) {
        //沒有指定 formatter
        return NO;
    }
    
    if (!_logMessagesArray)
        _logMessagesArray = [NSMutableArray arrayWithCapacity:500]; // saveThreshold只設(shè)置了500條
    
    
    //利用 formatter 得到消息字符串戈盈,添加到緩存奠衔,當(dāng)調(diào)用db_save時(shí)谆刨,寫入沙盒
    [_logMessagesArray addObject:[_logFormatter formatLogMessage:logMessage]];
    return YES;
}
  • 每隔1分鐘或者未寫入文件的log數(shù)達(dá)到 500 時(shí),db_save 就會(huì)自動(dòng)被調(diào)用归斤,這個(gè)時(shí)候我們就可以把存在緩存中的Log全部寫入沙盒文件了
/**
 *  每隔1分鐘或者未寫入文件的log數(shù)達(dá)到 500 時(shí)痊夭,db_save 就會(huì)被調(diào)用
 *
 */
- (void)db_save{
    //判斷是否在 logger 自己的GCD隊(duì)列中
    if (![self isOnInternalLoggerQueue])
        NSAssert(NO, @"db_saveAndDelete should only be executed on the internalLoggerQueue thread, if you're seeing this, your doing it wrong.");
    
    //如果緩存內(nèi)沒數(shù)據(jù),啥也不做
    if ([_logMessagesArray count] == 0) {
        return;
    }
    //獲取緩存中所有數(shù)據(jù)脏里,之后將緩存清空
    NSArray *oldLogMessagesArray = [_logMessagesArray copy];
    _logMessagesArray = [NSMutableArray arrayWithCapacity:0];
    
    //用換行符她我,把所有的數(shù)據(jù)拼成一個(gè)大字符串
    NSString *logMessagesString = [oldLogMessagesArray componentsJoinedByString:@"\n"];
    
    
    //判斷有沒有文件夾,如果沒有迫横,就創(chuàng)建
    NSString *createPath = [NSString stringWithFormat:@"%@/Logs/%@",DOCUMENTS_PATH,oyToStr(USERID)];
    NSString *txtPath = [NSString stringWithFormat:@"%@/%@",createPath,self.fileName];
    if ([[NSFileManager defaultManager] fileExistsAtPath:createPath]) {
       
        if (![[NSFileManager defaultManager] fileExistsAtPath:txtPath]) {
            
            [[NSFileManager defaultManager] createFileAtPath:txtPath contents:nil attributes:nil];
        }
        
    } else {
        
        [[NSFileManager defaultManager] createDirectoryAtPath:createPath withIntermediateDirectories:YES attributes:nil error:nil];
        
        [[NSFileManager defaultManager] createFileAtPath:txtPath contents:nil attributes:nil];
    }
    
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:txtPath];
    
    [fileHandle seekToEndOfFile];  //將節(jié)點(diǎn)跳到文件的末尾
    NSData* stringData  = [[NSString stringWithFormat:@"\n%@",logMessagesString] dataUsingEncoding:NSUTF8StringEncoding];
    [fileHandle writeData:stringData]; //追加寫入數(shù)據(jù)
    
    [fileHandle closeFile];
    
    //跳過iCloud上傳
    [self addSkipBackupAttributeToItemAtPath:txtPath];

}
3番舆、XZUserLogManager

該類有3個(gè)作用

  • 刪除過期Log,目前我定的只保留3天的Log矾踱,多余的全部清除恨狈,時(shí)間可以隨意改,無所謂的呛讲。
#pragma mark - delete out of date user log
- (void)deleteOutOfDateLog {
    
    NSString *logPath = [NSString stringWithFormat:@"%@/Logs/%@/",DOCUMENTS_PATH,oyToStr(USERID)];
    
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyy-MM-dd"];
    dateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT+0800"];;
    //刪除過期的日志
    NSDate *prevDate = [[NSDate date] dateByAddingTimeInterval:-60*60*24*3];
    NSDateComponents *components = [[NSCalendar currentCalendar] components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:prevDate];
    [components setHour:0];
    [components setMinute:0];
    [components setSecond:0];
    //要?jiǎng)h除三天以前的日志(0點(diǎn)開始)
    NSDate *delDate = [[NSCalendar currentCalendar] dateFromComponents:components];
    NSArray *logFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:logPath error:nil];
    for (NSString *file in logFiles)
    {
        NSString *fileName = [file stringByReplacingOccurrencesOfString:@".txt" withString:@""];
        fileName = [fileName stringByReplacingOccurrencesOfString:@"GQ_Log_" withString:@""];
        NSDate *fileDate = [dateFormatter dateFromString:fileName];
        if (nil == fileDate)
        {
            continue;
        }
        if (NSOrderedAscending == [fileDate compare:delDate])
        {
            [[NSFileManager defaultManager] removeItemAtPath:[logPath stringByAppendingString:file] error:nil];
        }
    }

}
  • 動(dòng)態(tài)修改Log等級(jí)和上傳Log到服務(wù)器拴事,這里需要說明一點(diǎn),我們項(xiàng)目是有即時(shí)通訊的圣蝎,所有需要修改Log等級(jí),只需要給用戶推一條消息衡瓶,然后用戶收到消息后徘公,就可以去修改Log等級(jí)了。后面說到XZDynamicLogLevel類的時(shí)候哮针,會(huì)詳細(xì)說明关面。所以上傳Log也是會(huì)給用戶推消息,用戶就后臺(tái)自動(dòng)上傳Log到服務(wù)器了十厢。所以你們看見的XZMessageModel對(duì)象就是我們項(xiàng)目中的消息體對(duì)象等太,這一塊得根據(jù)你們具體的業(yè)務(wù)來設(shè)計(jì)。
/**
 *   接收到服務(wù)器下發(fā)的日志消息
 *
 */
- (void)adjustLogLevelWithMsgModel:(XZMessageModel *)msgModel {
    
    //解析出來日志消息體
    XZUserLogMsgModel *userLogModel = msgModel.content;
    
    switch (userLogModel.actionType) {
        case XZ_USER_LOG_ACTION_TYPE_LogLevel: { // 修改用戶日志等級(jí)
            
            self.logLevel = userLogModel.logLevel;//先改變Log等級(jí)
            [XZDynamicLogLevel ddSetLogLevel:(DDLogLevel)userLogModel.logLevel];//存數(shù)據(jù)庫
            break;
        }
        case XZ_USER_LOG_ACTION_TYPE_GetLog: { // 上傳日志
            
            //獲取日志本地路徑
            NSString *txtPath = [NSString stringWithFormat:@"%@/Logs/%@/",DOCUMENTS_PATH,oyToStr(USERID)];
            
            //獲取壓縮用戶日志到本地的路徑
            NSString *zipPath = [NSString stringWithFormat:@"%@/Logs",DOCUMENTS_PATH];
            NSString *zipName = [NSString stringWithFormat:@"%@%lld.zip",oyToStr(USERID),[self getCurrentDate]];
            zipPath = [zipPath stringByAppendingPathComponent:zipName];
            
            //壓縮用戶日志
            BOOL success = [SSZipArchive createZipFileAtPath:zipPath withContentsOfDirectory:txtPath withPassword:userLogModel.compressPwd];
            
            if (success) {
                
                // 在這里上傳
                
            }
            break;
        }
        default:
            break;
    }
    
}
4蛮放、XZDynamicLogLevel
  • 該類就是動(dòng)態(tài)調(diào)整log等級(jí)缩抡,先遵守DDRegisteredDynamicLogging協(xié)議
  • 重寫+ (void)ddSetLogLevel:(DDLogLevel)level方法。收到動(dòng)態(tài)修改Log等級(jí)的消息后包颁,就取出消息中給的Log等級(jí)瞻想,然后存數(shù)據(jù)庫
+ (void)ddSetLogLevel:(DDLogLevel)level {
    
    [[XZUserKVStoreManage shareInstance] saveNumberWithNumber:@(level)
                                                    TableName:XZ_USER_LOG_LEVEL_DB_NAME
                                                          key:XZ_USER_LOG_LEVEL_DB_KEY];
}
  • 重寫+ (DDLogLevel)ddLogLevel方法,每次打印log會(huì)獲取log等級(jí)娩嚼,也就是會(huì)走+ (DDLogLevel)ddLogLevel方法蘑险,在ddLogLevel方法中,我們每次都會(huì)去獲取我們保存的Log等級(jí)
/**
 *  每次打印log會(huì)獲取log等級(jí)
 *
 */
+ (DDLogLevel)ddLogLevel {
    
    NSInteger logLevel = [[XZUserLogManager shareInstance] getLogLevel];
    
    if (logLevel == 0) {
        
        #if DEBUG
                return DDLogLevelVerbose;
        #else
                return DDLogLevelWarning;
        #endif
        
    } else {
        
        if (logLevel == 1) {
            
            return DDLogLevelError;
        }
        else if (logLevel == 2) {
            
            return DDLogLevelWarning;
        }
        else if (logLevel == 3) {
            
            return DDLogLevelInfo;
        }
        else if (logLevel == 4) {
            
            return DDLogLevelDebug;
        }
        else {
            
            return DDLogLevelVerbose;
        }
    }
     
}
5岳悟、AppDelegate
  • AppDelegate中也需要做一些設(shè)置
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    // 啟動(dòng)日志
    //刪除過期日志
    [[XZUserLogManager shareInstance] deleteOutOfDateLog];
    // 添加DDASLLogger佃迄,你的日志語句將被發(fā)送到Xcode控制臺(tái)
    [DDLog addLogger:[DDTTYLogger sharedInstance]];
    
    // 添加DDTTYLogger泼差,你的日志語句將被發(fā)送到Console.app
    [DDLog addLogger:[DDASLLogger sharedInstance]];
    
    XZLogger *logger = [[XZLogger alloc] init];
    [logger setLogFormatter:[[XZLogFormatter alloc] init]];
    [DDLog addLogger:logger];
    
    DDLogInfo(@"%@",DOCUMENTS_PATH);
    DDLogInfo(@"打印info");
    DDLogWarn(@"打印Warn");
    DDLogError(@"打印Error");
    
    return YES;
}

這就是保存到沙盒的Log文件


582481A5-EA34-4A1A-85E0-63F5FB09E3C7.png

好了,收工呵俏。Demo在這里堆缘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市柴信,隨后出現(xiàn)的幾起案子套啤,更是在濱河造成了極大的恐慌,老刑警劉巖随常,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件潜沦,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡绪氛,警方通過查閱死者的電腦和手機(jī)唆鸡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枣察,“玉大人争占,你說我怎么就攤上這事⌒蚰浚” “怎么了臂痕?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)猿涨。 經(jīng)常有香客問我握童,道長(zhǎng),這世上最難降的妖魔是什么叛赚? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任澡绩,我火速辦了婚禮,結(jié)果婚禮上俺附,老公的妹妹穿的比我還像新娘肥卡。我一直安慰自己,他們只是感情好事镣,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布步鉴。 她就那樣靜靜地躺著,像睡著了一般蛮浑。 火紅的嫁衣襯著肌膚如雪唠叛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天沮稚,我揣著相機(jī)與錄音艺沼,去河邊找鬼。 笑死蕴掏,一個(gè)胖子當(dāng)著我的面吹牛障般,可吹牛的內(nèi)容都是我干的调鲸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼挽荡,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼藐石!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起定拟,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤于微,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后青自,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體株依,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年延窜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了恋腕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逆瑞,死狀恐怖荠藤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情获高,我是刑警寧澤哈肖,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站念秧,受9級(jí)特大地震影響牡彻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜出爹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缎除。 院中可真熱鬧严就,春花似錦、人聲如沸器罐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽轰坊。三九已至铸董,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肴沫,已是汗流浹背粟害。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留颤芬,地道東北人悲幅。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓套鹅,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親汰具。 傳聞我的和親對(duì)象是個(gè)殘疾皇子卓鹿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,735評(píng)論 25 707
  • 工廠模式類似于現(xiàn)實(shí)生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情留荔,實(shí)現(xiàn)同樣的效果;這時(shí)候需要使用工廠模式吟孙。簡(jiǎn)單...
    舟漁行舟閱讀 7,724評(píng)論 2 17
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件聚蝶、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,058評(píng)論 4 62
  • 譯文參考自《隱藏的論語》杰妓。 解讀是個(gè)人暫時(shí)的淺見。 接下來的幾章主要在討論小人是什么樣子的既荚。 17.12 【原文】...
    吾宗老孫子閱讀 395評(píng)論 0 0
  • 人之初稚失,性本善。性相近恰聘,習(xí)相遠(yuǎn)句各。茍不教,性乃遷晴叨。教之道凿宾,貴以專。 自譯:人最初的本性是善良的兼蕊。性格大都相近初厚,因?yàn)榱?xí)...
    崴崴閱讀 7,686評(píng)論 0 0