xlog接入方案

mars 是微信最近开源的终端基础组件,是一个使用 C++ 编写的基础组件。 xlog是其中一个可单独使用的高性能日志模块。 本文将简单介绍下xlog的特点,并给出一个自定义的输出到文件的策略。


xlog的特点

使用流式压缩方式对单行日志进行压缩,压缩加密后写进作为 log 中间 buffer的 mmap 中,当 mmap 中的数据到达一定大小后再写进磁盘文件中。

输出到文件的主要实现是在 Appender 模块也是可插拔的,如果对默认的策略不满意可以自己实现一套。

xlog还存在一些其他策略:

  • 每次启动的时候会清理日志,防止占用太多用户磁盘空间
  • 为了防止 sdcard 被拔掉导致写不了日志,支持设置缓存目录,当 sdcard 插上时会把缓存目录里的日志写入到 sdcard 上

从下面的log2file流程图中可以看出xlog是如何利用cahce目录解决插拔sdcard的问题的。 log2file流程图

上一次没写完的日志,如何重新写到日志中

在日志模块初始化会执行如下的代码,sg_log_buff为与mmap文件映射的逻辑内存,这里会主动调用Flush,把mmap文件中的数据(即上一次没写到日志文件中的日志)fluse到buffer中,并调用__log2file写到日志文件中。

char mmap_file_path[512] = {0};
snprintf(mmap_file_path, sizeof(mmap_file_path), "%s/%s.mmap2", sg_cache_logdir.empty()?_dir:sg_cache_logdir.c_str(), _nameprefix);

    bool use_mmap = false;
    if (OpenMmapFile(mmap_file_path, kBufferBlockLength, sg_mmmap_file))  {
        sg_log_buff = new LogBuffer(sg_mmmap_file.data(), kBufferBlockLength, true);
        use_mmap = true;
    }
    AutoBuffer buffer;
    sg_log_buff->Flush(buffer); // 把mmap文件中日志信息flush到内存中,下面调用__log2file写到文件中。
    if (buffer.Ptr()) {
        __writetips2file("~~~~~ begin of mmap ~~~~~\n");
        __log2file(buffer.Ptr(), buffer.Length());
        __writetips2file("~~~~~ end of mmap ~~~~~%s\n", mark_info);
    }

修改xlog默认的输出到文件的策略

xlog默认的策略

每次启动时会删除过期文件,只保留十天内的日志文件(该值定义在appender.cc中的 kMaxLogAliveTime ),所以给 Xlog 的目录请使用单独目录,防止误删其他文件。目前不会根据文件大小进行清理。

static void __del_timeout_file(const std::string& _log_path) {
    time_t now_time = time(NULL);

    boost::filesystem::path path(_log_path);

    if (boost::filesystem::exists(path) && boost::filesystem::is_directory(path)){
        boost::filesystem::directory_iterator end_iter;
        for (boost::filesystem::directory_iterator iter(path); iter != end_iter; ++iter) {
            time_t fileModifyTime = boost::filesystem::last_write_time(iter->path());

            if (now_time > fileModifyTime && now_time - fileModifyTime > kMaxLogAliveTime) {
                if (boost::filesystem::is_regular_file(iter->status())) {
                    boost::filesystem::remove(iter->path());
                }
                else if (boost::filesystem::is_directory(iter->status())) {
                    __del_files(iter->path().string());
                }
            }
        }
    }
}

日志文件是按天命名的,每天产生一个日志文件。 在__openlogfile__log2file中会调用。

static void __make_logfilename(const timeval& _tv, const std::string& _logdir, const char* _prefix, const std::string& _fileext, char* _filepath, unsigned int _len) {
    time_t sec = _tv.tv_sec;
    tm tcur = *localtime((const time_t*)&sec);

    std::string logfilepath = _logdir;
    logfilepath += "/";
    logfilepath += _prefix;
    char temp [64] = {0};
    snprintf(temp, 64, "_%d%02d%02d", 1900 + tcur.tm_year, 1 + tcur.tm_mon, tcur.tm_mday);
    logfilepath += temp;
    logfilepath += ".";
    logfilepath += _fileext;
    strncpy(_filepath, logfilepath.c_str(), _len - 1);
    _filepath[_len - 1] = '\0';
}

比如在2017年01月10号的日志会写到LOG_20170110.log文件中,到2017年01月11号时,日志写到LOG_20170111.log,依次类推。过期的日志文件会在日志模块初始化时被清理掉。

改写输出到文件的策略

xlog输出的文件的逻辑都在appender.cc中实现,可以修改这里的代码实现一套自己的策略。 比如想要控制日志文件的大小,即Rotating file based on size的策略。 这里的实现方案并不支持cachedir。

static bool __writefile(const void* _data, size_t _len) {
    if (NULL == sg_logfile) {
        assert(false);
        return false;
    }

    long before_len = ftell(sg_logfile);
    if (before_len < 0) return false;

    // 如果发生了__roate,需要reopen sg_logfile
    if(before_len+_len > sg_max_size){
        if(!__roate()){
            return false;
        }
    }

    if (1 != fwrite(_data, _len, 1, sg_logfile)) {
        int err = ferror(sg_logfile);

        __writetips2console("write file error:%d", err);


        ftruncate(fileno(sg_logfile), before_len);
        fseek(sg_logfile, 0, SEEK_END);

        char err_log[256] = {0};
        snprintf(err_log, sizeof(err_log), "\nwrite file error:%d\n", err);

        char tmp[256] = {0};
        size_t len = sizeof(tmp);
        LogBuffer::Write(err_log, strnlen(err_log, sizeof(err_log)), tmp, len);

        fwrite(tmp, len, 1, sg_logfile);

        return false;
    }

    return true;
}

template <typename T>
std::string NumberToString ( T Number )
{
     std::ostringstream ss;
     ss << Number;
     return ss.str();
}

static std::string __calc_filename(const std::string& _logdir, const char* _prefix, const std::string& _fileext,unsigned int index){
    std::string logfilepath = _logdir;
    logfilepath += "/";
    logfilepath += _prefix;
    if(index){
        logfilepath += ".";
        logfilepath += NumberToString(index);
    }
    logfilepath += ".";
    logfilepath += _fileext;
    return logfilepath;
}


    // Rotate files:
    // log.txt -> log.1.txt
    // log.1.txt -> log2.txt
    // log.2.txt -> log3.txt
    // log.3.txt -> delete
static bool __roate(){
    if(sg_logfilename.empty() || sg_logdir.empty() || sg_logfileprefix.empty()){
        return false;
    }

    fclose(sg_logfile);
    sg_logfile = NULL;

    for(auto i = sg_max_files; i > 0; --i){
        std::string src = __calc_filename(sg_logdir,sg_logfileprefix.c_str(),LOG_EXT,i-1);
        std::string target = __calc_filename(sg_logdir,sg_logfileprefix.c_str(),LOG_EXT,i);
        boost::filesystem::path src_path(src);
        boost::filesystem::path target_path(target);

        if(boost::filesystem::exists(target_path)){
            boost::filesystem::remove(target_path);
        }
        if(boost::filesystem::exists(src_path)){
            boost::filesystem::rename(src_path,target_path);
        }

    }

    //reopen
    sg_logfile = fopen(sg_logfilename.c_str(), "wb");
    if (NULL == sg_logfile) {
        __writetips2console("open file error:%d %s, path:%s", errno, strerror(errno), sg_logfilename.c_str());
        return false;
    }

    return true;
}

static bool __openlogfile(const std::string& _log_dir){
    if (sg_logdir.empty()) return false;

    // 如果sg_logfile已经打开了,则直接返回。
    if (NULL != sg_logfile) {
        return true;
    }

    sg_current_dir = _log_dir;
    sg_logfilename = __calc_filename(_log_dir, sg_logfileprefix.c_str(), LOG_EXT,0);

    sg_logfile = fopen(sg_logfilename.c_str(), "ab");

    if (NULL == sg_logfile) {
        __writetips2console("open file error:%d %s, path:%s", errno, strerror(errno), sg_logfilename.c_str());
    }

    return NULL != sg_logfile;
}

static void __log2file(const void* _data, size_t _len) {
    if (NULL == _data || 0 == _len || sg_logdir.empty()) {
        return;
    }

        ScopedLock lock_file(sg_mutex_log_file);
        if (__openlogfile(sg_logdir)) {
            __writefile(_data, _len);
            if (kAppednerAsync == sg_mode) {
                __closelogfile();
            }
        return;
}        

代码详见:github


相关链接

http://dev.qq.com/topic/581c2c46bef1702a2db3ae53

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏深度学习计算机视觉

操作系统知识梳理共9次缺页

第一章:概述 什么是操作系统? 是一段一直运行在计算机上的程序 是资源的分配者 向上管理软件向下管理硬件 为用户提供良好接口 中断的概念? 中断指当出现需要时,...

28950
来自专栏Laoqi's Linux运维专列

nginx之php-fpm优化

72570
来自专栏企鹅号快讯

学好webpack,一名前端开发工程师的自我修养

前言 webpack 前端工程中扮演的角色越来越重要,它也是前端工程化很重要的一环。本文将和大家一起按照项目流程学习使用 wwbpack,由浅入深的学习,妈妈再...

301100
来自专栏JAVA技术zhai

分享30道Redis面试题,面试官能问到的我都找到了

Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到...

31120
来自专栏学习有记

Python和SQL Server 2017的强大功能

25050
来自专栏个人分享

数据集成中间件知识点总结

  数据集成是把不同来源、格式、特点性质的数据在逻辑上或物理上有机地集中,从而为企业提供全面的数据共享。

52410
来自专栏北京马哥教育

十二条Linux运维面试必备经典笔试/面试题,来挑战一下吧!

运维行业正在变革,推荐阅读:30万年薪Linux运维工程师成长魔法 又到了一年一度的秋招,作为运维方向,看了一些面经,收集了一些笔试面试题,总结了一下,贴出来仅...

431150
来自专栏blackpiglet

使用 Docker 部署 MediaWiki

MediaWiki 是 Wikipedia 使用的网站解决方案的开源版,以个人观点来看,Wiki 在这个时代显得不够时尚,且不支持 MarkDown 等新兴的标...

36940
来自专栏Java帮帮-微信公众号-技术文章全总结

15.MVC/业务代表模式

15.MVC/业务代表模式 MVC 模式 MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开...

45270
来自专栏腾讯数据库技术

如何让xtrabackup恢复速度提升20倍?

25540

扫码关注云+社区

领取腾讯云代金券