【作者前言】:13年入圈,分享些本人工作中遇到的點點滴滴那些事兒,17年剛開始寫博客厦画,高手勿噴混坞!以分享交流為主,歡迎各路豪杰點評改進礼旅!
1.應(yīng)用場景:
很多時候,需要我們將項目的日志留存到文件中,或通過手機分享唾琼、Xcode調(diào)試、上傳服務(wù)器 等形式澎剥,對日志進行分析锡溯;
這里也是應(yīng)公司項目需要,寫了一個小工具類,沒那么長時間去寫-直接一個類處理了(不喜勿噴~~~??)祭饭,有需要的盆友可以參考芜茵、使用
如有幫助,歡迎點贊/收藏/留言??
2.實現(xiàn)目標(biāo):
通過自定義宏實現(xiàn)多形式的日志打印甜癞,debug環(huán)境下同步輸出控制臺夕晓,便于調(diào)試;可開啟是否寫入文件等
上一版在實際使用的過程中 - 發(fā)現(xiàn)了一些問題 - 后來進行了演進和修改
- 新增線程安全的兼容悠咱,考慮到多線程訪問日志類蒸辆,帶來的線程污染日志數(shù)據(jù)等問題,確蔽黾龋控制臺輸出與文件I/O完全一致
- 降低對文件I/O的操作頻率躬贡,提升穩(wěn)定性和性能
- 新增日志等級控制接口,取消以前的強制寫入眼坏,采用等級去控制拂玻,低于設(shè)置的日志等級不再加入文件寫入的隊列之中
- 新增動態(tài)配置日志存儲根目錄、配置存儲空間宰译、配置最大保留時長等接口檐蚜,新增可自定義設(shè)置日志標(biāo)識符,為檢索日志內(nèi)容提供便利
- 考慮到類的編譯加載的性能消耗等問題 - 去除多余類沿侈,采用單一類闯第,降低不必要的性能消耗
性能測試中,發(fā)現(xiàn)一些問題 - 繼續(xù)優(yōu)化缀拭,調(diào)整 -> 按需使用UI調(diào)試回顯咳短,避免過多的內(nèi)存占用,同時進一步優(yōu)化I/O機制蛛淋,通過任務(wù)降級+定時監(jiān)測+閾值觸發(fā)+同步寫入的接口咙好,進一步降低I/O頻率和內(nèi)存影響
懶得看?褐荷?勾效?demo直通車[1]
使用效果圖示:
Debug模式下,控制臺同步:
對應(yīng)的日志文件效果:
3.代碼說明:
主要通過自寫小工具類YPLogTool實現(xiàn)日志打印诚卸、同步文件寫入葵第、crash捕捉、日志上傳等
1)支持多用戶日志存取合溺,數(shù)據(jù)文件存儲結(jié)構(gòu)說明:
2)支持通過日志等級動態(tài)是否需要寫入文件流卒密、info/warn/error 多形式打印日志的 便捷宏
3)支持設(shè)置日志文件的最大存儲空間(Mb)
4)支持設(shè)置日志文件的保留天數(shù) (與3相比,具有優(yōu)先級)
5)支持日志脫敏的開關(guān)控制
7)支持獲取當(dāng)次打印的所有數(shù)據(jù)棠赛,可用于一些圖形化日志數(shù)據(jù)的回顯等
8)核心代碼實現(xiàn)(列舉下寫文件的吧哮奇,多了看了也煩??~~~具體看本文底部膛腐,我會附上demo地址)
YPLogTool.m
//MARK: - yp_printLogWithContentModel: For Xcode
+ (void)yp_printLogWithContentModel:(YPLogContentModel *)contentModel {
yp_printLogTimes ++;
if (contentModel.keyIdentifierStr.length) {
fprintf(contentModel.logLevel == YP_LOG_LEVEL_ERROR ? stderr : stdout,"%s [%s] [%s] %s [%s:%lu] [%s] [%s] :%s\n",contentModel.printLogLevelFlagUTF8, contentModel.timeStrUTF8, contentModel.fmtLogLevelStrUTF8, contentModel.keyIdentifierStrUTF8, contentModel.fileUTF8, (unsigned long)contentModel.line, contentModel.functionNameUTF8, contentModel.threadFlagUTF8, contentModel.formatUTF8);
}else {
fprintf(contentModel.logLevel == YP_LOG_LEVEL_ERROR ? stderr : stdout,"%s [%s] [%s] [%s:%lu] [%s] [%s] :%s\n",contentModel.printLogLevelFlagUTF8, contentModel.timeStrUTF8, contentModel.fmtLogLevelStrUTF8, contentModel.fileUTF8, (unsigned long)contentModel.line, contentModel.functionNameUTF8, contentModel.threadFlagUTF8, contentModel.formatUTF8);
}
if (!(yp_printLogTimes % 5000)) { //
if (contentModel.logLevel == YP_LOG_LEVEL_ERROR) {
fflush(stderr);
}else {
fflush(stdout);
}
}
}
//MARK: - yp_writeLogWithContentModel: For File Save
+ (void)yp_writeLogWithContentModel:(YPLogContentModel *)contentModel multiLinesLog:(NSString *)linesLog{
// 校驗下輪轉(zhuǎn)清理
NSString *curDayStr = [[contentModel.timeStr componentsSeparatedByString:@" "][0] stringByReplacingOccurrencesOfString:@"-" withString:@""];
if (yp_useBeginTimeDayStr && (![curDayStr isEqualToString:yp_useBeginTimeDayStr])) {// 當(dāng)啟用日志組件時的日期 與 當(dāng)前打印日志時的日期 不一致時 && 當(dāng)前打印日志時的日期有值 , 即跨夜了...
YPLogWarn(@"少年好厲害,決戰(zhàn)到天亮鼎俘!跨夜時間:【%@->%@】", yp_useBeginTimeDayStr, curDayStr);
yp_useBeginTimeDayStr = curDayStr;
if (yp_tempNoClearUserDirectoryNames) {
[yp_tempNoClearUserDirectoryNames removeAllObjects];
}
}
NSError *error = nil;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *filePath = [yp_curUserDirectoryPath stringByAppendingPathComponent:[NSString stringWithFormat:@"YPLog_%@_%@.log", yp_curUserIdentifier, curDayStr]];
if(![fileManager fileExistsAtPath:filePath]) {// 如果日志文件不存在 - 創(chuàng)建并寫入日志文件
[linesLog writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (error) {
YPLogError(@"日志信息寫入文件失敗,errorInfo: %@ 對應(yīng)日志內(nèi)容:%@", error.domain, contentModel.fullLogContent);
}
}else {// 日志文件存在 - 則繼續(xù)追加寫入
NSFileHandle *fileHandle;
@try {
fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
[fileHandle seekToEndOfFile];
NSData* stringData = [linesLog dataUsingEncoding:NSUTF8StringEncoding];
[fileHandle writeData:stringData];
[fileHandle synchronizeFile];
} @catch (NSException *exception) {
} @finally {
// 確保句柄被有效關(guān)閉
[fileHandle closeFile];
}
}
// 打印日志寫入 次數(shù) ++
yp_writeLogTimes ++;
[[NSUserDefaults standardUserDefaults] setObject:[NSString stringWithFormat:@"%lld", (long long)yp_writeLogTimes] forKey:@"yp_writeLogTimesKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
if (!(yp_writeLogTimes % 10000)) {// 每10000次打印哲身,校驗一回文件大小相關(guān)問題,降低頻率贸伐,增加性能 => 實測 10000次大概是1.*Mb左右
FILE_SIZE_CHECK_LOOP: {
float curFileSize = [YPLoggerTool yp_getTotalLogsSizeMb];
if (curFileSize >= yp_maxFoldSize) {
FILE_EARLIEST_LOOP: {
NSString *earliestFilePath = [YPLoggerTool yp_getEarliestLogFilePath];
if ([earliestFilePath isEqualToString:@"NoFilePath"]) {
return;
}
NSMutableArray *temp = [NSMutableArray arrayWithArray:[earliestFilePath componentsSeparatedByString:@"/"]];
[temp removeLastObject];
NSString *earliestUserPath = [temp componentsJoinedByString:@"/"];
NSInteger userLogsCount = [YPLoggerTool yp_getUserPathLogsCount:earliestUserPath];
if (userLogsCount > yp_maxSaveDays) {
if ([fileManager fileExistsAtPath:earliestFilePath]) {
[fileManager removeItemAtPath:earliestFilePath error:nil];
goto FILE_SIZE_CHECK_LOOP;
}
}else {
NSString *userDirectoryName = temp.lastObject;
[yp_tempNoClearUserDirectoryNames addObject:userDirectoryName];
goto FILE_EARLIEST_LOOP;
}
}
}
}
}
}