sylar高性能服务器-日志(P7-P8)代码解析

文章目录

    • p7
      • 1.TabFormatItem
      • 2.init函数,对于{}内容的解析
      • 3.Util.h
      • 4.CmakeLists
      • 5.优化日志输出-流式输出
    • p8
      • 1.优化日志输出-格式化输出
      • 2.日志管理器
      • 3.单例模型设计
    • 测试(无调试步骤)

P7P8两节视频新增内容不多,主要看下优化日志输出使用的宏函数。本次记录的内容比较简单,没有一步一步详细写出来,如果对P7之前的代码不存在问题,那么写到这里应该不会存在疑惑。同时建议每一次看代码时都去捋一下日志几大组件之间的关系,看多了真的有点绕。

p7

1.TabFormatItem

新建一个字符标记,优化日志输出格式

class TabFormatItem : public LogFormatter::FormatItem { // 输出tab
public:
    TabFormatItem(const std::string& fmt = "") {}
    void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
        os << "\t";
    }
};

别忘了去解析字符模板map中进行注册

    static std::map<std::string, std::function<FormatItem::ptr(const std::string& str)>> s_format_items = {
        // {"m",[](const std::string& fmt) { return FormatItem::ptr(new MessageFormatItem(fmt)); } }
#define XX(str,C) \
        {#str, [] (const std::string& fmt) { return FormatItem::ptr(new C(fmt)); }}

        XX(m, MessageFormatItem),           //m:消息
        XX(p, LevelFormatItem),             //p:日志级别
        XX(r, EplaseFormatItem),            //r:累计毫秒数
        XX(c, LoggerNameFormatItem),        //c:日志名称
        XX(t, ThreadIdFormatItem),          //t:线程id
        XX(n, LineFormatItem),              //n:换行
        XX(d, DateTimeFormatItem),          //d:时间
        XX(l, LineFormatItem),              //l:行号
        XX(F, FiberIdFormatItem),           //F:协程id
        XX(T, TabFormatItem),               //T:table
        XX(f, FileNameFormatItem)           //f:文件名
#undef XX

更改日志默认的格式器模板

%d{%Y-%m-%d %H:%M:%S}%T%t%T%F%T[%p]%T[%c]%T%f:%l%T%m%n

查看效果

image-20231013085707474

2.init函数,对于{}内容的解析

对格式化模板的解析函数有一点小改动,

  • if(!fmt_status && !isalpha(m_pattern[n]) && m_pattern[n] != '{' && m_pattern[n] != '}')
    

    条件判断加入对当前解析状态fmt_status的判断,防止在{}未解析完跳出。同时把字符标记str的截取放在这里str = m_pattern.substr(i + 1, n - i - 1);,在跳出while后的fmt_status判断条件里,就可以不用再提取。简单改变了一下代码逻辑,自己不更改也没问题

  • if(m_pattern[n] == '}') {
                        fmt = m_pattern.substr(fmt_begin + 1, n - fmt_begin - 1);
                        // std::cout << fmt << std::endl;
                        fmt_status = 0;
                        ++ n;
                        break;
                    }
    

    判断}时,结束后fmt_status不再为2,更改为0,也是再跳出while后的fmt_status判断条件里,少写一个判断语句。

  • if (n == m_pattern.size()) {	//最后一个字符, 每次获得str都是走到下一个字符然后进行截取,所以只有最后一个字符需要特殊处理
    				if (str.empty()) {
    					str = m_pattern.substr(i + 1);
    				}
    			}
            }
    

    while循环中,每次可以看作使用双指针的形式,当遍历到最后一个元素时,就会产生越界,从而漏掉最后一个字符的提取。

  • if(fmt_status == 0) {
                if(!nstr.empty()) {
                    vec.push_back(std::make_tuple(nstr, std::string(), 0));
                    nstr.clear();
                }
                vec.push_back(std::make_tuple(str, fmt, 1));
                i = n - 1;
            } else if(fmt_status == 1) {
                std::cout << "pattern parse error: " << m_pattern << " - " << m_pattern.substr(i) << std::endl;
                vec.push_back(std::make_tuple("", fmt, 0));
            } 
    

    这里的判断再经过上面的几个改动后就可以精简一些。

3.Util.h

目前该文件的里的函数有获取线程ID和协程ID。

只实现了一个使用SYS_gettid获取线程ID的函数,不会的可以百度一下这个函数。

// util.h
#ifndef __SYKAR_UTIL_H__
#define __SYKAR_UTIL_H__
#include
#include
#include
#include 
#include

namespace sylar {

// 获取线程ID
pid_t GetTreadId();

// 获取协程ID
uint32_t GetFiberId();

}

#endif

// util.cc
#include "util.h"
namespace sylar {

pid_t GetTreadId() { return syscall(SYS_gettid); }

uint32_t GetFiberId() {
    return 0; // TODO
}

}

4.CmakeLists

把util.cc加入cmake配置文件中

set(LIB_SRC
    sylar/log.cc
    sylar/util.cc
    )

5.优化日志输出-流式输出

每次我们在定义日志器的时候比较麻烦,需要去传入一堆参数,而很多参数都是代码会自动获取到的,唯一需要改动的就是传入的日志级别,sylar这里使用了宏函数来使日志的定义更加的方便简洁。

  • 新建一个LogEventWrap类,用来专门存放event事件

    // log.h
    class LogEventWrap {
    public:
        LogEventWrap(LogEvent::ptr e);
        ~LogEventWrap();
        std::stringstream& getSS();
        LogEvent::ptr getEvent() const { return m_event;}
    private:
        LogEvent::ptr m_event;
    };
    // log.cc
    LogEventWrap::LogEventWrap(LogEvent::ptr e)
        : m_event(e)  {
    
    }
    
    LogEventWrap::~LogEventWrap() {
        m_event->getLogger()->log(m_event->getLevel(), m_event); // 把自己写入日志
    }
    
    std::stringstream& LogEventWrap::getSS() {
        return m_event->getSS();
    }
    

    有一个指向event的智能指针成员,一个获得日志打印输出的函数getSS(),和获得event的getEvent函数。

  • #define SYLAR_LOG_LEVEL(logger, level) \
        if(logger->getLevel() <= level) \
            sylar::LogEventWrap(sylar::LogEvent::ptr(new sylar::LogEvent(logger,level, \
                __FILE__,__LINE__,0,sylar::GetTreadId(), \
                sylar::GetFiberId(), time(0)))).getSS()
    
    
    #define SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)
    #define SYLAR_LOG_INFO(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::INFO)
    #define SYLAR_LOG_WARN(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::WARN)
    #define SYLAR_LOG_ERROR(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::ERROR)
    #define SYLAR_LOG_FATAL(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::FATAL)
    

    宏函数里面的代码就是之前我们在test.cc初始化一个日志器使用的方法。

p8

1.优化日志输出-格式化输出

我们在定义日志时,每次传入的格式模板可能不同,在把日志存入文件时,过滤的条件也可能不同,所以sylar继续使用宏函数允许我们自定义传入格式模板

// 格式化输出
#define SYLAR_LOG_FMT_LEVEL(logger, level, fmt, ...) \
    if(logger->getLevel() <= level) \
        sylar::LogEventWrap(sylar::LogEvent::ptr(new sylar::LogEvent(logger, level, \
                        __FILE__, __LINE__, 0, sylar::GetTreadId(),\
                sylar::GetFiberId(), time(0)))).getEvent()->format(fmt, __VA_ARGS__)

#define SYLAR_LOG_FMT_DEBUG(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::DEBUG, fmt, __VA_ARGS__)
#define SYLAR_LOG_FMT_INFO(logger, fmt, ...)  SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::INFO, fmt, __VA_ARGS__)
#define SYLAR_LOG_FMT_WARN(logger, fmt, ...)  SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::WARN, fmt, __VA_ARGS__)
#define SYLAR_LOG_FMT_ERROR(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::ERROR, fmt, __VA_ARGS__)
#define SYLAR_LOG_FMT_FATAL(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::FATAL, fmt, __VA_ARGS__)

下面详细解析:

  • void format(const char* fmt, ...); // [?]
    void format(const char* fmt, va_list al);
    
    void LogEvent::format(const char* fmt, ...) {
        va_list al;
        va_start(al, fmt);  //引入stdarg.h
        format(fmt, al);
        va_end(al);
    }
    
    
    void LogEvent::format(const char* fmt, va_list al) {
        char* buf = nullptr;
        int len = vasprintf(&buf, fmt, al);
        if(len != -1) {
            m_ss << std::string(buf, len);
            free(buf);
        }
    }
    
    

    在LogEvent中新增两个函数,用于自定义模板

    第一个函数用了可变参数的方法,…的内容可以在输出时用宏定义__VA_ARGS替代。函数体中还使用了va_list,首先定义了一个变量指向va_list,然后使用方法va_start初始化al,使其指向第一个可变参数的地址,接着传入第二个format函数,处理完毕后使用va_end结束可变参数的获取

    sylar高性能服务器-日志(P7-P8)代码解析_第1张图片

    第二个函数传入格式模板fmt,指向可变参数的va_list对象al,创建一个缓冲区buf,使用va_list对像里面的vasprintf方法将格式化数据从可变参数列表写入缓冲区,如果写入的值不为空,则保存到LogEvent的成员m_ss中

2.日志管理器

新增LoggerManager,对日志进行管理

//log.h
// 日志管理器
class LoggerManager {
public:
    LoggerManager();
    Logger::ptr getLogger(const std::string& name);
private:
    std::map<std::string, Logger::ptr> m_loggers;    // 日志器容器
    Logger::ptr m_root;                             // 主日志器
};

// 日志器管理类单例模型
typedef sylar::Singleton<LoggerManager> LoggerMgr;

//log.cc
LoggerManager::LoggerManager() {
    m_root.reset(new Logger);
    m_root->addAppender(LogAppender::ptr(new StdoutAppender));
}

Logger::ptr LoggerManager::getLogger(const std::string& name) {
    auto it = m_loggers.find(name);
    return it == m_loggers.end() ? m_root : it->second;
}

构造函数会初始化一个默认日志器,getLogger会在日志管理器中寻找目标日志

3.单例模型设计

这一块第一次遇到,目前不咋懂,先占个位,后面再记录

#ifndef __SINGLETON_H__
#define __SINGLETON_H__

namespace sylar {

template<class T, class X = void, int N = 0> // T 类型 X 为了创造多个实例对应的Tag N 同一个Tag创造多个实例索引
class Singleton {
public:
    static T* GetInstance() {
        static T v;
        return &v;
    }
};

template<class T, class X = void, int N = 0>
class SingletonPtr {
public:
    static std::shared_ptr<T> GetInstance() {
        static std::shared_ptr<T> v(new T);
        return v;
    }
};

}

#endif

测试(无调试步骤)

贴一下当前代码的测试案例

test.cc

#include 
#include "../sylar/log.h"
#include "../sylar/util.h"

int main(int argc, char** argv) {
    sylar::Logger::ptr logger(new sylar::Logger);
    logger->addAppender(sylar::LogAppender::ptr(new sylar::StdoutAppender)); 
    
    sylar::FileLogAppender::ptr file_appender(new sylar::FileLogAppender("./log.txt"));
    sylar::LogFormatter::ptr fmt(new sylar::LogFormatter("%d%T%m%n"));
    file_appender->setFormatter(fmt);
    file_appender->setLevel(sylar::LogLevel::FATAL); // 过滤特定级别level
    logger->addAppender(file_appender);


    SYLAR_LOG_DEBUG(logger) << "asdasda";
    SYLAR_LOG_FMT_FATAL(logger, "fmt eeror %s", "hy");

    auto l = sylar::LoggerMgr::GetInstance()->getLogger("xx");
    SYLAR_LOG_INFO(l) << "XX";
    return 0;
}

结果

image-20231013102440006

生成的日志文件:

el::FATAL); // 过滤特定级别level
logger->addAppender(file_appender);

SYLAR_LOG_DEBUG(logger) << "asdasda";
SYLAR_LOG_FMT_FATAL(logger, "fmt eeror %s", "hy");

auto l = sylar::LoggerMgr::GetInstance()->getLogger("xx");
SYLAR_LOG_INFO(l) << "XX";
return 0;

}


**结果**

[外链图片转存中...(img-B5hsKY6Q-1697164453590)]

生成的日志文件:

![image-20231013102459920](https://img-blog.csdnimg.cn/img_convert/739a84f3f35b4305e9905fd195a1a68f.png)

你可能感兴趣的:(服务器,服务器,c++,算法)