DDLog源碼解析三:FileLogger

導(dǎo)語(yǔ):

DDLog光酣,即CocoaLumberjack是iOS開(kāi)發(fā)用的最多的日志框架苔货,出自大神Robbie Hanson之手(還有諸多知名開(kāi)源框架如 XMPPFramework狂男、 CocoaAsyncSocket埠忘,都是即時(shí)通信領(lǐng)域很基礎(chǔ)應(yīng)用很多的框架)脾拆。了解DDLog的源碼將有助于我們更好的輸出代碼中的日志信息,便于定位問(wèn)題莹妒,也能對(duì)我們?cè)跁?shū)寫(xiě)自己的日志框架或者其他模塊時(shí)有所啟發(fā)名船。

此系列文章將分為以下幾篇:
- DDLog源碼解析一:框架結(jié)構(gòu)
- DDLog源碼解析二:設(shè)計(jì)初衷
- DDLog源碼解析三:FileLogger

本文將對(duì)DDLog支持的眾多Logger中值得分析的文件logger(其余l(xiāng)ogger基本只涉及系統(tǒng)api的調(diào)用)進(jìn)行分析,并簡(jiǎn)要分析一些雜亂的知識(shí)點(diǎn)旨怠。

FileLogger初始化

FileLogger初始化包含兩種初始化操作:默認(rèn)配置和自定義配置

- (instancetype)init {
    DDLogFileManagerDefault *defaultLogFileManager = [[DDLogFileManagerDefault alloc] init];

    return [self initWithLogFileManager:defaultLogFileManager];
}

- (instancetype)initWithLogFileManager:(id <DDLogFileManager>)aLogFileManager {
    if ((self = [super init])) {
        _maximumFileSize = kDDDefaultLogMaxFileSize;
        _rollingFrequency = kDDDefaultLogRollingFrequency;
        _automaticallyAppendNewlineForCustomFormatters = YES;

        logFileManager = aLogFileManager;

        self.logFormatter = [DDLogFileFormatterDefault new];
    }

    return self;
}

FileLogger默認(rèn)配置

FileLogger默認(rèn)配置由DDLogFileManagerDefault來(lái)實(shí)現(xiàn)渠驼,DDLogFileManagerDefault類(lèi)中除可以定義日志文件保存路徑外,其余信息都屬于寫(xiě)死的固定值(包括下面的靜態(tài)常量):

// 日志文件數(shù)的最大值
NSUInteger         const kDDDefaultLogMaxNumLogFiles   = 5;                // 5 Files
// 日志文件占用空間最大值
unsigned long long const kDDDefaultLogFilesDiskQuota   = 20 * 1024 * 1024; // 20 MB

// 日志默認(rèn)路徑為沙盒中caches文件中的Logs文件夾
- (NSString *)defaultLogsDirectory {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *baseDir = paths.firstObject;
    NSString *logsDirectory = [baseDir stringByAppendingPathComponent:@"Logs"];
    return logsDirectory;
}

同時(shí)鉴腻,DDLogFileManagerDefault的實(shí)例初始化時(shí)還對(duì)兩個(gè)變量通過(guò)KVO形式進(jìn)行監(jiān)聽(tīng)變化:

[self addObserver:self forKeyPath:NSStringFromSelector(@selector(maximumNumberOfLogFiles)) options:kvoOptions context:nil];
[self addObserver:self forKeyPath:NSStringFromSelector(@selector(logFilesDiskQuota)) options:kvoOptions context:nil];

如果將一個(gè)對(duì)象設(shè)定成屬性,這個(gè)屬性是自動(dòng)支持KVO的,如果這個(gè)對(duì)象是一個(gè)實(shí)例變量,那么,這個(gè)KVO是需要我們自己來(lái)實(shí)現(xiàn)的. 所以這里對(duì)maximumNumberOfLogFiles和logFilesDiskQuota重寫(xiě)了
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey
來(lái)支持KVO:

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey
{
    BOOL automatic = NO;
    if ([theKey isEqualToString:@"maximumNumberOfLogFiles"] || [theKey isEqualToString:@"logFilesDiskQuota"]) {
        automatic = NO;
    } else {
        automatic = [super automaticallyNotifiesObserversForKey:theKey];
    }
    
    return automatic;
}

當(dāng)KVO監(jiān)聽(tīng)到兩個(gè)實(shí)例變量的變化時(shí)迷扇,需要通過(guò)- (void)deleteOldLogFiles方法來(lái)判斷是否需要?jiǎng)h除文件以滿(mǎn)足最新的文件數(shù)量和大小的要求百揭,但刪除的時(shí)候需要注意,如果只剩一個(gè)文件待刪除蜓席,判斷到該文件未歸檔信峻,則不能刪除,因?yàn)榇宋募赡苷趯?xiě)入信息瓮床,還沒(méi)有關(guān)閉文件盹舞。 代碼片段如下:

    if (firstIndexToDelete == 0) {
        // Do we consider the first file?
        // We are only supposed to be deleting archived files.
        // In most cases, the first file is likely the log file that is currently being written to.
        // So in most cases, we do not want to consider this file for deletion.

        if (sortedLogFileInfos.count > 0) {
            DDLogFileInfo *logFileInfo = sortedLogFileInfos[0];

            if (!logFileInfo.isArchived) {
                // Don't delete active file.
                ++firstIndexToDelete;
            }
        }
    }

文件命名

默認(rèn)的文件名命名方式:app名稱(chēng)為前綴,加上經(jīng)過(guò)一定格式format過(guò)的格式隘庄。

- (NSDateFormatter *)logFileDateFormatter {
    NSMutableDictionary *dictionary = [[NSThread currentThread]
                                       threadDictionary];
    NSString *dateFormat = @"yyyy'-'MM'-'dd'--'HH'-'mm'-'ss'-'SSS'";
    NSString *key = [NSString stringWithFormat:@"logFileDateFormatter.%@", dateFormat];
    NSDateFormatter *dateFormatter = dictionary[key];

    if (dateFormatter == nil) {
        dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
        [dateFormatter setDateFormat:dateFormat];
        [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
        dictionary[key] = dateFormatter;
    }

    return dateFormatter;
}
- (NSString *)newLogFileName {
    NSString *appName = [self applicationName];

    NSDateFormatter *dateFormatter = [self logFileDateFormatter];
    NSString *formattedDate = [dateFormatter stringFromDate:[NSDate date]];

    return [NSString stringWithFormat:@"%@ %@.log", appName, formattedDate];
}

- (NSString *)applicationName {
    static NSString *_appName;
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
        _appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"];

        if (!_appName) {
            _appName = [[NSProcessInfo processInfo] processName];
        }

        if (!_appName) {
            _appName = @"";
        }
    });

    return _appName;
}

這里需要注意applicationName的獲取方式中使用了dispatch_once踢步,是為了保證線程安全和低負(fù)載:NSProcessInfo是線程不安全的,而這個(gè)方法可能在多個(gè)線程中同時(shí)訪問(wèn)到NSProcessInfo丑掺。而低負(fù)載是指這部分信息其實(shí)是app的通用信息获印,不會(huì)改變,所以復(fù)制到靜態(tài)變量中街州,不管哪個(gè)實(shí)例變量來(lái)獲取兼丰,都可以通過(guò)第一次獲取的值直接給它。

FileLogger重要邏輯

FileLogger還要在初始化時(shí)配置單個(gè)文件大小的最大值和輪詢(xún)檢查文件時(shí)間(這兩個(gè)值已寫(xiě)死)

unsigned long long const kDDDefaultLogMaxFileSize      = 1024 * 1024;      // 1 MB
NSTimeInterval     const kDDDefaultLogRollingFrequency = 60 * 60 * 24;     // 24 Hours

_maximumFileSize = kDDDefaultLogMaxFileSize;
_rollingFrequency = kDDDefaultLogRollingFrequency;

由于這兩個(gè)值直接跟寫(xiě)日志相關(guān)唆缴,所以這兩個(gè)值的getter和setter方法都使用了上一節(jié)解析的線程保護(hù)方式:先在全局日志隊(duì)列排隊(duì)鳍征,再到自己的日志隊(duì)列中排隊(duì)進(jìn)行操作,以一個(gè)為例:

- (NSTimeInterval)rollingFrequency {
    __block NSTimeInterval result;

    dispatch_block_t block = ^{
        result = _rollingFrequency;
    };

    NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
    NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");

    dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];

    dispatch_sync(globalLoggingQueue, ^{
        dispatch_sync(self.loggerQueue, block);
    });

    return result;
}

當(dāng)輪詢(xún)檢查需要檢查文件兩方面信息:大小和已經(jīng)打開(kāi)的時(shí)間面徽。文件大小通過(guò)NSFileHandle的方法可以判斷艳丛,而已經(jīng)打開(kāi)的時(shí)間則需要通過(guò)計(jì)時(shí)器來(lái)計(jì)時(shí),最終在輪詢(xún)時(shí)刻與設(shè)定的值比較趟紊。到當(dāng)前文件已經(jīng)符合設(shè)置的值時(shí)氮双,需要關(guān)閉文件,并將文件歸檔霎匈,再將文件計(jì)時(shí)器關(guān)閉戴差。

文件權(quán)限

在寫(xiě)日志文件前需要?jiǎng)?chuàng)建新文件,由于iOS系統(tǒng)默認(rèn)設(shè)置文件權(quán)限為NSFileProtectionCompleteUnlessOpen铛嘱, 但如果app可以在后臺(tái)運(yùn)行暖释,需要設(shè)置為NSFileProtectionCompleteUntilFirstUserAuthentication,才能保證即使鎖屏也能正常創(chuàng)建和讀寫(xiě)文件弄痹。

//文件未受保護(hù)饭入,隨時(shí)可以訪問(wèn) (Default)  
        NSFileProtectionNone

//文件受到保護(hù),而且只有在設(shè)備未被鎖定時(shí)才可訪問(wèn)                                
        NSFileProtectionComplete 
  
//文件收到保護(hù)肛真,直到設(shè)備啟動(dòng)且用戶(hù)第一次輸入密碼                        
        NSFileProtectionCompleteUntilFirstUserAuthentication 

//文件受到保護(hù),而且只有在設(shè)備未被鎖定時(shí)才可打開(kāi)爽航,不過(guò)即便在設(shè)備被鎖定時(shí)蚓让,已經(jīng)打開(kāi)的文件還是可以繼續(xù)使用和寫(xiě)入
    NSFileProtectionCompleteUnlessOpen                      

而其中app是否可以在app后臺(tái)運(yùn)行乾忱,是通過(guò)plist中對(duì)應(yīng)的配置項(xiàng)是否申請(qǐng)了后臺(tái)運(yùn)行能力來(lái)判斷的:

BOOL doesAppRunInBackground() {
    BOOL answer = NO;

    NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];

    for (NSString *mode in backgroundModes) {
        if (mode.length > 0) {
            answer = YES;
            break;
        }
    }

    return answer;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市历极,隨后出現(xiàn)的幾起案子窄瘟,更是在濱河造成了極大的恐慌,老刑警劉巖趟卸,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹄葱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡锄列,警方通過(guò)查閱死者的電腦和手機(jī)图云,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)邻邮,“玉大人竣况,你說(shuō)我怎么就攤上這事⊥惭希” “怎么了丹泉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鸭蛙。 經(jīng)常有香客問(wèn)我摹恨,道長(zhǎng),這世上最難降的妖魔是什么娶视? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任睬塌,我火速辦了婚禮,結(jié)果婚禮上歇万,老公的妹妹穿的比我還像新娘揩晴。我一直安慰自己,他們只是感情好贪磺,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布硫兰。 她就那樣靜靜地躺著,像睡著了一般寒锚。 火紅的嫁衣襯著肌膚如雪劫映。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,785評(píng)論 1 290
  • 那天刹前,我揣著相機(jī)與錄音泳赋,去河邊找鬼。 笑死喇喉,一個(gè)胖子當(dāng)著我的面吹牛祖今,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼千诬,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼耍目!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起徐绑,我...
    開(kāi)封第一講書(shū)人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤邪驮,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后傲茄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體毅访,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年盘榨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了喻粹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡较曼,死狀恐怖磷斧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捷犹,我是刑警寧澤弛饭,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站萍歉,受9級(jí)特大地震影響侣颂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜枪孩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一憔晒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蔑舞,春花似錦拒担、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至钧栖,卻和暖如春低零,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拯杠。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工掏婶, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人潭陪。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓雄妥,卻偏偏與公主長(zhǎng)得像最蕾,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子茎芭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348