亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

利用CocoaLumberjack搭建自己的Log系統(轉)

2019-11-14 19:28:08
字體:
來源:轉載
供稿:網友

一直需要一個 Log 系統,可以將程序運行過程中打的 log 發送到自己服務器,方便之后數據分析或者除錯。之前也嘗試過找一些第三方服務,但看來看去,國內貌似沒看到專門做這一塊的,而國外看了下有 Loggly,似乎滿足需求,但它要收費且日志保存時間太短。后來無意間看了下 Loggly 提供的 SDK 源代碼,發現了 CocoaLumberjack 這個好東西,而 Loggly 其也不過就是在 CocoaLumberjack 上自定義了 Logger 和 Formatter 而已。自己做的話,也很簡單。

先說下需求,我理想中的 Log 系統需要:

  • 可以設定 Log 等級

  • 可以積攢到一定量的 log 后,一次性發送給服務器,絕對不能打一個 Log 就發一次

  • 可以一定時間后,將未發送的 log 發送到服務器

  • 可以在 App 切入后臺時將未發送的 log 發送到服務器

其他一些需求,比如可以遠程設定發送 log 的等級閥值,還有閥值的有效期等,和本文無關就不寫了。

開始動手前,先了解下 CocoaLumberjack 是什么:

CocoaLumberjack 最早是由 Robbie Hanson 開發的日志庫,可以在 iOS 和 MacOSX 開發上使用。其簡單,快讀,強大又不失靈活。它自帶了幾種log方式,分別是:

  • DDASLLogger 將 log 發送給蘋果服務器,之后在 Console.app 中可以查看

  • DDTTYLogger 將 log 發送給 Xcode 的控制臺

  • DDFileLogger 講 log 寫入本地文件

CocoaLumberjack 打一個 log 的流程大概就是這樣的:

001.png

所有的 log 都會發給 DDLog 對象,其運行在自己的一個GCD隊列(GlobalLoggingQueue),之后,DDLog 會將 log 分發給其下注冊的一個或多個 Logger,這步在多核下是并發的,效率很高。每個 Logger 處理收到的 log 也是在它們自己的 GCD隊列下(loggingQueue)做的,它們詢問其下的 Formatter,獲取 Log 消息格式,然后最終根據 Logger 的邏輯,將 log 消息分發到不同的地方。

因為一個 DDLog 可以把 log 分發到所有其下注冊的 Logger 下,也就是說一個 log 可以同時打到控制臺,打到遠程服務器,打到本地文件,相當靈活。

CocoaLumberjack 支持 Log 等級:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef NS_OPTIONS(NSUInteger, DDLogFlag) {
    DDLogFlagError      = (1 << 0), // 0...00001
    DDLogFlagWarning    = (1 << 1), // 0...00010
    DDLogFlagInfo       = (1 << 2), // 0...00100
    DDLogFlagDebug      = (1 << 3), // 0...01000
    DDLogFlagVerbose    = (1 << 4)  // 0...10000
};
typedef NS_ENUM(NSUInteger, DDLogLevel) {
    DDLogLevelOff       = 0,
    DDLogLevelError     = (DDLogFlagError),                       // 0...00001
    DDLogLevelWarning   = (DDLogLevelError   | DDLogFlagWarning), // 0...00011
    DDLogLevelInfo      = (DDLogLevelWarning | DDLogFlagInfo),    // 0...00111
    DDLogLevelDebug     = (DDLogLevelInfo    | DDLogFlagDebug),   // 0...01111
    DDLogLevelVerbose   = (DDLogLevelDebug   | DDLogFlagVerbose), // 0...11111
    DDLogLevelAll       = NSUIntegerMax                           // 1111....11111 (DDLogLevelVerbose plus any other flags)
};

DDLogLevel 定義了全局的 log 等級,DDLogFlag 是我們打 log 時設定的 log 等級,CocoaLumberjack 會比較兩者,如果 flag 低于 level,則不會打 log:

1
2
#define LOG_MAYBE(async, lvl, flg, ctx, tag, fnct, frmt, ...) /
        do if(lvl & flg) LOG_MACRO(async, lvl, flg, ctx, tag, fnct, frmt, ##__VA_ARGS__); } while(0)

DDLogger 協議定義了 logger 對象需要遵從的方法和變量,為了方便使用,其提供了 DDAbstractLogger 對象,我們只需要繼承該對象就可以自定義自己的 logger。對于第二點和第三點需求,我們可以利用 DDAbstractDatabaseLogger,其也是繼承自 DDAbstractLogger,并在其上定義了 saveThreshold, saveInterval 等控制參數。這個 logger 本身是針對寫入數據庫的 log 設計的,我們也可以利用它這幾個參數,實現我們上面所提的需求的第二和第三點。

對于第二點,設定 _saveThreshold 值即可,比如如果希望積攢1000條 log 再一次性發送,就賦值 1000.

對于第三點,設定 _saveInterval,比如如果希望每分鐘發送一次,就設定 60.

由此,CocoaLumberjack 已經實現了需求中的 1、2、3 點,我們要做的無非是自定義 Logger 和 Formatter,將 log 的最終去處改為發送到我們自己的服務器中。

而第四點,我們可以監聽 UIapplicationWillResignActiveNotification 事件,當觸發時,手動調用 logger 的 db_save 方法,發送數據給服務器。

廢話了半天,現在看下實現。

首先我們設定 log 的消息結構。自定義一個 LogFormatter, 遵從 DDLogFormatter 協議,我們需要重寫 formatLogMessage 這個方法,這個方法返回值是 NSString,就是最終 log 的消息體字符串。而輸入參數 logMessage 是由 logger 發的一個 DDLogMessage 對象,包含了一些必要的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@interface DDLogMessage : NSObject {
    // Direct accessors to be used only for performance
    @public
    NSString *_message;
    DDLogLevel _level;
    DDLogFlag _flag;
    NSUInteger _context;
    NSString *_file;
    NSString *_fileName;
    NSString *_function;
    NSUInteger _line;
    id _tag;
    DDLogMessageOptions _options;
    NSDate *_timestamp;
    NSString *_threadID;
    NSString *_threadName;
    NSString *_queueLabel;
}

可以利用這些信息構建自己的 log 消息體。比如我們這里只需要 log 所在文件名,行數還有所在函數名,則可以這樣寫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (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";
    //這里的格式: {"location":"myfile.m:120(void a::sub(int)"}, 文件名,行數和函數名是用的編譯器宏 __FILE__, __LINE__, __PRETTY_FUNCTION__
    logDict[@"location"] = [NSString stringWithFormat:@"%@:%lu(%@)", locationString, (unsigned long)logMessage->_line, logMessage->_function]
    //嘗試將logDict內容轉為字符串,其實這里可以直接構造字符串,但真實項目中,肯定需要很多其他的信息,不可能僅僅文件名、行數和函數名就夠了的。
    NSError *error;
    NSData *outputJson = [NSJSONSerialization dataWithJSONObject:logfields options:0 error:&error];
    if (error)
        return @"{/"location/":/"error/"}"
    NSString *jsonString = [[NSString alloc] initWithData:outputJson encoding:NSUTF8StringEncoding];
    if (jsonString)
        return jsonString;
    return @"{/"location/":/"error/"}"
}

接下來自定義 logger,其繼承自 DDAbstractDatabaseLogger。在初始化方法中,先設定好一些參數,以及添加一個UIApplicationWillResignActiveNotification的觀察者,用以實現第四個需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (instancetype)init {
    self = [super init];
    if (self) {
        self.deleteInterval = 0;
        self.maxAge = 0;
        self.deleteOnEverySave = NO;
        self.saveInterval = 60;
        self.saveThreshold = 500;
        //別忘了在 dealloc 里 removeObserver
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(saveOnSuspend)
                                                     name:@"UIApplicationWillResignActiveNotification"
                                                   object:nil];
    }
    return self;
}
- (void)saveOnSuspend {
    dispatch_async(_loggerQueue, ^{
        [self db_save];
    });
}

每次打 log 時,db_log: 會被調用,我們在這個函數里,將 log 發給 formatter,將返回的 log 消息體字符串保存在緩沖中。 db_log 的返回值告訴 DDLog 該條 log 是否成功保存進緩存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (BOOL)db_log:(DDLogMessage *)logMessage
{
    if (!_logFormatter) {
        //沒有指定 formatter
        return NO;
    }
    if (!_logMessagesArray)
        _logMessagesArray = [NSMutableArray arrayWithCapacity:500]; // 我們的saveThreshold只有500,所以一般情況下夠了
    if ([_logMessagesArray count] > 2000) {
        // 如果段時間內進入大量log,并且遲遲發不到服務器上,我們可以判斷哪里出了問題,在這之后的 log 暫時不處理了。
        // 但我們依然要告訴 DDLog 這個存進去了。
        return YES;
    }
    //利用 formatter 得到消息字符串,添加到緩存
    [_logMessagesArray addObject:[_logFormatter formatLogMessage:logMessage]];
    return YES;
}

當1分鐘或者未寫入 log 數達到 500 時, db_save 就會被調用,我們在這里,將緩存的數據上傳到自己的服務器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)db_save{
    //判斷是否在 logger 自己的GCD隊列中
    if (![self isOnInternalLoggerQueue])
        NSAssert(NO, @"db_saveAndDelete should only be executed on the internalLoggerQueue thread, if you're seeing this, your doing it wrong.");
    //如果緩存內沒數據,啥也不做
    if ([_logMessagesArray count] == 0)
        return;
    獲取緩存中所有數據,之后將緩存清空
    NSArray *oldLogMessagesArray = [_logMessagesArray copy];
    _logMessagesArray = [NSMutableArray arrayWithCapacity:0];
    //用換行符,把所有的數據拼成一個大字符串 
    NSString *logMessagesString = [oldLogMessagesArray componentsJoinedByString:@"/n"];
    //發送給咱自己服務器(自己實現了)
    [self post:logMessagesString];
}

最后,我們需要在程序某處定義全局 log 等級(我這里使用 Info),并在 AppDelegate 的 didFinishLaunchingWithOptions 里初始化所有 Log 相關的東西:

1
2
3
4
5
6
7
8
static NSUInteger LOG_LEVEL_DEF = DDLogLevelInfo;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    MyLogger *logger = [MyLogger new];
    [logger setLogFormatter:[MyLogFormatter new]];
    [DDLog addLogger:logger];
    //....
}

然后就可以利用 DDLogError, DDLogWarning 等宏在程序中打 log 了。使用方法與 NSLog 一樣。這幾個宏的定義:

1
2
3
4
5
6
//注意,DDLogError 是肯定同步的
#define DDLogError(frmt, ...) LOG_MAYBE(NO, LOG_LEVEL_DEF, DDLogFlagError, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
#define DDLogWarn(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagWarning, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
#define DDLogInfo(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagInfo, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
#define DDLogDebug(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagDebug, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
#define DDLogVerbose(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagVerbose, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)

最后感謝 CocoaLumberjack 的作者 Robbie Hanson ,如果你喜歡他開發的庫,比如 XMPPFramework,別忘了幫他買杯啤酒哦~


上一篇:11-UITableView

下一篇:ScrumPlanningCard

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
97久久超碰福利国产精品…| 日韩av最新在线观看| 亚洲精品电影在线| 精品亚洲一区二区三区在线观看| 久久免费视频在线观看| 黑人巨大精品欧美一区二区一视频| 色综合久久88| 国产精品视频资源| 青青久久av北条麻妃黑人| 欧美性受xxx| 日韩中文在线中文网三级| 欧美—级高清免费播放| 高清在线视频日韩欧美| 欧美国产日韩一区二区| 国产精品久久久久久久久久久不卡| 国产中文字幕日韩| 国产成人精品久久二区二区| 91天堂在线观看| 欧美高清自拍一区| 最近2019中文字幕大全第二页| 国产精品色婷婷视频| 日韩在线视频免费观看| 麻豆一区二区在线观看| 欧美在线视频一区| 国产日韩精品电影| 国产福利精品av综合导导航| 色偷偷噜噜噜亚洲男人的天堂| 91精品视频在线看| 亚洲国产日韩一区| 日韩av免费观影| zzijzzij亚洲日本成熟少妇| 国产精品91在线观看| 欧美一级黑人aaaaaaa做受| 在线观看久久久久久| www日韩中文字幕在线看| 欧美极品xxxx| 日韩av123| 日韩第一页在线| 亚洲一区二区中文| 亚洲男人天堂手机在线| 国产亚洲在线播放| 久久69精品久久久久久久电影好| 成人写真福利网| 菠萝蜜影院一区二区免费| 亚洲一区二区三区在线免费观看| 秋霞午夜一区二区| 2018中文字幕一区二区三区| 欧美韩国理论所午夜片917电影| 一区二区三区国产视频| 久久香蕉国产线看观看av| 国产精品va在线播放| 久热在线中文字幕色999舞| 日韩在线免费视频| 国产精品678| 91精品视频大全| 欧美日韩福利电影| 精品国产电影一区| 91国产在线精品| 欧美综合在线观看| 91视频免费在线| 热re99久久精品国产66热| 国产精品视频网址| 国产日韩精品一区二区| 精品少妇一区二区30p| 亚洲成人免费网站| 伊人伊人伊人久久| 欧美巨大黑人极品精男| 日韩视频永久免费观看| 亚洲精品久久久久中文字幕欢迎你| 成人激情电影一区二区| 欧美日韩在线观看视频| 久久精品国产91精品亚洲| 一区二区欧美激情| 日韩视频亚洲视频| 91久久久久久久久久久久久| 欧美极品少妇xxxxx| 亚洲色图狂野欧美| 欧美精品在线网站| 成人免费视频在线观看超级碰| 日韩视频免费大全中文字幕| 国产日本欧美一区二区三区在线| 欧美成人三级视频网站| 亚洲第一福利网| 欧美成人精品在线| 国产亚洲激情视频在线| 久久精品国产亚洲7777| 亚洲欧美日韩精品久久| 成人免费网站在线| 欧美美女15p| 国产精品v日韩精品| 国模吧一区二区三区| 91国产一区在线| 日韩成人在线电影网| 丝袜情趣国产精品| 欧美激情性做爰免费视频| 国产精品久久久久久久久| 久久国产精品久久国产精品| 91夜夜揉人人捏人人添红杏| 国产乱肥老妇国产一区二| 欧美美女15p| 青青草原成人在线视频| 亚洲欧美日韩一区二区三区在线| 久久免费视频在线| 久久亚洲国产精品| 4438全国成人免费| 亚洲国产日韩欧美在线图片| 91社影院在线观看| 不卡av在线网站| 国产视频久久久久| 亚洲999一在线观看www| 国产精品人成电影在线观看| 欧美最猛黑人xxxx黑人猛叫黄| 成人妇女免费播放久久久| 国产日韩精品在线| 在线看日韩av| 日韩少妇与小伙激情| 国产精品网红福利| 久久精品99久久香蕉国产色戒| 日韩精品欧美激情| 日韩视频在线一区| 亚洲欧美日韩精品久久亚洲区| 欧美夫妻性生活视频| 久久久女女女女999久久| 亚洲一区第一页| 国产午夜精品视频免费不卡69堂| 欧美性xxxxx极品| 97国产真实伦对白精彩视频8| 色综合导航网站| 久久九九全国免费精品观看| 深夜福利一区二区| 免费97视频在线精品国自产拍| 欧美日韩国产一中文字不卡| 色偷偷av一区二区三区| 最好看的2019年中文视频| 国产精品老女人精品视频| 最近中文字幕日韩精品| 97人洗澡人人免费公开视频碰碰碰| 亚洲欧美日韩在线高清直播| 午夜精品久久久久久久男人的天堂| 亚洲视频在线免费看| 日韩男女性生活视频| 国产精品毛片a∨一区二区三区|国| 中文字幕在线观看亚洲| 精品久久香蕉国产线看观看gif| 日韩高清免费观看| 国产精品久久久久影院日本| 欧美精品情趣视频| 精品久久久久久久久中文字幕| 久久国产精品免费视频| 国产精品免费久久久久久| 成人免费网站在线观看| 亚洲精品中文字幕有码专区| 亚洲成av人乱码色午夜| 亚洲欧美激情在线视频| 亚洲国产欧美一区二区三区同亚洲| 国产精品亚洲激情| 国产精品久久精品| 麻豆国产va免费精品高清在线| 久久久免费精品| 欧美日韩国产精品专区| 国产精品永久免费| 亚洲国产精品国自产拍av秋霞| 91精品视频免费| 在线观看视频亚洲|