會(huì)了JNI,我們來(lái)看看Logan庫(kù)

前言

隨著業(yè)務(wù)的發(fā)展,日志是必不可少的環(huán)節(jié)之一,越來(lái)越多的日志庫(kù)如雨后春筍般嶄露頭角。這里就有很多日志庫(kù)從快、準(zhǔn)、穩(wěn)這三個(gè)維度的追求,將觸手從java層蔓延到了Linux層。這里就市面上運(yùn)用了mmap技術(shù)的開(kāi)源日志庫(kù)(騰訊Mars中的一環(huán)XLog\美團(tuán)點(diǎn)評(píng)的logan)進(jìn)行一些個(gè)人總結(jié),如有不對(duì)的地方,還請(qǐng)指正。
抱著從簡(jiǎn)到雜,從易到復(fù)的原則,這里首先簡(jiǎn)述美團(tuán)點(diǎn)評(píng)的Logan庫(kù),希望對(duì)廣大希望快速擁有日志能力的開(kāi)發(fā)者們?cè)缛彰撾x日志坑。

Logan在初始化如下:
Logan初始化.png
接著以炒菜的形式來(lái)表述吧:
1、切菜、備油(這里我們可以看到必須的幾要素如下)

(1)、mmap文件的緩存路徑;(2)、每個(gè)文件的最大文件大小,默認(rèn)10M;(3)、保存日志的緩存天數(shù),默認(rèn)是7天;(4)、ASE加密的key;(5)、ASE加密的IV;(6)、需要SD卡最小的額外存儲(chǔ),默認(rèn)是50M。
1LoganConfig.png
1-1初始化參數(shù).png

2、準(zhǔn)備工具
LoganControlCenter使用一個(gè)基于鏈接節(jié)點(diǎn)的無(wú)界線(xiàn)程安全隊(duì)列的ConcurrentLinkedQueue來(lái)處理消費(fèi)事件。同樣我們還是準(zhǔn)備工具:
重點(diǎn)如下:(1)、最大隊(duì)列數(shù),默認(rèn)500個(gè);(2)、自定義線(xiàn)程LogainThread;(3)、日期格式
隊(duì)列處理中心參數(shù).png
這個(gè)隊(duì)列處理中心擔(dān)負(fù)了兩個(gè)主要責(zé)任:一個(gè)是分發(fā)處理日志事件、還有一個(gè)就是負(fù)責(zé)把之前參數(shù)全部緩存起來(lái)在合適的時(shí)機(jī)傳給JNI層。
這些都有了,我們思考,如果我們要寫(xiě)一個(gè)日志庫(kù),配置、參數(shù)都有了是不是就差寫(xiě)文件了,就好比菜和工具都齊了,我們現(xiàn)在是不要要有菜譜做細(xì)節(jié)啊,那好,我們接下來(lái)看美團(tuán)點(diǎn)評(píng)的同學(xué)如何做菜下酒。

3、JNI層交互方法匯總

前言說(shuō)到這是一個(gè)基于Linux層面的一種內(nèi)存映射技術(shù),這技術(shù)會(huì)在后面統(tǒng)一講解,這里暫時(shí)先梳理Logan庫(kù)的思路和邏輯。我已經(jīng)迫不及待的在找菜譜了,直接搜關(guān)鍵字Native,因?yàn)樯弦徽鹿?jié)提到JNI交互的語(yǔ)法格式需要一個(gè)native字段,這里我們直接抓住重點(diǎn),看到clogan_init、clogan_open、clogan_debug、clogan_write、clogan_flush
CLoganProtocol中維護(hù)的native方法.png
那既然我們找到菜譜了,我們看看究竟每一步是干嘛的,第一步clogan_init:簡(jiǎn)述就是初始化緩存路徑、目錄路徑,秘鑰,如果mmap文件緩存目錄初始化失敗了,這里會(huì)延用內(nèi)存作為緩存的姿勢(shì)繼續(xù)保存文件。因?yàn)檫壿嬢^多,刪除了部分判斷邏輯用省略號(hào)代替。
/**
 * Logan初始化
 * @param cachedirs 緩存路徑
 * @param pathdirs  目錄路徑
 * @param max_file  日志文件最大值
 */
int
clogan_init(const char *cache_dirs, const char *path_dirs, int max_file, const char *encrypt_key16,
            const char *encrypt_iv16) {
    .........
    if (max_file > 0) {
        max_file_len = max_file;
    } else {
        max_file_len = LOGAN_LOGFILE_MAXLENGTH;
    }
    ...........
    //初始化秘鑰的KEY和IV
    aes_init_key_iv(encrypt_key16, encrypt_iv16);
    makedir_clogan(cache_path); //創(chuàng)建保存mmap文件的目錄
    strcat(cache_path, LOGAN_CACHE_FILE);
    ..........
    char *dirs = (char *) malloc(total); //緩存文件目錄
    if (NULL != dirs) {
        _dir_path = dirs; //日志寫(xiě)入的文件目錄
    } else {
        ......
    }
   ......
    if (isAddDivede)
        strcat(dirs, LOGAN_DIVIDE_SYMBOL);
    makedir_clogan(_dir_path); //創(chuàng)建緩存目錄,如果初始化失敗,注意釋放_(tái)dir_path

    int flag = LOGAN_MMAP_FAIL;
    if (NULL == _logan_buffer) {
        if (NULL == _cache_buffer_buffer) {
            flag = open_mmap_file_clogan(cache_path, &_logan_buffer, &_cache_buffer_buffer);
        } else {
            flag = LOGAN_MMAP_MEMORY;
        }
    } else {
        flag = LOGAN_MMAP_MMAP;
    }
    .............
    if (is_init_ok) {
        if (NULL == logan_model) {
            logan_model = malloc(sizeof(cLogan_model));
            if (NULL != logan_model) { //堆非空判斷 , 如果為null , 就失敗
                memset(logan_model, 0, sizeof(cLogan_model));
            } else {
                is_init_ok = 0;
                printf_clogan("clogan_init > malloc memory fail for logan_model\n");
                back = CLOGAN_INIT_FAIL_NOMALLOC;
                return back;
            }
        }
        if (flag == LOGAN_MMAP_MMAP) //MMAP的緩存模式,從緩存的MMAP中讀取數(shù)據(jù)
            read_mmap_data_clogan(_dir_path);
        printf_clogan("clogan_init > logan init success\n");
    } else {
        printf_clogan("clogan_open > logan init fail\n");
        // 初始化失敗,刪除所有路徑
       .........
    return back;
}

接下來(lái)是clogan_open

FILE *file_temp = fopen(logan_model->file_path, "ab+");
long longBytes = ftell(file_temp);
logan_model->file_len = longBytes;
// 在之前init的時(shí)候會(huì)判斷mmap緩存目錄是否有緩存,如果有,下面在open的時(shí)候會(huì)把緩存數(shù)據(jù)寫(xiě)入防止丟失,這里主要告訴我們:1、如果之前有剩下的菜沒(méi)有燒的,這次也放進(jìn)去,就是有緩存我們也寫(xiě)到日志中;2、初始化我們的logan_model結(jié)構(gòu)體(后面會(huì)貼出解釋?zhuān)娣盼募?shù)據(jù)的精髓類(lèi),里面包含了文件大小,壓縮等信息);3、對(duì)于是否是mmap,這里還做了目錄創(chuàng)建等處理
int clogan_open(const char *pathname) {
    .........
    if (NULL != logan_model) { //回寫(xiě)到日志中
        if (logan_model->total_len > LOGAN_WRITEPROTOCOL_HEAER_LENGTH) {
            clogan_flush();
        }
      ..........
    } else {
        logan_model = malloc(sizeof(cLogan_model));
        // 初始化Logan_model結(jié)構(gòu)體
        ............
    }
    char *temp = NULL;

    size_t file_path_len = strlen(_dir_path) + strlen(pathname) + 1;
    char *temp_file = malloc(file_path_len); // 日志文件路徑
    if (NULL != temp_file) {
        memset(temp_file, 0, file_path_len);
        temp = temp_file;
        memcpy(temp, _dir_path, strlen(_dir_path));
        temp += strlen(_dir_path);
        memcpy(temp, pathname, strlen(pathname)); //創(chuàng)建文件路徑
        logan_model->file_path = temp_file;

        if (!init_file_clogan(logan_model)) {  //初始化文件IO和文件大小
            is_open_ok = 0;
            back = CLOGAN_OPEN_FAIL_IO;
            return back;
        }

        if (init_zlib_clogan(logan_model) != Z_OK) { //初始化zlib壓縮
            is_open_ok = 0;
            back = CLOGAN_OPEN_FAIL_ZLIB;
            return back;
        }

        logan_model->buffer_point = _logan_buffer;

        if (buffer_type == LOGAN_MMAP_MMAP) {  //如果是MMAP,緩存文件目錄和文件名稱(chēng)
            cJSON *root = NULL;
            Json_map_logan *map = NULL;
            root = cJSON_CreateObject();
            map = create_json_map_logan();
            char *back_data = NULL;
            if (NULL != root) {
                if (NULL != map) {
                    add_item_number_clogan(map, LOGAN_VERSION_KEY, CLOGAN_VERSION_NUMBER);
                    add_item_string_clogan(map, LOGAN_PATH_KEY, pathname);
                    inflate_json_by_map_clogan(root, map);
                    back_data = cJSON_PrintUnformatted(root);
                }
                cJSON_Delete(root);
                if (NULL != back_data) {
                    add_mmap_header_clogan(back_data, logan_model);
                    free(back_data);
                } else {
                    logan_model->total_point = _logan_buffer;
                    logan_model->total_len = 0;
                }
            } else {
                logan_model->total_point = _logan_buffer;
                logan_model->total_len = 0;
            }

            logan_model->last_point = logan_model->total_point + LOGAN_MMAP_TOTALLEN;

            if (NULL != map) {
                delete_json_map_clogan(map);
            }
        } else {
            logan_model->total_point = _logan_buffer;
            logan_model->total_len = 0;
            logan_model->last_point = logan_model->total_point + LOGAN_MMAP_TOTALLEN;
        }
        restore_last_position_clogan(logan_model);
        init_encrypt_key_clogan(logan_model);
        logan_model->is_ok = 1;
        is_open_ok = 1;
    } else {
        is_open_ok = 0;
        back = CLOGAN_OPEN_FAIL_MALLOC;
        printf_clogan("clogan_open > malloc memory fail\n");
    }
    ..........
    return back;
}
typedef struct logan_model_struct {
    int total_len; //數(shù)據(jù)長(zhǎng)度
    char *file_path; //文件路徑
    int is_malloc_zlib;
    z_stream *strm;
    int zlib_type; //壓縮類(lèi)型
    char remain_data[16]; //剩余空間
    int remain_data_len; //剩余空間長(zhǎng)度

    int is_ready_gzip; //是否可以gzip

    int file_stream_type; //文件流類(lèi)型
    FILE *file; //文件流

    long file_len; //文件大小

    unsigned char *buffer_point; //緩存的指針 (不變)
    unsigned char *last_point; //最后寫(xiě)入位置的指針
    unsigned char *total_point; //總數(shù)的指針 (可能變) , 給c看,低字節(jié)
    unsigned char *content_lent_point;//協(xié)議內(nèi)容長(zhǎng)度指針 , 給java看,高字節(jié)
    int content_len; //內(nèi)容的大小

    unsigned char aes_iv[16]; //aes_iv
    int is_ok;

} cLogan_model;

接下來(lái)就是精髓了,就是菜譜告訴我們,第一步我們要放油初始化文件目錄參數(shù),第二步告訴我們調(diào)用open方法開(kāi)火,第三步就是放菜啦clogan_write,這里都是寫(xiě)入緩存的,具體隨我們來(lái)看美團(tuán)點(diǎn)評(píng)的同學(xué)如何來(lái)玩。

/**
 @brief 寫(xiě)入數(shù)據(jù) 按照順序和類(lèi)型傳值(強(qiáng)調(diào)、強(qiáng)調(diào)、強(qiáng)調(diào))
 @param flag 日志類(lèi)型 (int)
 @param log 日志內(nèi)容 (char*)
 @param local_time 日志發(fā)生的本地時(shí)間,形如1502100065601 (long long)
 @param thread_name 線(xiàn)程名稱(chēng) (char*)
 @param thread_id 線(xiàn)程id (long long) 為了兼容JAVA
 @param ismain 是否為主線(xiàn)程,0為是主線(xiàn)程,1位非主線(xiàn)程 (int)
 */
int
clogan_write(int flag, char *log, long long local_time, char *thread_name, long long thread_id,
             int is_main) {
    int back = CLOGAN_WRITE_FAIL_HEADER;
    if (!is_init_ok || NULL == logan_model || !is_open_ok) {
        back = CLOGAN_WRITE_FAIL_HEADER;
        return back;
    }
    //如果文件大小超過(guò)10M
    if (is_file_exist_clogan(logan_model->file_path)) {
        if (logan_model->file_len > max_file_len) {
            printf_clogan("clogan_write > beyond max file , can't write log\n");
            back = CLOAGN_WRITE_FAIL_MAXFILE;
            return back;
        }
    } else {
        if (logan_model->file_stream_type == LOGAN_FILE_OPEN) {
            fclose(logan_model->file);
            logan_model->file_stream_type = LOGAN_FILE_CLOSE;
        }
        if (NULL != _dir_path) {
            if (!is_file_exist_clogan(_dir_path)) {
                makedir_clogan(_dir_path);
            }
            init_file_clogan(logan_model);
            printf_clogan("clogan_write > create log file , restore open file stream \n");
        }
    }

    //判斷MMAP文件是否存在,如果被刪除,用內(nèi)存緩存
    if (buffer_type == LOGAN_MMAP_MMAP && !is_file_exist_clogan(_mmap_file_path)) {
        if (NULL != _cache_buffer_buffer) {
            buffer_type = LOGAN_MMAP_MEMORY;
            buffer_length = LOGAN_MEMORY_LENGTH;

            printf_clogan("clogan_write > change to memory buffer");

            _logan_buffer = _cache_buffer_buffer;
            logan_model->total_point = _logan_buffer;
            logan_model->total_len = 0;
            logan_model->content_len = 0;
            logan_model->remain_data_len = 0;

            if (logan_model->zlib_type == LOGAN_ZLIB_INIT) {
                clogan_zlib_delete_stream(logan_model); //關(guān)閉已開(kāi)的流
            }

            logan_model->last_point = logan_model->total_point + LOGAN_MMAP_TOTALLEN;
            restore_last_position_clogan(logan_model);
            init_zlib_clogan(logan_model);
            init_encrypt_key_clogan(logan_model);
            logan_model->is_ok = 1;
        } else {
            buffer_type = LOGAN_MMAP_FAIL;
            is_init_ok = 0;
            is_open_ok = 0;
            _logan_buffer = NULL;
        }
    }

    Construct_Data_cLogan *data = construct_json_data_clogan(log, flag, local_time, thread_name,
                                                             thread_id, is_main);
    if (NULL != data) {
        clogan_write_section(data->data, data->data_len);
        construct_data_delete_clogan(data);
        back = CLOGAN_WRITE_SUCCESS;
    } else {
        back = CLOGAN_WRITE_FAIL_MALLOC;
    }
    return back;
}

上面這塊東西代碼塊不大我就不省略了,別看這里代碼塊不多,但卻是重中之重,首先當(dāng)頭便是文件寫(xiě)入策略,1、如果當(dāng)日只會(huì)有一個(gè)文件記錄,且大于10M則拒絕寫(xiě)入,這里筆者認(rèn)為應(yīng)該是根據(jù)實(shí)際情況做了一層妥協(xié),其實(shí)很多業(yè)務(wù)方的數(shù)據(jù)都會(huì)丟進(jìn)來(lái),那么美團(tuán)在業(yè)務(wù)回?fù)频臅r(shí)候,用戶(hù)肯定會(huì)很懵逼,怎么就反饋了一個(gè)頁(yè)面打不開(kāi)的問(wèn)題,我就10M流量沒(méi)了,這里我們是否可以實(shí)現(xiàn)多業(yè)務(wù)多文件或者多類(lèi)別的區(qū)分,這樣在回?fù)频臅r(shí)候也可以側(cè)重點(diǎn)來(lái)進(jìn)行回?fù)疲簿屯瑫r(shí)也避免了單文件10M數(shù)據(jù)拒絕寫(xiě)入的尷尬境地。2、這里的寫(xiě)入在非特殊情況下只是寫(xiě)入緩存和內(nèi)存,讀者們要在這里注意下。3、clogan_write_section關(guān)注這個(gè)方法,非常重要,這里是在正常情況下,非接入方干預(yù)前提下的緩存寫(xiě)入用戶(hù)真實(shí)文件的時(shí)機(jī),具體邏輯如下:

void clogan_write2(char *data, int length) {
    if (NULL != logan_model && logan_model->is_ok) {
        clogan_zlib_compress(logan_model, data, length);
        update_length_clogan(logan_model); //有數(shù)據(jù)操作,要更新數(shù)據(jù)長(zhǎng)度到緩存中
        int is_gzip_end = 0;

        if (!logan_model->file_len ||
            logan_model->content_len >= LOGAN_MAX_GZIP_UTIL) { //是否一個(gè)壓縮單元結(jié)束
            clogan_zlib_end_compress(logan_model);
            is_gzip_end = 1;
            update_length_clogan(logan_model);
        }

        int isWrite = 0;
        if (!logan_model->file_len && is_gzip_end) { //如果是個(gè)空文件、第一條日志寫(xiě)入
            isWrite = 1;
            printf_clogan("clogan_write2 > write type empty file \n");
        } else if (buffer_type == LOGAN_MMAP_MEMORY && is_gzip_end) { //直接寫(xiě)入文件
            isWrite = 1;
            printf_clogan("clogan_write2 > write type memory \n");
        } else if (buffer_type == LOGAN_MMAP_MMAP &&
                   logan_model->total_len >=
                   buffer_length / LOGAN_WRITEPROTOCOL_DEVIDE_VALUE) { //如果是MMAP 且 文件長(zhǎng)度已經(jīng)超過(guò)三分之一
            isWrite = 1;
            printf_clogan("clogan_write2 > write type MMAP \n");
        }
        if (isWrite) { //寫(xiě)入
            write_flush_clogan();
        } else if (is_gzip_end) { //如果是mmap類(lèi)型,不回寫(xiě)IO,初始化下一步
            logan_model->content_len = 0;
            logan_model->remain_data_len = 0;
            init_zlib_clogan(logan_model);
            restore_last_position_clogan(logan_model);
            init_encrypt_key_clogan(logan_model);
        }
    }
}

總結(jié)一下:1、如果是空文件的頭文件,直接寫(xiě)入文件;2、如果是內(nèi)存緩存且壓縮結(jié)束了,直接寫(xiě)入文件;3、如果是mmap緩存,且文件長(zhǎng)度超過(guò)緩存的三分之一,直接寫(xiě)入文件;
這時(shí)候差不多菜譜就讀的差不多了,那細(xì)心的讀者就會(huì)有一個(gè)問(wèn)題了,說(shuō)我就喜歡吃8分熟的牛排,我寫(xiě)了30條日志,并不滿(mǎn)足上述的3個(gè)條件,或者我就要在APP退出之前把所有緩存數(shù)據(jù)寫(xiě)入文件,我就喜歡這樣不寫(xiě)進(jìn)去我心里不踏實(shí)。那這里順理成章,一個(gè)經(jīng)過(guò)通用的SDK庫(kù)肯定具備完善的API,菜譜肯定是能滿(mǎn)足大多數(shù)人的需要的,那就是clogan_flush,接下來(lái)我們來(lái)梳理下:

int clogan_flush(void) {
    int back = CLOGAN_FLUSH_FAIL_INIT;
    if (!is_init_ok || NULL == logan_model) {
        return back;
    }
    write_flush_clogan();
    back = CLOGAN_FLUSH_SUCCESS;
    printf_clogan(" clogan_flush > write flush\n");
    return back;
}
void write_flush_clogan() {
    if (logan_model->zlib_type == LOGAN_ZLIB_ING) {
        clogan_zlib_end_compress(logan_model);
        update_length_clogan(logan_model);
    }
    if (logan_model->total_len > LOGAN_WRITEPROTOCOL_HEAER_LENGTH) {
        unsigned char *point = logan_model->total_point;
        point += LOGAN_MMAP_TOTALLEN;
        write_dest_clogan(point, sizeof(char), logan_model->total_len, logan_model);
        printf_clogan("write_flush_clogan > logan total len : %d \n", logan_model->total_len);
        clear_clogan(logan_model);
    }
}
//文件寫(xiě)入磁盤(pán)、更新文件大小
void write_dest_clogan(void *point, size_t size, size_t length, cLogan_model *loganModel) {
    if (!is_file_exist_clogan(loganModel->file_path)) { //如果文件被刪除,再創(chuàng)建一個(gè)文件
        if (logan_model->file_stream_type == LOGAN_FILE_OPEN) {
            fclose(logan_model->file);
            logan_model->file_stream_type = LOGAN_FILE_CLOSE;
        }
        if (NULL != _dir_path) {
            if (!is_file_exist_clogan(_dir_path)) {
                makedir_clogan(_dir_path);
            }
            init_file_clogan(logan_model);
            printf_clogan("clogan_write > create log file , restore open file stream \n");
        }
    }
    if (CLOGAN_EMPTY_FILE == loganModel->file_len) { //如果是空文件插入一行CLogan的頭文件
        insert_header_file_clogan(loganModel);
    }
    fwrite(point, sizeof(char), logan_model->total_len, logan_model->file);//寫(xiě)入到文件中
    fflush(logan_model->file);
    loganModel->file_len += loganModel->total_len; //修改文件大小
}

那這里就是寫(xiě)入磁盤(pán)、更新文件數(shù)據(jù)model.好了,這盤(pán)菜出鍋了,接下來(lái)會(huì)為大家簡(jiǎn)述下微信的Mars XLOG流程,那個(gè)比這個(gè)更全面些,但是魔改起來(lái)需要對(duì)C和底層有更高階的認(rèn)識(shí)。期望大家可以做出比我更美味的菜,如有錯(cuò)誤請(qǐng)指出,謝謝食客們的光臨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 32,275評(píng)論 2 89
  • title: App技術(shù)選型--日志框架--- **版 本 歷 史**| **版本** | **責(zé)任人** | *...
    海南雞閱讀 1,827評(píng)論 0 1
  • 提區(qū)塊鏈,言必稱(chēng)去中心化。我想別人解釋區(qū)塊鏈,也是拿分布式記賬技術(shù)來(lái)說(shuō)。可是我們真的想過(guò)為什么一定要去中心化嗎?今...
    張永勝_永往直前閱讀 346評(píng)論 0 1
  • 01 今天同事都在討論小孩子的學(xué)習(xí)成績(jī),成績(jī)好的家長(zhǎng)說(shuō)的歡天喜地,成績(jī)一般的家長(zhǎng)愁眉苦臉。家長(zhǎng)書(shū)法群里一位家長(zhǎng),他...
    2orange閱讀 149評(píng)論 0 0
  • 親愛(ài)的朋友您好,歡迎關(guān)注沈博士護(hù)膚坊,沈博士護(hù)膚坊隸屬于安徽識(shí)楓科技有限公司旗下一個(gè)獨(dú)立的部門(mén)。專(zhuān)注于各種...
    沈博士護(hù)膚坊閱讀 1,055評(píng)論 0 0

友情鏈接更多精彩內(nèi)容