前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >xlog接入方案

xlog接入方案

作者头像
felix
发布2018-06-08 11:43:47
4.3K0
发布2018-06-08 11:43:47
举报
文章被收录于专栏:Felix的技术分享

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写到日志文件中。

代码语言:javascript
复制
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 的目录请使用单独目录,防止误删其他文件。目前不会根据文件大小进行清理。

代码语言:javascript
复制
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中会调用。

代码语言:javascript
复制
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。

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017年01月12日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • xlog的特点
    • 上一次没写完的日志,如何重新写到日志中
    • 修改xlog默认的输出到文件的策略
      • xlog默认的策略
        • 改写输出到文件的策略
        • 相关链接
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档