GodEye之日志監(jiān)控

日志幾乎是我們每一個(gè)iOS開(kāi)發(fā)者每一天都要打交道的東西饿这,比如運(yùn)行時(shí)想看一下某個(gè)變量的值,那就NSLog()一下;當(dāng)然手趣,在Swift語(yǔ)言下我們還有另外一種選擇,那就是print()方法倔韭。都是幫助我們方便調(diào)試與分析的工具。

開(kāi)源社區(qū)也為我們貢獻(xiàn)了很多非常優(yōu)秀的日志框架虹菲,比如OC中大名鼎鼎的CocoaLumberjack,有興趣的同學(xué)可以移步https://github.com/CocoaLumberjack/CocoaLumberjack

開(kāi)源日志框架是為了我們方便極了日志而開(kāi)發(fā)的掉瞳,比如我們可以調(diào)用CocoaLumberjack的DDLogInfo("Info")來(lái)記錄一個(gè)Info類型的日志毕源,用DDLogError("Error")來(lái)記錄一個(gè)Error類型的日志。不同的日志記錄框架我們需要調(diào)用的API也不一樣陕习,但是他們都有一個(gè)共同的特點(diǎn)霎褐,就是到最后都會(huì)調(diào)用系統(tǒng)的NSLog()方法。

因此我們需要做日志全自動(dòng)監(jiān)控的話该镣,可以從NSLog()方法入手冻璃。這里我們會(huì)分兩方面來(lái)講,因?yàn)樵趇OS上我們不單單可以用NSLog()來(lái)記錄日志,在Swift語(yǔ)言下還可以用print()方法來(lái)記錄我們的日志省艳。這一章我們會(huì)先介紹如何選擇和優(yōu)化我們監(jiān)控NSLog()日志的方案娘纷,然后比較NSLog()print()的不同點(diǎn)。然后再介紹Swift下特有的print()方法的處理跋炕。

0 前言

該文章摘自筆者撰寫(xiě)的一本iOS技術(shù)書(shū)籍iOS監(jiān)控編程赖晶。

這是一本介紹iOS監(jiān)控編程的書(shū)籍,內(nèi)容涉及日志監(jiān)控,監(jiān)控崩潰枣购,監(jiān)控網(wǎng)絡(luò)嬉探,監(jiān)控卡頓,監(jiān)控硬件棉圈,內(nèi)存泄漏監(jiān)控等方面涩堤。所有的功能都是通過(guò)自行編程實(shí)現(xiàn),而不是通過(guò)使用第三方工具分瘾。每個(gè)章節(jié)記錄了功能的實(shí)現(xiàn)細(xì)節(jié)胎围,以及筆者一路探索過(guò)來(lái)的心路歷程。當(dāng)然德召,筆者后續(xù)依舊會(huì)尋求與探索新的監(jiān)控方向白魂,一旦有所得都會(huì)更新到本書(shū)的章節(jié)中。

這本書(shū)是筆者的開(kāi)源工具GodEye的產(chǎn)出物上岗,GodEye能全自動(dòng)福荸,零代碼入侵,一行代碼接入來(lái)監(jiān)控應(yīng)用的日志肴掷,卡頓敬锐,崩潰,網(wǎng)絡(luò)呆瞻,內(nèi)存泄漏台夺,CPU以及內(nèi)存使用率,幀率等信息痴脾。

Paste_Image.png

1 監(jiān)控NSLog日志

該章節(jié)涉及到的開(kāi)源庫(kù)ASLEye,大家可以先通過(guò)https://github.com/zixun/ASLEye下載下來(lái)颤介,一遍對(duì)照開(kāi)源代碼一遍閱讀該章節(jié)。

可能你在翻開(kāi)該章節(jié)前已經(jīng)有了自己的方案了赞赖,因?yàn)檫@道題并沒(méi)有唯一解滚朵,我們只是要找出最優(yōu)解。下面讓我們來(lái)逐一分析監(jiān)控NSLog日志的方案前域。

自定義API

可能你和我一樣始绍,第一個(gè)想到的就是模仿CocoaLumberjack,咱整一套自己的API话侄,讓大家都來(lái)調(diào)咱的API不完事了么,多省事。比如LogEyeInfo("Info")就是Info類型的日志年堆,LogEyeError("Error")就是Error類型的日志吞杭。

哈哈,咱是省事了变丧,但是接入咱日志監(jiān)控的開(kāi)發(fā)者不干了芽狗,或許他正在和CocoaLumberjack愉快的玩耍,你儂我儂痒蓬,你突然讓我全換了童擎,你他媽的在逗我?

Paste_Image.png

的確攻晒,這樣接入方的開(kāi)發(fā)者的工作量就太大了顾复,而且也和全自動(dòng)記錄日志沾不上邊,不是我們的最佳方案鲁捏,換芯砸!


NSLog宏

相信大家這個(gè)時(shí)候也都想到了NSLog宏的方案。咱可以定義一個(gè)NSLog的宏给梅,去覆蓋掉系統(tǒng)的NSLog的方法假丧。在咱的NSLog的宏里面去調(diào)用我們第一種方案中的自定義API就好。比如

#define NSLog(frmt, ...) AppInfo(frmt, ##__VA_ARGS__)

用這招偷梁換柱的方法动羽,我們就可以在我們的AppInfo里面撒歡的處理我們的日志啦包帚,用戶一調(diào)我們就知道,用戶一調(diào)我們就知道运吓,好開(kāi)心啊渴邦。趕緊找我們的接入方開(kāi)發(fā)者來(lái)接入。

“我不接!”

“為啥子喲羽德?”

“因?yàn)槲椰F(xiàn)在用的開(kāi)源日志記錄框架也有了一個(gè)NSLog宏几莽,把你的接入了日志記錄框架的NSLog宏會(huì)被你覆蓋掉,也有可能你的宏會(huì)被他的宏覆蓋掉宅静,誰(shuí)先加載誰(shuí)被覆蓋章蚣!”

“......(握草泥馬)”

Paste_Image.png

看來(lái),NSLog宏的方案雖然好姨夹,但是一旦出現(xiàn)另一個(gè)NSLog宏就會(huì)出現(xiàn)“一山不容二宏”纤垂,“宏和宏不可兼得”的現(xiàn)象。我們不能保證接入方的開(kāi)發(fā)者們不會(huì)接入其他編寫(xiě)了NSLog宏的第三方庫(kù)磷账,也不能保證接入方的開(kāi)發(fā)者自己不會(huì)寫(xiě)一個(gè)NSLog宏峭沦。由此可見(jiàn),NSLog宏的方案雖好逃糟,但是存在易沖突吼鱼,不可靠蓬豁,不可控等問(wèn)題。換菇肃!

尋找新的方案

現(xiàn)在地粪,兩個(gè)方案都被我們排除了,還有啥比這兩個(gè)更好的方案可以用呢琐谤。如果腦海里暫時(shí)沒(méi)有好的方案與想法蟆技,我們就應(yīng)該祭出我們程序員的基本生存技能,問(wèn)度娘斗忌。质礼。。织阳。眶蕉。。不是陈哑,問(wèn)Google妻坝!

首先,我們?nèi)ニ阉饕幌翧pple關(guān)于NSLog的文檔惊窖。我們會(huì)發(fā)現(xiàn)NSLog方法中調(diào)用的是NSLogv方法刽宪。Apple文檔中對(duì)于NSLogv函數(shù)是這樣寫(xiě)的Logs an error message to the Apple System Log facility(參見(jiàn)https://developer.apple.com/reference/foundation/1395074-nslogv?language=objc)。

這句話有兩個(gè)信息我們需要重點(diǎn)關(guān)注下界酒,一個(gè)就是error message,還有一個(gè)就是Apple System Log,為啥NSLog是記錄錯(cuò)誤信息的圣拄,而不是日志信息呢?Apple System Log又是個(gè)什么鬼毁欣?

不急庇谆,我們?cè)偎阉飨?code>CocoaLumberjack的文檔,我們會(huì)發(fā)現(xiàn)CocoaLumberjack的文檔說(shuō)了這么兩句話:

NSLog does 2 things:
    It writes log messages to the Apple System Logging (asl) facility. This allows log messages to show up in Console.app.

    It also checks to see if the application's stderr stream is going to a terminal (such as when the application is being run via Xcode). If so it writes the log message to stderr (so that it shows up in the Xcode console).

參見(jiàn)https://github.com/CocoaLumberjack/CocoaLumberjack/blob/b1a3837366bc286ee24671ef1042b7214a8aa0ca/Documentation/Performance.md

CocoaLumberjack的文檔告訴我們凭疮,當(dāng)我們寫(xiě)了一句NSLog的時(shí)候饭耳,他會(huì)做兩件事情:1.把日志寫(xiě)到Apple System Log里面。2.把日志展示到Xcode的控制臺(tái)上面执解。

兩個(gè)文檔都提到了Apple System Log寞肖,那這個(gè)Apple System Log到底是個(gè)什么鬼。其實(shí)Apple System Log可以理解為就是一個(gè)我們?cè)O(shè)備的日志數(shù)據(jù)庫(kù)衰腌,這里存放了所有應(yīng)用所有進(jìn)程產(chǎn)生的日志新蟆。也就是說(shuō),不管在哪右蕊,只要你調(diào)用了NSLog,系統(tǒng)就會(huì)把它寫(xiě)到Apple System Log中琼稻。

那為啥NSLog是記錄錯(cuò)誤信息的,而不是日志信息呢饶囚?其實(shí)Apple設(shè)計(jì)NSLog的時(shí)候就是一個(gè)記錄錯(cuò)誤日志的API帕翻,不然他也不會(huì)把日志寫(xiě)到一個(gè)叫Apple System Log的數(shù)據(jù)庫(kù)里面鸠补,要知道記錄日志是一個(gè)高頻發(fā)的操作,如果每條都放到數(shù)據(jù)庫(kù)中熊咽,是一件很浪費(fèi)性能的事情莫鸭。所以我覺(jué)得應(yīng)該叫NSLogError才更合適。

NSLog耗性能的一個(gè)原因也是因?yàn)樾枰讶罩緮?shù)據(jù)寫(xiě)到Apple System Log數(shù)據(jù)庫(kù)横殴,所以大伙的App線上Release版本盡量不要寫(xiě)NSLog,不但耗性能卿拴,而且不安全衫仑。Swift的print方法就不會(huì)把日志寫(xiě)到數(shù)據(jù)庫(kù)中。當(dāng)然在Swift中并沒(méi)有廢棄掉NSLog方法堕花,但是Swift對(duì)NSLog方法做了優(yōu)化文狱,只有在模擬器環(huán)境下才會(huì)將日志寫(xiě)入Apple System Log

Apple System Log

OK缘挽,我們找到了我們的方案:Apple System Log瞄崇。Apple為Apple System Log提供了一個(gè)framework供大家使用來(lái)對(duì)Apple System Log的數(shù)據(jù)進(jìn)行操作。他就是asl,那下面我們就來(lái)試試怎么通過(guò)代碼把Apple System Log的數(shù)據(jù)撈出來(lái)壕曼。

首先苏研,我們需要把我們的asl模塊import進(jìn)來(lái):

import asl

然后我們創(chuàng)建一個(gè)專門用來(lái)?yè)艫SL的日志的類ASLEye,并且做個(gè)Timer定時(shí)器,定時(shí)拉取ASL的數(shù)據(jù):

public class ASLEye: NSObject {

    public func open(with interval:TimeInterval) {
        self.timer = Timer.scheduledTimer(timeInterval: interval,target: self,selector: #selector(ASLEye.pollingLogs),userInfo: nil,repeats: true)
    }
    
    public func close() {
        self.timer?.invalidate()
        self.timer = nil
    }
 
    private var timer: Timer?
}

好了腮郊,現(xiàn)在就讓我們來(lái)實(shí)現(xiàn)pollingLogs方法摹蘑,來(lái)拉取我們的數(shù)據(jù)。

@objc private func pollingLogs() {
    //TODO  調(diào)用拉取數(shù)據(jù)API轧飞,獲得數(shù)據(jù)數(shù)組
    //TODO  判斷數(shù)據(jù)數(shù)組是否為空
    //TODO  將數(shù)據(jù)數(shù)組回調(diào)給上層
    }

咱先不著急pollingLogs方法的具體實(shí)現(xiàn)衅鹿。大家可以和我一樣先注釋好方法要做的事情。

這里我們要先介紹下asl的api的特點(diǎn)过咬。asl的api類似SQL語(yǔ)句大渤,你可以往查詢語(yǔ)句里面加參數(shù)來(lái)過(guò)濾數(shù)據(jù),比如要查詢當(dāng)前App的日志的話掸绞,需要設(shè)置App的BundleID泵三,當(dāng)然,我們每次啟動(dòng)我們的App的時(shí)候都是不同的進(jìn)程集漾,所以我們還需要設(shè)置當(dāng)前App的進(jìn)程ID切黔。因此我們可以編寫(xiě)一個(gè)initQuery的方法來(lái)生成我們的查詢語(yǔ)句:

private func initQuery() -> aslmsg {
        var query: aslmsg = asl_new(UInt32(ASL_TYPE_QUERY))
        //set BundleIdentifier to ASL_KEY_FACILITY
        let bundleIdentifier = (Bundle.main.bundleIdentifier! as NSString).utf8String
        asl_set_query(query, ASL_KEY_FACILITY, bundleIdentifier, UInt32(ASL_QUERY_OP_EQUAL))
        
        //set pid to ASL_KEY_PID
        let pid = NSString(format: "%d", getpid()).cString(using: String.Encoding.utf8.rawValue)
        asl_set_query(query, ASL_KEY_PID, pid, UInt32(ASL_QUERY_OP_NUMERIC))
        
        return query
    }

然后我們來(lái)編寫(xiě)拉取數(shù)據(jù)API,獲得數(shù)據(jù)數(shù)組的API具篇,這個(gè)API要做的事情就是調(diào)用initQuery()方法生成我們的查詢語(yǔ)句纬霞,然后解析返回的response,將response解析成一個(gè)字符串的數(shù)組返回給調(diào)動(dòng)著驱显,咱就叫他retrieveLogs()方法:

private func retrieveLogs() -> [String] {
        var logs = [String]()
        
        var query: aslmsg = self.initQuery()
        
        let response: aslresponse? = asl_search(nil, query)
        guard response != nil else {
            return logs
        }
        
        var message = asl_next(response)
        while (message != nil) {
            let log = self.log(from: message!)
            logs.append(log)
            
            message = asl_next(response)
        }
        asl_free(response)
        asl_free(query)
        
        return logs
    }

private func parserLog(from message:aslmsg) ->String {
        let content = asl_get(message, ASL_KEY_MSG)!;
        return String(cString: content, encoding: String.Encoding.utf8)!
    }

好诗芜,現(xiàn)在我們讓pollingLogs()來(lái)調(diào)用我們的retrieveLogs()方法:

@objc private func pollingLogs() {
        let logs = self.retrieveLogs()
        if logs.count > 0 {
            self.delegate?.aslMonitor?(aslMonitor: self, didMonitorLogs: logs)
        }
    }

這樣我們的日志就能通過(guò)我們的delegate方法回調(diào)給上層了瞳抓。當(dāng)然在這之前我們需要編寫(xiě)一個(gè)我們這個(gè)delegate的protocol

@objc public protocol ASLEyeDelegate: class {
    @objc optional func aslEye(aslEye:ASLEye,catchLogs logs:[String])
}

然后在ASLEye上添加一個(gè)delegate的變量:

    public weak var delegate: ASLEyeDelegate?

但是,這個(gè)時(shí)候大家去調(diào)用NSLog會(huì)發(fā)現(xiàn)存在一個(gè)問(wèn)題伏恐。比如我調(diào)用了下NSLog("Hello"),我們的日志監(jiān)控每次輪詢都會(huì)取到我們的日志數(shù)據(jù)孩哑,我們的delegate會(huì)一直回調(diào),把Hello給上層翠桦。

會(huì)造成這樣其實(shí)也不難理解横蜒,因?yàn)锳pp System Log是一個(gè)數(shù)據(jù)庫(kù),按照咱現(xiàn)在的查詢語(yǔ)句销凑,每次插敘的時(shí)候當(dāng)然會(huì)把當(dāng)前App當(dāng)前的日志全部返回給我們丛晌,所以,我們還需要設(shè)置一個(gè)'游標(biāo)'斗幼,換句話說(shuō)就是我們需要在我們的查詢語(yǔ)句里加一個(gè)參數(shù)澎蛛,將我們上次查到的最后一個(gè)日志的id給塞進(jìn)去,然后查詢大于這個(gè)ID的日志蜕窿。

OK,首先谋逻,我們來(lái)設(shè)置一個(gè)變量lastMessageID,用來(lái)記錄最后一次查到的日志ID:

private var lastMessageID: Int32 = 0

然后在我們的initQuery()方法里最后返回我們的query前將我們的lastMessageID給塞進(jìn)去:

private func initQuery() -> aslmsg {
        var query: aslmsg = asl_new(UInt32(ASL_TYPE_QUERY))
        //set BundleIdentifier to ASL_KEY_FACILITY
        let bundleIdentifier = (Bundle.main.bundleIdentifier! as NSString).utf8String
        asl_set_query(query, ASL_KEY_FACILITY, bundleIdentifier, UInt32(ASL_QUERY_OP_EQUAL))
        
        //set pid to ASL_KEY_PID
        let pid = NSString(format: "%d", getpid()).cString(using: String.Encoding.utf8.rawValue)
        asl_set_query(query, ASL_KEY_PID, pid, UInt32(ASL_QUERY_OP_NUMERIC))
        
        if self.lastMessageID != 0 {
            let m = NSString(format: "%d", self.lastMessageID).utf8String
            asl_set_query(query, ASL_KEY_MSG_ID, m, UInt32(ASL_QUERY_OP_GREATER | ASL_QUERY_OP_NUMERIC));
        }
        
        return query
    }

最后桐经,我們需要在每次解析我們的日志的時(shí)候?qū)⑽覀兊娜罩綢D給記錄下來(lái)放到lastMessageID中:

private func parserLog(from message:aslmsg) ->String {
        let content = asl_get(message, ASL_KEY_MSG)!;
        let msg_id = asl_get(message, ASL_KEY_MSG_ID);
        
        let m = atoi(msg_id);
        if (m != 0) {
            self.lastMessageID = m;
        }
        
        return String(cString: content, encoding: String.Encoding.utf8)!
    }

OK,這樣毁兆,我們就能夠全自動(dòng)的監(jiān)控我們的日志啦。上層開(kāi)發(fā)者還是按照他們的方式玩就好次询,我們都能拿到他們的日志荧恍。
該章節(jié)涉及到的開(kāi)源庫(kù)ASLEye有興趣的同學(xué)可以移步https://github.com/zixun/ASLEye查看。

<br />
<br />

更好的方案

通過(guò)ASL來(lái)監(jiān)控NSLog日志雖然能很好的完成自動(dòng)監(jiān)控的功能屯吊,但是畢竟Apple System Log是一個(gè)數(shù)據(jù)庫(kù)送巡,每次做查詢操作由于會(huì)產(chǎn)生I/0操作,所以會(huì)產(chǎn)生一定的性能消耗盒卸。雖然這點(diǎn)性能消耗在日常應(yīng)用的運(yùn)行時(shí)完全可以忽略不計(jì)骗爆,但是當(dāng)我們?cè)诮y(tǒng)計(jì)應(yīng)用整個(gè)流程的CPU使用率的時(shí)候還是會(huì)造成一定的干擾,那有沒(méi)有更好的方案呢蔽介?

當(dāng)然有摘投。那就是將NSLog方法給hook掉。不過(guò)當(dāng)我們點(diǎn)開(kāi)NSLog方法的時(shí)候可以發(fā)現(xiàn)這個(gè)API并不是一個(gè)Objective-C語(yǔ)言的方法虹蓄,而是一個(gè)C語(yǔ)言包裝的API犀呼。C語(yǔ)言的API當(dāng)然不能用我們Runtime的Method Swizzle來(lái)替換,他并不會(huì)走OC的運(yùn)行時(shí)薇组。

不過(guò)方法總比問(wèn)題多外臂,F(xiàn)acebook有一個(gè)名字叫fishhook的開(kāi)源庫(kù),就可以做到將我們的Mach-O的庫(kù)的符號(hào)表重新綁定律胀,因此應(yīng)該也能將我們的NSLog重新綁定宋光,重新綁定后就可以做我們的日志監(jiān)控啦貌矿。

有興趣的同學(xué)可以自己實(shí)驗(yàn)下,筆者也會(huì)在今后GodEye的更新迭代中探索替換該方案來(lái)實(shí)現(xiàn)全自動(dòng)的日志監(jiān)控罪佳。當(dāng)然也會(huì)將實(shí)現(xiàn)原理編寫(xiě)成該書(shū)的一個(gè)章節(jié)提供給大家逛漫。

1 監(jiān)控print日志

前一章節(jié)我們分析了NSLog日志的監(jiān)控,不過(guò)接觸過(guò)Swift語(yǔ)言的同學(xué)都知道,Swift中大家一般都是用print來(lái)記錄日志赘艳。當(dāng)然酌毡,Swift并沒(méi)有將NSLog給廢棄掉。

NSLog In Swift

當(dāng)然蕾管,Swift中我們也可以用NSLog來(lái)做日志記錄阔馋,但是Swift對(duì)NSLog做過(guò)優(yōu)化。筆者發(fā)現(xiàn)Swift中調(diào)用NSLog方法娇掏,它并不會(huì)想Objective-C中調(diào)用NSLog方法那樣將日志放入Apple System Log中,模擬器環(huán)境下除外勋眯。也就是說(shuō)我們?cè)谡鎸?shí)環(huán)境下并不能通過(guò)ALS獲取NSLog的日志信息婴梧。因此我們需要尋找其他的辦法。

自定義print方法

我們來(lái)看看Swift中print方法的定義:

public func print(_ items: Any..., separator: String = default, terminator: String = default)

其中separator的默認(rèn)值是一個(gè)" "(空格字符串)客蹋。terminator的默認(rèn)值是"\n"(回車符)塞蹭。

我們自定義的print方法不能完全和Swift的一樣,不然編譯器會(huì)給你一個(gè)報(bào)錯(cuò)Ambiguous use of 'print(_:separator:terminator:)',因?yàn)閮蓚€(gè)API一模一樣,編譯器就不知道去鏈接哪個(gè)API了讶坯。

不過(guò)我們可以把默認(rèn)值去掉番电,只需要一個(gè)item參數(shù)就好:

<br />

public func print(_ items: Any...) {
    //TODO: Custom Actions
    Swift.print(items)
}

這樣,我們只需要在使用print方法的swift文件中辆琅,import我們這個(gè)自定義api的庫(kù)就好了漱办。然后將TODO: Custom Actions中放入我們的日志監(jiān)控操作即可。

自定義日志框架

自定義print方法固然不錯(cuò)婉烟,但是使用者很容易忘了import我們這個(gè)自定義api的庫(kù)娩井,Swift中也沒(méi)有預(yù)加載的功能,因此使用者很容易忘記做import操作似袁,導(dǎo)致監(jiān)控不到洞辣。而且單獨(dú)一個(gè)print方法無(wú)法優(yōu)雅的區(qū)分日志的類型,比如log,warning,error等昙衅。更不能獲取文件名扬霜,方法名等日志輔助信息。因此我們何嘗不自定義一個(gè)輕量級(jí)的日志框架呢而涉。

該日志框架大家可通過(guò)https://github.com/zixun/Log4G下載著瓶,文中由于篇幅的原因,只會(huì)列出關(guān)鍵代碼婴谱,建議將開(kāi)源源碼下載后對(duì)照著源碼查看蟹但,會(huì)更便于理解躯泰。

我們先來(lái)看一張Log4G的簡(jiǎn)要設(shè)計(jì)圖:

Paste_Image.png

從圖上我們可以看出,我們對(duì)外有三個(gè)接口华糖,分別是log(),warning()error()麦向。他們內(nèi)部都會(huì)調(diào)用一個(gè)不公開(kāi)的接口record(),當(dāng)然在方法中會(huì)自動(dòng)獲取日志的文件(file),行數(shù)(line)客叉,方法名(function)以及線程(thread)诵竭,然后傳遞給record()方法,record()方法內(nèi)部所有的操作都在一個(gè)指定的queue中進(jìn)行兼搏,可以防止多線程的問(wèn)題卵慰。record()會(huì)做兩件事,一個(gè)就是調(diào)用系統(tǒng)的print()函數(shù)佛呻,還有一個(gè)就是通知Log4g內(nèi)部維護(hù)的delegate數(shù)組裳朋,告訴日志監(jiān)聽(tīng)者們新的日志信息。

我們先來(lái)看看log()的實(shí)現(xiàn):

/// record a log type message
///
/// - Parameters:
///   - message: log message
///   - file: file which call the api
///   - line: line number at file which call the api
///   - function: function name which call the api
open class func log(_ message: Any = "",
                    file: String = #file,
                    line: Int = #line,
                    function: String = #function) {
    self.shared.record(type: .log,
                       thread: Thread.current,
                       message: "\(message)",
                       file: file,
                       line: line,
                       function: function)
}

我們通過(guò)#file,#line,#function可以自動(dòng)獲取調(diào)用API時(shí)候的文件名吓著,行數(shù)以及方法名鲤嫡,不需要調(diào)用者參與。然后通過(guò)Thread.current拿到當(dāng)前線程绑莺,一并傳遞給record方法暖眼。這樣調(diào)用者只需要傳遞日志即可,其他一切都是自動(dòng)纺裁。

warning()error()原理一致诫肠,那我們來(lái)看看record()方法:

/// record message base function
///
/// - Parameters:
///   - type: log type
///   - thread: thread which log the messsage
///   - message: log message
///   - file: file which call the api
///   - line: line number at file which call the api
///   - function: function name which call the api
private func record(type:Log4gType,
                 thread:Thread,
                 message: String,
                 file: String,
                 line: Int,
                 function: String) {
    self.queue.async {
        let model = LogModel(type: type,
                             thread: thread,
                             message: message,
                             file: self.name(of: file),
                             line: line,
                             function: function)
        print(message)
        
        for delegate in self.delegates {
            delegate.delegate?.log4gDidRecord(with: model)
        }
        
    }
}

record()方法內(nèi)部通過(guò)self.queue.async{...}將所有的操作都同步在一個(gè)統(tǒng)一的線程中,然后調(diào)用系統(tǒng)的print()方法并生成一個(gè)日志的model欺缘,告訴Log4g內(nèi)部維護(hù)的delegate數(shù)組監(jiān)聽(tīng)到的新的日志栋豫。這里涉及成delegate數(shù)組可以方便日志監(jiān)聽(tīng)的擴(kuò)展,Log4g顧名思義浪南,就是Log4GodEye,就是設(shè)計(jì)給GodEye使用的日志記錄框架笼才,GodEye是delegate的一員,但是可能用戶的App其他地方也需要監(jiān)控日志络凿,只要將其加入到delegate即可骡送。

GodEye開(kāi)源地址:https://github.com/zixun/GodEyeGodEye是一個(gè)可以自動(dòng)監(jiān)控日志絮记,崩潰摔踱,卡頓,網(wǎng)絡(luò)怨愤,內(nèi)存泄漏派敷,沙盒文件,CPU,RAM,F(xiàn)PS,流量等信息的工具篮愉,功能豐富腐芍,just like god open his eye。

delegate數(shù)組的坑

在Objective-C中试躏,我們將delegate放入一個(gè)數(shù)組中完全沒(méi)有問(wèn)題猪勇,因?yàn)?code>NSArray不會(huì)強(qiáng)引用放進(jìn)來(lái)的對(duì)象。但是Swift中的Array是是一個(gè)結(jié)構(gòu)體颠蕴,會(huì)強(qiáng)引用放入的元素泣刹。比如我們的delegate是一個(gè)ViewController,將我們的ViewController放入Array后犀被,只要Array不釋放椅您,我們的ViewController就永遠(yuǎn)不會(huì)釋放,但是ViewController可能已經(jīng)被pop出去了寡键,這就造成了內(nèi)存泄漏掀泳。那怎么辦呢?

只要做一層weak化的包裝就好西轩,就拿Log4g來(lái)說(shuō)开伏,內(nèi)部維護(hù)了一個(gè)delegate的數(shù)組:

fileprivate var delegates = [WeakLog4GDelegate]()

WeakLog4GDelegate是個(gè)什么鬼呢,這個(gè)屬性的申明是fileprivate的遭商,說(shuō)明用戶是不知道WeakLog4GDelegate的,我們來(lái)看看WeakLog4GDelegate

class WeakLog4GDelegate: NSObject {
    weak var delegate : Log4GDelegate?
    init (delegate: Log4GDelegate) {
        super.init()
        self.delegate = delegate
    }
}

可以看到捅伤,WeakLog4GDelegateLog4GDelegate的一個(gè)weak化包裝劫流,內(nèi)部有一個(gè)Log4GDelegate的weak屬性,也通過(guò)Log4GDelegate來(lái)初始化丛忆。

這樣當(dāng)我們將WeakLog4GDelegate放入數(shù)組后祠汇,外部的Log4GDelegate就不會(huì)因?yàn)閺?qiáng)制引用而無(wú)法釋放了。當(dāng)然WeakLog4GDelegate并不是一個(gè)open或者public的類熄诡,所以我們還需要包裝一些方法讓使用者添加或刪除外部使用的Log4GDelegate

open class func add(delegate:Log4GDelegate) {
    let log4g = self.shared
    // delete null week delegate
    log4g.delegates = log4g.delegates.filter {
        return $0.delegate != nil
    }
    // judge if contains the delegate from parameter
    let contains = log4g.delegates.contains {
        return $0.delegate?.hash == delegate.hash
    }
    // if not contains, append it with weak wrapped
    if contains == false {
        let week = WeakLog4GDelegate(delegate: delegate)
        self.shared.delegates.append(week)
    }
}
open class func remove(delegate:Log4GDelegate) {
    let log4g = self.shared
    log4g.delegates = log4g.delegates.filter {
        // filter null weak delegate
        return $0.delegate != nil
    }.filter {
        // filter the delegate from parameter
        return $0.delegate?.hash != delegate.hash
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末可很,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子凰浮,更是在濱河造成了極大的恐慌我抠,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件袜茧,死亡現(xiàn)場(chǎng)離奇詭異菜拓,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)笛厦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門纳鼎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事贱鄙∪懊常” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵逗宁,是天一觀的道長(zhǎng)映九。 經(jīng)常有香客問(wèn)我,道長(zhǎng)疙剑,這世上最難降的妖魔是什么氯迂? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮言缤,結(jié)果婚禮上嚼蚀,老公的妹妹穿的比我還像新娘。我一直安慰自己管挟,他們只是感情好轿曙,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著僻孝,像睡著了一般导帝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上穿铆,一...
    開(kāi)封第一講書(shū)人閱讀 49,071評(píng)論 1 285
  • 那天您单,我揣著相機(jī)與錄音,去河邊找鬼荞雏。 笑死虐秦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的凤优。 我是一名探鬼主播悦陋,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼筑辨!你這毒婦竟也來(lái)了俺驶?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤棍辕,失蹤者是張志新(化名)和其女友劉穎暮现,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體楚昭,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡送矩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哪替。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栋荸。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晌块,到底是詐尸還是另有隱情爱沟,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布匆背,位于F島的核電站呼伸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏钝尸。R本人自食惡果不足惜括享,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望珍促。 院中可真熱鬧铃辖,春花似錦、人聲如沸猪叙。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)穴翩。三九已至犬第,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芒帕,已是汗流浹背歉嗓。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留背蟆,地道東北人遥椿。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像淆储,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子家浇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)本砰、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,029評(píng)論 4 62
  • 老中醫(yī)救救我 每天痛的不要不要的每天要的不停不停的 這就是我的滑雪之夜 按摩這哪里是按摩簡(jiǎn)直就是心靈的碰觸就是拿大...
    哈哈同學(xué)閱讀 484評(píng)論 0 0
  • 1. 序列for循環(huán) Range-based for loops 這是不需要多說(shuō)钢悲,很多語(yǔ)言都有的特性点额,可以方便的進(jìn)...
    stevenjobs閱讀 996評(píng)論 0 2
  • 參加了一位閨蜜的婚禮,婚禮簡(jiǎn)樸而隆重莺琳,當(dāng)新郎和新娘向來(lái)賓鞠躬的時(shí)候还棱,我分明看到了閨蜜眼角盈盈的淚光。那一刻惭等,我的淚...
    柳二白閱讀 638評(píng)論 4 6
  • 《極限挑戰(zhàn)》第三季剛復(fù)播就結(jié)束了么? 這一期設(shè)計(jì)意外又動(dòng)人寡具,關(guān)于人生,關(guān)于家庭稚补,關(guān)于生活童叠,片尾響起了這首歌~現(xiàn)在就...
    winwinwings閱讀 613評(píng)論 0 0