准备:认识Log4J 日志

出处:《百度百科》
概念:Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。这些功能可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

· 类关系

日志系统开发(一) - 图1

1. LogLevel 类封装

目的:标识日志器、日志输出器的格式,只有满足一定的层级关系,日志输出器才能输出相应日志内容。能够区分一些日志信息的重要程度,在开发调试以及线上可以有选择的显示日志内容。
UNKNOW < DEBUG < INFO < WARN < ERROR < FATAL

  • 日志级别:=DEBUG,输出器级别:<= DEBUG 此时可以输出日志内容

  • 日志级别:=DEBUG,输出器级别:= INFO

此时不能输出日志内容,只能输出级别为INFO及其以上的日志

1.1 成员变量

  1. class LogLevel
  2. {
  3. ...
  4. ...
  5. public:
  6. //日志级别
  7. enum Level{
  8. UNKNOW = 0, //未知信息
  9. DEBUG = 1, //调试信息
  10. INFO = 2, //一般信息
  11. WARN = 3, //警告信息
  12. ERROR = 4, //一般错误
  13. FATAL = 5 //致命错误
  14. };
  15. }

1.2 接口

1.2.1 ToString()

功能:日志级别从枚举类型转为字符串

  1. /**
  2. * @brief 从枚举类型转为字符串
  3. * @param[in] level 日志级别枚举类型
  4. * @return const char*
  5. */
  6. static const char* ToString(LogLevel::Level level);
  7. const char* LogLevel::ToString(LogLevel::Level level)
  8. {
  9. /*采用宏替换能减少重复代码*/
  10. switch(level)
  11. {
  12. #define XX(name)\
  13. case LogLevel::name:\
  14. return #name;\
  15. break;
  16. XX(DEBUG);
  17. XX(INFO);
  18. XX(WARN);
  19. XX(FATAL);
  20. XX(ERROR);
  21. #undef XX //用完一个宏XX 希望下面的代码不再使用到它
  22. default:
  23. return "UNKNOW";
  24. }
  25. return "UNKNOW";
  26. }

1.2.2 FromString()

功能:日志级别从字符串转为日志枚举类型

  1. /**
  2. * @brief 从字符串转为日志枚举类型
  3. * @param[in] val 日志级别字符串
  4. * @return LogLevel::Level
  5. */
  6. static LogLevel::Level FromString(const std::string& val);
  7. LogLevel::Level LogLevel::FromString(const std::string& val)
  8. {
  9. #define XX(name)\
  10. if(strcasecmp(val.c_str(), #name) == 0)\
  11. {\
  12. return LogLevel::name;\
  13. }
  14. //解决小写不识别
  15. XX(DEBUG);
  16. XX(INFO);
  17. XX(WARN);
  18. XX(FATAL);
  19. XX(ERROR);
  20. #undef XX
  21. return LogLevel::UNKNOW;
  22. }

2. LoggerAppender 类封装 (接口类)

* 成员变量

  1. class LogAppender
  2. {
  3. ...
  4. ...
  5. protected:
  6. //输出器日志级别
  7. LogLevel::Level m_level;
  8. //日志格式器智能指针
  9. LogFormatter::ptr m_formatter;
  10. //互斥锁
  11. MutexType m_mutex;
  12. //是否设置过日志格式
  13. bool is_set_formatter = false;
  14. };

* 接口

1. 虚接口

1.1 log()

功能:日志输出的多态接口。通过该多态接口,会去调用具体子类中的log()函数执行输出动作。

  1. /**
  2. * @brief 日志输出的多态接口
  3. * @param[in] logger 传入的日志器
  4. * @param[in] level 日志级别
  5. * @param[in] event 日志属性
  6. */
  7. virtual void log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) = 0;

1.2 toYamlString()

功能:日志输出器信息以yaml格式用字符串输出,多态接口

  1. /**
  2. * @brief 日志输出器信息以yaml格式用字符串输出 多态接口
  3. * @return std::string
  4. */
  5. virtual std::string toYamlString() = 0;

2. 常用接口

  1. /**
  2. * @brief 设置日志输出器输出格式
  3. * @param[in] val 具体格式
  4. */
  5. void setFormatter(LogFormatter::ptr val);
  6. void LogAppender::setFormatter(LogFormatter::ptr val)
  7. {
  8. MutexType::Lock lock(m_mutex);
  9. m_formatter = val;
  10. //传入的日志格式器不为nullptr 该输出器被设置过格式
  11. if(m_formatter)
  12. is_set_formatter = true;
  13. else
  14. is_set_formatter = false;
  15. }
  16. /**
  17. * @brief 获取日志输出器输出格式
  18. * @return LogFormatter::ptr
  19. */
  20. LogFormatter::ptr getFormatter();
  21. LogFormatter::ptr LogAppender::getFormatter()
  22. {
  23. MutexType::Lock lock(m_mutex);
  24. return m_formatter;
  25. }
  26. /**
  27. * @brief 获取日志输出器的日志级别
  28. * @return LogLevel::Level
  29. */
  30. LogLevel::Level getLevel() const {return m_level;}
  31. /**
  32. * @brief 设置日志输出器的日志级别
  33. * @param val
  34. */
  35. void setLevel(LogLevel::Level val){m_level = val; }
  36. /**
  37. * @brief 设置是否设置过日志格式
  38. */
  39. void setIsFormatter() {is_set_formatter = true;}
  40. /**
  41. * @brief 获取是否设置过日志格式
  42. * @return true 设置过
  43. * @return false 没有设置过
  44. */
  45. bool getIsFormatter() {return is_set_formatter;}

2.1 StdoutLogAppender 输出到控制台类 具体子类

2.1.1 重写接口

2.1.1.1 log()

  1. /**
  2. * @brief 输出日志内容到控制台显示
  3. * @param[in] logger 具体日志器
  4. * @param[in] level 日志级别
  5. * @param[in] event 日志属性
  6. */
  7. void log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override;
  8. void StdoutLogAppender::log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event)
  9. {
  10. if(level >= m_level)
  11. {
  12. //std::cout 不是线程安全的
  13. //锁m_formatter
  14. MutexType::Lock lock(m_mutex);
  15. std::cout << m_formatter->format(logger, level, event);
  16. }
  17. }

2.1.1.2 toYamlString()

功能:将日志器输出器信息以yaml格式用字符串输出

  1. /**
  2. * @brief 将日志器输出器信息以yaml格式用字符串输出
  3. * @return std::string
  4. */
  5. std::string toYamlString() override;
  6. std::string StdoutLogAppender::toYamlString()
  7. {
  8. //锁m_formatter
  9. MutexType::Lock lock(m_mutex);
  10. YAML::Node node;
  11. node["type"] = "StdoutLogAppender";
  12. node["level"] = LogLevel::ToString(m_level);
  13. //如果输出器单独设置了格式 也要显示
  14. if(m_formatter && getIsFormatter())
  15. node["formatter"] = m_formatter->getPattern();
  16. std::stringstream ss;
  17. ss << node;
  18. return ss.str();
  19. }

2.2 FileLogAppender 输出到文件类 具体子类

2.2.1 成员变量

  1. class FileLogAppender: public LogAppender
  2. {
  3. ...
  4. ...
  5. private:
  6. //输出文件路径
  7. std::string m_filename;
  8. //输出的文件流
  9. std::ofstream m_filestream;
  10. //上一次打开文件的时间
  11. uint64_t m_last_time = 0;
  12. };

2.2.2 接口

2.2.2.1 构造函数

  1. /**
  2. * @brief 输出到文件的日志输出器类构造函数
  3. * @param[in] filename 输出到的具体文件路径
  4. */
  5. FileLogAppender(const std::string& filename);
  6. FileLogAppender::FileLogAppender(const std::string& filename)
  7. :m_filename(filename)
  8. {
  9. //初始化时间结构体
  10. memset(&tv_cur, 0, sizeof(struct timeval));
  11. //将当前文件重打开一次
  12. reopen();
  13. }

2.2.2.2 reopen()

功能:文件重打开。在多线程情况下,有可能当前我们操作的这个文件,已经被别的线程关闭,当前线程操作时候需要重新以追加内容的方式打开文件。

  1. /**
  2. * @brief 文件重打开
  3. * @return true 文件重打开成功
  4. * @return false 文件重打开失败
  5. */
  6. bool reopen();
  7. bool FileLogAppender::reopen()
  8. {
  9. //锁 文件句柄
  10. MutexType::Lock lock(m_mutex);
  11. if(m_filestream)
  12. {
  13. m_filestream.close();
  14. }
  15. //以追加方式打开文件
  16. m_filestream.open(m_filename, std::ios::app);
  17. return !m_filestream;
  18. }

2.2.2.3 重写接口

1). log()

功能:输出日志内容到文件中。选择每间隔1s重打开文件,由于过分频繁的重打开应该会有较大的开销

  1. /**
  2. * @brief 输出日志内容到文件中
  3. * @param[in] logger 具体日志器
  4. * @param[in] level 日志级别
  5. * @param[in] event 日志属性
  6. */
  7. void log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override;
  8. void FileLogAppender::log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event)
  9. {
  10. if(level >= m_level)
  11. {
  12. //获取当前的时间
  13. uint64_t now = time(0);
  14. if(now != m_last_time) //这个!=不等于意味着 每过1s就重打开一次
  15. {
  16. reopen();
  17. m_last_time = now;
  18. }
  19. MutexType::Lock lock(m_mutex);
  20. m_filestream << m_formatter->format(logger, level, event);;
  21. }
  22. }

2). toYamlString()

功能:将日志器输出器以yaml格式用字符串输出

  1. /**
  2. * @brief 将日志器输出器以yaml格式用字符串输出
  3. * @return std::string
  4. */
  5. std::string toYamlString() override;
  6. std::string FileLogAppender::toYamlString()
  7. {
  8. //锁m_formatter
  9. MutexType::Lock lock(m_mutex);
  10. YAML::Node node;
  11. node["type"] = "FileLogAppender";
  12. node["file"] = m_filename;
  13. node["level"] = LogLevel::ToString(m_level);
  14. if(m_formatter && getIsFormatter())
  15. node["formatter"] = m_formatter->getPattern();
  16. std::stringstream ss;
  17. ss << node;
  18. return ss.str();
  19. }

3. Logger 类封装

3.1 成员变量

  1. class Logger :public std::enable_shared_from_this<Logger>
  2. {
  3. ....
  4. ....
  5. private:
  6. //日志器名字
  7. std::string m_name;
  8. //日志级别
  9. LogLevel::Level m_level;
  10. //日志输出器的指针队列
  11. std::list<LogAppender::ptr> m_appenders;
  12. //互斥锁
  13. MutexType m_mutex;
  14. //Logger自带的一个LogFormatter 防止LogAppender没有LogFormatter
  15. LogFormatter::ptr m_formatter;
  16. //改动:设置默认Logger
  17. Logger::ptr default_root;
  18. };

3.2 接口

3.2.1 构造函数

  1. /**
  2. * @brief 日志器类构造函数,日志器默认级别为DEBUG
  3. * @param[in] name 日志器的名称 默认为"root"
  4. */
  5. Logger(const std::string &name = "root");
  6. Logger::Logger(const std::string &name)
  7. :m_name(name), m_level(LogLevel::DEBUG)
  8. {
  9. //在生成Logger时候会生成一个默认的LogFormatter
  10. m_formatter.reset(new LogFormatter("[%p]%T<%f:%l>%T%d{%Y-%m-%d %H:%M:%S}%T%t(%tn)%T%c%T%g%T%m%n"));
  11. }

3.2.2 log()(核心)

功能:输出日志内容。通过组合LogAppender对象实现实际的日志内容输出

  1. /**
  2. * @brief 输出日志内容
  3. * @param[in] level 日志级别
  4. * @param[in] event 日志属性
  5. */
  6. void log(LogLevel::Level level, const LogEvent::ptr event);
  7. void Logger::log(LogLevel::Level level, const LogEvent::ptr event)
  8. {
  9. //如果传入的日志级别 >= 当前日志器的级别都能进行输出
  10. if(level >= m_level)
  11. {
  12. //拿到this指针的智能指针
  13. auto self = shared_from_this();
  14. if(m_appenders.size())
  15. {
  16. //操作日志输出器的时候要独占 锁住
  17. MutexType::Lock lock(m_mutex);
  18. for(auto &x : m_appenders)
  19. {
  20. //调用的是appender中的log()进行实际输出
  21. //有点类似观察者模式
  22. x->log(self, level, event);
  23. }
  24. }
  25. else if(default_root) //如果当前的Logger没有分配LogAppender就使用default_root中的LogAppender来打印
  26. {
  27. //是一层递归 使用default_root中的日志输出器来打印
  28. default_root->log(level, event);
  29. }
  30. }
  31. }

· 以log()为基础的扩展便捷接口

  1. /**
  2. * @brief DUBEG级别输出日志内容
  3. * @param[in] event 日志属性
  4. */
  5. void debug(LogEvent::ptr event);
  6. void Logger::debug(LogEvent::ptr event)
  7. {
  8. log(LogLevel::DEBUG, event);
  9. }
  10. /**
  11. * @brief INFO级别输出日志内容
  12. * @param[in] event 日志属性
  13. */
  14. void info(LogEvent::ptr event);
  15. void Logger::info(LogEvent::ptr event)
  16. {
  17. log(LogLevel::INFO, event);
  18. }
  19. /**
  20. * @brief WARN级别输出日志内容
  21. * @param[in] event 日志属性
  22. */
  23. void warn(LogEvent::ptr event);
  24. void Logger::warn(LogEvent::ptr event)
  25. {
  26. log(LogLevel::WARN, event);
  27. }
  28. /**
  29. * @brief ERROR级别输出日志内容
  30. * @param[in] event 日志属性
  31. */
  32. void error(LogEvent::ptr event);
  33. void Logger::error(LogEvent::ptr event)
  34. {
  35. log(LogLevel::ERROR, event);
  36. }
  37. /**
  38. * @brief FATAL级别输出日志内容
  39. * @param[in] event 日志属性
  40. */
  41. void fatal(LogEvent::ptr event);
  42. void Logger::fatal(LogEvent::ptr event)
  43. {
  44. log(LogLevel::FATAL, event);
  45. }

3.2.3 addAppender()

功能:添加日志输出器

  1. /**
  2. * @brief 添加日志输出器
  3. * @param[in] appender 具体的日志输出器
  4. */
  5. void addAppender(LogAppender::ptr appender);
  6. void Logger::addAppender(LogAppender::ptr appender)
  7. {
  8. //加锁
  9. MutexType::Lock lock(m_mutex);
  10. //发现加入的日志输出器没有格式器的话 赋予默认格式器
  11. if(!appender->getFormatter())
  12. {
  13. //锁 LogAppender中的LogFormatter
  14. appender->setFormatter(m_formatter);
  15. }
  16. m_appenders.push_back(appender);
  17. }

3.2.4 delAppender()

功能:删除日志输出器

  1. /**
  2. * @brief 删除日志输出器
  3. * @param[in] appender 具体的日志输出器
  4. */
  5. void delAppender(LogAppender::ptr appender);
  6. void Logger::delAppender(LogAppender::ptr appender)
  7. {
  8. //锁 m_appenders
  9. MutexType::Lock lock(m_mutex);
  10. auto it = find(m_appenders.begin(), m_appenders.end(), appender);
  11. m_appenders.erase(it);
  12. /*等价*/
  13. //遍历的方式删除
  14. // for(auto it = m_appenders.begin();it != m_appenders.end();it++)
  15. // {
  16. // if(*it == appender)
  17. // {
  18. // m_appenders.erase(it);
  19. // break;
  20. // }
  21. // }
  22. }

3.2.5 clearAppender()

功能:清空日志输出队列

  1. /**
  2. * @brief 清空日志输出队列
  3. */
  4. void clearAppender();
  5. void Logger::clearAppender()
  6. {
  7. MutexType::Lock lock(m_mutex);
  8. m_appenders.clear();
  9. }

3.2.6 setFormatter()

功能:设置日志器格式。检查所有日志输出器,如果没有设置过格式,就和当前日志器的格式同步。

  1. /**
  2. * @brief 设置日志器格式
  3. * @param[in] val 具体格式字符串
  4. */
  5. void setFormatter(const std::string& val);
  6. void Logger::setFormatter(const std::string& val)
  7. {
  8. //解析模板并创建新的日志格式器
  9. LogFormatter::ptr new_value(new LogFormatter(val));
  10. //模板解析是否有错误
  11. if(new_value->isError())
  12. {
  13. std::cout << "Logger setFormatter name=" << m_name
  14. << "value=" << val << "invaild formatter" << std::endl;
  15. return;
  16. }
  17. //加锁
  18. MutexType::Lock lock(m_mutex);
  19. m_formatter = new_value;
  20. //将所有LogAppender 没有设置过格式的formatter和Logger同步
  21. for(auto &x : m_appenders)
  22. {
  23. if(!x->getIsFormatter())
  24. x->setFormatter(new_value);
  25. }
  26. }

3.2.7 getFormatter()

功能:获取日志器格式

  1. /**
  2. * @brief 获取日志器格式
  3. * @return LogFormatter::ptr
  4. */
  5. LogFormatter::ptr getFormatter();
  6. LogFormatter::ptr Logger::getFormatter()
  7. {
  8. MutexType::Lock lock(m_mutex);
  9. return m_formatter;
  10. }

3.2.8 toYamlString()

功能:将日志器信息以yaml格式用字符串输出

  1. /**
  2. * @brief 将日志器信息以yaml格式用字符串输出
  3. * @return std::string
  4. */
  5. std::string toYamlString();
  6. std::string Logger::toYamlString()
  7. {
  8. //锁 m_formatter
  9. MutexType::Lock lock(m_mutex);
  10. YAML::Node node;
  11. node["name"] = m_name;
  12. node["level"] = LogLevel::ToString(m_level);
  13. node["formatter"] = m_formatter->getPattern();
  14. for(auto &x : m_appenders)
  15. {
  16. node["appender"].push_back(YAML::Load(x->toYamlString()));
  17. }
  18. //如果日志输出器队列为空 也需要标识
  19. if(!node["appender"].size())
  20. node["appender"] = "NULL";
  21. std::stringstream ss;
  22. ss << node;
  23. return ss.str();
  24. }

3.2.9 其他接口

  1. /**
  2. * @brief 获取日志器级别
  3. * @return LogLevel::Level
  4. */
  5. LogLevel::Level getLevel() const {return m_level;}
  6. /**
  7. * @brief 设置日志器级别
  8. * @param[in] val 具体日志级别
  9. */
  10. void setLevel(LogLevel::Level val){m_level = val;}
  11. /**
  12. * @brief 获取日志的名字
  13. * @return const std::string&
  14. */
  15. const std::string& getName() const {return m_name;}
  16. /**
  17. * @brief 设置默认Root日志器
  18. * @param[in] p 默认的日志器
  19. */
  20. void setDefaultRoot(Logger::ptr p) {default_root = p;}

4. LogFormatter 类封装

目的:给定日志器、日志输出器日志输出格式。在输出器没有格式的情况下,从属于日志器的格式,日志器生成时未指定格式会有默认的格式被赋予。

4.1 成员变量

  1. class LogFormatter
  2. {
  3. ...
  4. ...
  5. public:
  6. /**
  7. * @brief 实现具体格式输出的子类
  8. */
  9. class FormatItem
  10. {
  11. ...
  12. };
  13. private:
  14. //日志格式模板 对应模板解析对应内容
  15. std::string m_pattern;
  16. //具体格式输出的具体子类队列
  17. std::vector<FormatItem::ptr> m_items;
  18. //格式模板是否发生错误
  19. bool m_error = false;
  20. };

4.2 接口

4.2.1 构造函数

  1. /**
  2. * @brief 日志格式器类构造函数
  3. * @param[in] pattern 具体模板格式字符串
  4. */
  5. LogFormatter(const std::string &pattern);
  6. LogFormatter::LogFormatter(const std::string &pattern)
  7. :m_pattern(pattern)
  8. {
  9. //解析传入模板格式
  10. init();
  11. }

** 私有接口

1). init() (核心)

功能:日志格式器初始化,解析模板字符串,分析出具体要使用的模板格式

  • 字符对应的格式输出:
    1. %n-------换行符 '\n'
    2. %m-------日志内容
    3. %p-------level日志级别
    4. %r-------程序启动到现在的耗时
    5. %%-------输出一个'%'
    6. %t-------当前线程ID
    7. %T-------Tab
    8. %tn------当前线程名称
    9. %d-------日期和时间
    10. %f-------文件名
    11. %l-------行号
    12. %g-------日志器名字
    13. %c-------当前协程ID

"%d [%p] %f %l %m %n"为例子1

"%test%n" 为例子2

"[%p]%T<%f:%l>%T%d{%Y-%m-%d %H:%M:%S}%T%t(%tn)%T%c%T%g%T%m%n" 为例子3

使用vector暂存由m_pattern模板格式中分解得到的 模板内容 + 普通字符串内容

  1. std::vector<std::tuple<std::string, std::string, int> > mv;
  2. **例子1mv中包含的结果: (_表示空格 1为有效模板内容 0为无效模板内容)
  3. "_" "_" 0
  4. "d" "_" 1
  5. "_[" "_" 0
  6. "p" "_" 1
  7. "]_" "_" 0
  8. "_" "_" 0
  9. "f" "_" 1
  10. "_" "_" 0
  11. "l" "_" 1
  12. "_" "_" 0
  13. "m" "_" 1
  14. "_" "_" 0
  15. "n" "_" 1
  16. **例子2mv中包含的结果: (_表示空格 1为有效模板内容 0为无效模板内容)
  17. "_" "_" 0
  18. "test" "_" 1
  19. "_" "_" 0
  20. "n" "_" 1
  21. **例子3mv中包含的结果: (_表示空格 1为有效模板内容 0为无效模板内容)
  22. "[" "_" 0
  23. "p" "_" 1
  24. "]" "_" 0
  25. "T" "_" 1
  26. "<" "_" 0
  27. "f" "_" 1
  28. ":" "_" 0
  29. "l" "_" 1
  30. ">" "_" 0
  31. "T" "_" 1
  32. "d" "%Y-%m-%d %H:%M:%S" 1
  33. "T" "_" 1
  34. "t" "_" 1
  35. "(" "_" 0
  36. "tn" "_" 1
  37. ")" "_" 0
  38. "T" "_" 1
  39. "c" "_" 1
  40. "T" "_" 1
  41. "g" "_" 1
  42. "T" "_" 1
  43. "m" "_" 1
  44. "n" "_" 1
  • 技巧点:
  1. 利用map容器将解析出来的这些可能的模板字符串,与具体实现子类建立映射关系。
  2. 使用宏定义替换,代替传统switch....case写法,很便捷。
  3. 使用function<智能指针(传参)> = {return 实例化对象智能指针;},结合多态实现一个接口多实现。如果采用函数指针,则需要每一种函数写一个指针依次赋值。
  1. /**
  2. * @brief 日志格式器初始化,解析模板字符串
  3. */
  4. void init();
  5. void LogFormatter::init()
  6. {
  7. // 1.模板字符串 2.指定的参数字符串 3.是否为正确有效模板
  8. std::vector<std::tuple<std::string, std::string, int> > mv;
  9. //缓存和模板内容无关的普通字符串 一并加入到mv中
  10. std::string str = "";
  11. for(size_t i = 0;i < m_pattern.size();i++)
  12. {
  13. //遇到"%"前的普通字符存入到str中并且跳到下一个字符
  14. if(m_pattern[i] != '%')
  15. {
  16. str += m_pattern[i];
  17. continue;
  18. }
  19. //连续遇到两个"%%" 进行转义处理
  20. if(i + 1 < m_pattern.size() && m_pattern[i + 1] == '%')
  21. {
  22. str += '%';
  23. continue;
  24. }
  25. /*遇到'%'后开始判断是否是某种格式模板*/
  26. std::string pcahr = ""; //暂时存储相关模板代表字母
  27. std::string fmt = ""; //存储"{...}"间的模板内容
  28. size_t index = i + 1; // '%'后的第一个位置
  29. int fmt_status = 0; //有限状态机表示
  30. size_t fmt_begin = 0; // "{...}"间内容的起点
  31. while(index < m_pattern.size())
  32. {
  33. // 碰到符号H:非字符 && 非'{' && 非'}' 后将"%XXXH"紧跟串XXX存入temp中,并且break
  34. if(fmt_status == 0 && (!isalpha(m_pattern[index]) && m_pattern[index] != '{' && m_pattern[index] != '}'))
  35. {
  36. pcahr = m_pattern.substr(i + 1, index - i - 1);
  37. break;
  38. }
  39. //fmt_status表示一种有限状态机,处理括号中的内容
  40. //0 = 未遇到 '{'
  41. //1 = 已经遇到'{' , 未遇到'}'
  42. if(fmt_status == 0)
  43. {
  44. //%XXX{ 存储XXX串
  45. if(m_pattern[index] == '{')
  46. {
  47. pcahr = m_pattern.substr(i + 1, index - i - 1);
  48. fmt_status = 1;
  49. fmt_begin = index;
  50. ++index;
  51. continue;
  52. }
  53. }
  54. else if(fmt_status == 1)
  55. {
  56. // {XXXX} 存储XXX串
  57. if(m_pattern[index] == '}')
  58. {
  59. fmt = m_pattern.substr(fmt_begin + 1, index - fmt_begin - 1);
  60. fmt_status = 0;
  61. ++index;
  62. break;
  63. }
  64. }
  65. ++index;
  66. // "%XXXXXX" 说明整个字符串中只有一个"%"符
  67. if(index == m_pattern.size())
  68. {
  69. if(!pcahr.size())
  70. {
  71. pcahr = m_pattern.substr(i + 1);
  72. }
  73. }
  74. }
  75. //没有遇到'{' 或者 已经完成"{...}"的扫描
  76. if(fmt_status == 0)
  77. {
  78. //%aaaaXXX%bbbb 或者 XXX%aaaa 存储XXX
  79. if(pcahr.size())
  80. {
  81. mv.push_back(std::make_tuple(str, std::string(), 0));
  82. str.clear();
  83. }
  84. //%XXX 存储XXX 且 {YYY} 存储YYY
  85. mv.push_back(std::make_tuple(pcahr, fmt, 1));
  86. i = index - 1;
  87. }
  88. else if(fmt_status == 1) //有 { 但没有遇到 } 一定是一个错误的序列
  89. {
  90. std::cout << "pattern parse error" << m_pattern << "--" << m_pattern.substr(i) << std::endl;
  91. m_error = true;
  92. mv.push_back(std::make_tuple("<<pattern error>>", fmt, 0));
  93. }
  94. }
  95. // "%aaaXXX"存储XXX 即最后尾部为普通字符串的情况
  96. if(str.size())
  97. {
  98. mv.push_back(std::make_tuple(str, "", 0));
  99. }
  100. //将字符 和 对应的函数操作建立映射关系
  101. //可以使用函数指针完成,这里使用了函数包装器function + lambda表达式,非常方便
  102. static std::map<std::string, std::function<FormatItem::ptr(const std::string& str)> > s_format_items = {
  103. #define XX(str, C)\
  104. {#str, [](const std::string& fmt){ return FormatItem::ptr(new C(fmt));}}
  105. /*注意必须是 ',' */
  106. XX(m, MessageFormatItem),
  107. XX(p, LevelFormatItem),
  108. XX(r, ElapseFormatItem),
  109. XX(t, ThreadIdFormatItem),
  110. XX(tn, ThreadNameFormatItem),
  111. XX(d, DateTimeFormatItem),
  112. XX(l, LineFormatItem),
  113. XX(n, NewLineFormatItem),
  114. XX(f, FileNameFormatItem),
  115. XX(T, TabFormatItem),
  116. XX(c, CoroutineIdFormatItem),
  117. XX(g, LogNameFormatItem)
  118. //XX(test, TestFormatItem),
  119. #undef XX
  120. };
  121. for(auto &x : mv)
  122. {
  123. //元组中标志int = 0 说明不是模板内容 构造为普通字符串对象
  124. if(std::get<2>(x) == 0)
  125. {
  126. //构造输出文本内容的子类对象
  127. m_items.push_back(FormatItem::ptr(new StringFormatItem(std::get<0>(x))));
  128. }
  129. else
  130. {
  131. auto it = s_format_items.find(std::get<0>(x));
  132. //如果mv中 存储的<0>位置的字符串 无法与map中的映射关系对应,说明存储了一个错误的字符模板
  133. if(it == s_format_items.end())
  134. {
  135. //构造输出文本内容的子类对象 输出一下错误
  136. m_items.push_back(FormatItem::ptr(new StringFormatItem("<<error_format %" + std::get<0>(x) + ">>")));
  137. m_error = true;
  138. }
  139. else
  140. {
  141. //给对应的子类构造传参<1>位置的fmt字符串
  142. m_items.push_back(it->second(std::get<1>(x)));
  143. }
  144. }
  145. }
  146. }

4.2.2 format()(核心)

功能:输出具体格式要求内容,经过解析后会按照既定的合格式输出日志内容。通过调用FormatItem接口下实现的各个子类的format()完成日志内容的迭代处理。
效果演示:

  1. **调用**:
  2. LogEvent::format("This is vsprintf test:%s %d\n", "string", 1024);
  3. **过程**:
  4. LogEvent::format(const char* fmt, ...)
  5. |
  6. | "string"1024------>va_list
  7. |
  8. LogEvent::format(const char* fmt, va_list al)
  9. buf"This is vsprintf test:string 1024"
  1. /**
  2. * @brief 输出具体格式要求内容
  3. * @param[in] logger 具体日志器
  4. * @param[in] level 具体日志级别
  5. * @param[in] event 具体日志属性
  6. * @return std::string
  7. */
  8. std::string format(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event);
  9. std::string LogFormatter::format(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event)
  10. {
  11. std::stringstream ss;
  12. for(auto &x : m_items)
  13. {
  14. x->format(ss, logger, level, event);
  15. }
  16. return ss.str();
  17. }

4.3 FormatItem 类封装(接口类)(核心)

目的:设置一个接口,能够由不同子类实现该接口,从而满足多种日志格式的需求

* 虚接口

  1. /**
  2. * @brief 输出具体格式要求内容
  3. * @param[in] os 标准输出流
  4. * @param[in] logger 具体日志器
  5. * @param[in] level 具体日志级别
  6. * @param[in] event 具体日志属性
  7. */
  8. virtual void format(std::ostream &os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) = 0;

4.3.1 输出日志器名称 子类

  1. /**
  2. * @brief 输出日志名
  3. */
  4. class LogNameFormatItem: public LogFormatter::FormatItem
  5. {
  6. public:
  7. //该构造函数为了和基类保持一致 无作用
  8. LogNameFormatItem(const std::string& str = ""){}
  9. void format(std::ostream &os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override
  10. {
  11. os << event->getLogger()->getName();
  12. }
  13. };

4.3.2 输出线程ID 子类

  1. /**
  2. * @brief 输出线程ID
  3. */
  4. class ThreadIdFormatItem: public LogFormatter::FormatItem
  5. {
  6. public:
  7. ThreadIdFormatItem(const std::string& str = ""){}
  8. void format(std::ostream &os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override
  9. {
  10. os << event->getThreadId();
  11. }
  12. };

4.3.3 输出线程名 子类

  1. /**
  2. * @brief 输出线程名
  3. */
  4. class ThreadNameFormatItem: public LogFormatter::FormatItem
  5. {
  6. public:
  7. ThreadNameFormatItem(const std::string& str = ""){}
  8. void format(std::ostream &os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override
  9. {
  10. os << event->getThreadName();
  11. }
  12. };

4.3.4 输出协程ID 子类

  1. /**
  2. * @brief 输出协程ID
  3. */
  4. class CoroutineIdFormatItem: public LogFormatter::FormatItem
  5. {
  6. public:
  7. CoroutineIdFormatItem(const std::string& str = ""){}
  8. void format(std::ostream &os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override
  9. {
  10. os << event->getCoroutineId();
  11. }
  12. };

4.3.5 输出日期时间 子类

  1. /**
  2. * @brief 输出日期时间
  3. */
  4. class DateTimeFormatItem: public LogFormatter::FormatItem
  5. {
  6. public:
  7. DateTimeFormatItem(const std::string& format = "%Y-%m-%d %H:%M:%S")
  8. :m_format(format)
  9. {
  10. if(!m_format.size())
  11. m_format = "%Y-%m-%d %H:%M:%S";
  12. }
  13. void format(std::ostream &os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override
  14. {
  15. time_t t = (time_t)(event->getTime());
  16. struct tm tm;
  17. char s[100];
  18. tm = *localtime(&t);
  19. strftime(s, sizeof(s), m_format.c_str(), &tm);
  20. os << s;
  21. }
  22. private:
  23. //时间显示格式
  24. std::string m_format;
  25. };

4.3.6 输出行号 子类

  1. /**
  2. * @brief 输出行号
  3. */
  4. class LineFormatItem: public LogFormatter::FormatItem
  5. {
  6. public:
  7. LineFormatItem(const std::string& str = ""){}
  8. void format(std::ostream &os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override
  9. {
  10. os << event->getLine();
  11. }
  12. };

4.3.7 输出换行符 子类

  1. /**
  2. * @brief 输出换行符
  3. */
  4. class NewLineFormatItem: public LogFormatter::FormatItem
  5. {
  6. public:
  7. NewLineFormatItem(const std::string& str = ""){}
  8. void format(std::ostream &os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override
  9. {
  10. os << std::endl;
  11. }
  12. };

4.3.8 输出文件名 子类

  1. /**
  2. * @brief 输出文件名
  3. */
  4. class FileNameFormatItem: public LogFormatter::FormatItem
  5. {
  6. public:
  7. FileNameFormatItem(const std::string& str = ""){}
  8. void format(std::ostream &os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override
  9. {
  10. os << event->getFileName();
  11. }
  12. };

4.3.9 输出文本内容 子类

  1. /**
  2. * @brief 输出文本内容
  3. */
  4. class StringFormatItem: public LogFormatter::FormatItem
  5. {
  6. public:
  7. StringFormatItem(const std::string& m_s)
  8. :m_string(m_s){}
  9. void format(std::ostream &os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override
  10. {
  11. os << m_string;
  12. }
  13. private:
  14. //文本内容
  15. std::string m_string;
  16. };

4.3.10 输出一个Tab键 子类

  1. /**
  2. * @brief 输出一个Tab键
  3. */
  4. class TabFormatItem: public LogFormatter::FormatItem
  5. {
  6. public:
  7. TabFormatItem(const std::string& str = ""){}
  8. void format(std::ostream &os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override
  9. {
  10. os << "\t";
  11. }
  12. };

5. LogEvent 类封装

目的:记录要输出的日志内容来自某一文件、来自某一函数、线程ID、所属日志器、日志级别等信息

5.1 成员变量

  1. class LogEvent
  2. {
  3. ...
  4. ...
  5. private:
  6. //文件名
  7. const char* m_file = nullptr;
  8. //行号
  9. int32_t m_line = 0;
  10. //程序启动到现在的毫秒数
  11. uint32_t m_elapse = 0;
  12. //线程ID
  13. uint32_t m_threadid = 0;
  14. //线程名称
  15. std::string m_thread_name = "";
  16. //协程ID
  17. uint32_t m_coroutineid = 0;
  18. //日志时间戳
  19. uint64_t m_time;
  20. //日志内容
  21. std::stringstream m_content;
  22. //所属日志器
  23. std::shared_ptr<Logger> m_logger;
  24. //日志级别
  25. LogLevel::Level m_level;
  26. };

5.2 接口

5.2.1 构造函数

  1. /**
  2. * @brief 日志属性类构造函数
  3. * @param[in] logger 具体日志器
  4. * @param[in] level 具体日志级别
  5. * @param[in] file 从哪一个文件输出
  6. * @param[in] m_line 从哪一行输出
  7. * @param[in] elapse 程序启动后总共时间 单位ms
  8. * @param[in] thread_id 线程号
  9. * @param[in] thread_name 线程名称
  10. * @param[in] coroutine_id 协程号
  11. * @param[in] time 日志输出时间
  12. */
  13. LogEvent(std::shared_ptr<Logger> logger, LogLevel::Level level, const char* file,
  14. int32_t m_line, uint32_t elapse, uint32_t thread_id,
  15. const std::string& thread_name, uint32_t coroutine_id, uint64_t time);
  16. LogEvent::LogEvent(std::shared_ptr<Logger> logger, LogLevel::Level level, const char* file,
  17. int32_t line, uint32_t elapse, uint32_t thread_id,
  18. const std::string& thread_name, uint32_t coroutine_id, uint64_t time)
  19. :m_file(file),
  20. m_line(line),
  21. m_elapse(elapse),
  22. m_threadid(thread_id),
  23. m_thread_name(thread_name),
  24. m_coroutineid(coroutine_id),
  25. m_time(time),
  26. m_logger(logger),
  27. m_level(level)
  28. {
  29. }

5.2.2 常用接口

  1. /**
  2. * @brief 获取文件名称
  3. * @return const char*
  4. */
  5. const char* getFileName() const {return m_file;}
  6. /**
  7. * @brief 获取行号
  8. * @return int32_t
  9. */
  10. int32_t getLine() const {return m_line;}
  11. /**
  12. * @brief 获取程序启动后总共时间 单位ms
  13. * @return uint32_t
  14. */
  15. uint32_t getElapse() const {return m_elapse;}
  16. /**
  17. * @brief 获取线程号
  18. * @return uint32_t
  19. */
  20. uint32_t getThreadId() const {return m_threadid;}
  21. /**
  22. * @brief 获取线程名称
  23. * @return const std::string&
  24. */
  25. const std::string& getThreadName() const {return m_thread_name;}
  26. /**
  27. * @brief 获取协程号
  28. * @return uint32_t
  29. */
  30. uint32_t getCoroutineId() const {return m_coroutineid;}
  31. /**
  32. * @brief 获取输出时间
  33. * @return uint64_t
  34. */
  35. uint64_t getTime() const {return m_time;}
  36. /**
  37. * @brief 获取日志内容,以字符串返回
  38. * @return std::string
  39. */
  40. std::string getContent() const {return m_content.str();}
  41. /**
  42. * @brief 获取日志内容,以流返回(能加const修饰,需要不断的对"流"内容进行追加和修改)
  43. * @return std::stringstream&
  44. */
  45. std::stringstream& getSS() {return m_content;}
  46. /**
  47. * @brief 获取日志器
  48. * @return std::shared_ptr<Logger>
  49. */
  50. std::shared_ptr<Logger> getLogger() const {return m_logger;}
  51. /**
  52. * @brief 获取日志级别
  53. * @return LogLevel::Level
  54. */
  55. LogLevel::Level getLevel() const {return m_level;}

5.2.3 format()(核心)

功能:实现类似printf("%d", 6);这样的功能,让日志内容能够带上参数。

  1. /**
  2. * @brief 自定义日志模板格式
  3. * @param[in] fmt 传入的具体日志模板
  4. * @param[in] ... 可变参数
  5. */
  6. void format(const char *fmt, ...);
  7. void LogEvent::format(const char* fmt, ...)
  8. {
  9. va_list al;
  10. va_start(al, fmt);
  11. format(fmt, al);
  12. va_end(al);
  13. }
  14. /**
  15. * @brief 自定义日志模板格式
  16. * @param[in] fmt 传入的具体日志模板
  17. * @param[in] al 可变参数列表
  18. */
  19. void format(const char *fmt, va_list al);
  20. //传入已经准备好的可变参数 进行一个组合的得到的结果放在buf中
  21. //并且构造一个string对象以"流"的方式送入到stringstream中去作为日志内容
  22. void LogEvent::format(const char* fmt, va_list al)
  23. {
  24. char *buf = nullptr;
  25. int len = vasprintf(&buf, fmt, al);
  26. //vasprintf会动态分配内存 失败返回-1
  27. if(len != -1)
  28. {
  29. m_content << std::string(buf, len);
  30. free(buf);
  31. }
  32. }

6. LogEventWrap 类封装

目的:封装便捷的宏函数调用时,无法使得LogEvent对象正常析构。封装一层包装器,在其析构函数中去实现原本想实现的功能,而又不产生内存泄漏。使用了一个LogEventWrap的匿名对象,由于匿名对象的生命周期只有一行,因此会马上回收空间触发析构函数。析构函数中就有对应日志打印语句:Logger::log()

6.1 成员变量

class LogEventWrap
{
    ...
private:
    //日志属性对象智能指针
    LogEvent::ptr m_event;
};

6.2 接口

6.2.1 构造函数

/**
 * @brief 日志属性包装器类构造函数
 * @param[in] event 日志属性对象智能指针
 */
LogEventWrap(LogEvent::ptr event);

LogEventWrap::LogEventWrap(LogEvent::ptr event)
    :m_event(event)
{

}

6.2.2 析构函数 (核心)

/**
 * @brief 日志属性包装器类析构函数
 */
~LogEventWrap();

LogEventWrap::~LogEventWrap()
{
    //通过日志属性对象中日志器去打印日志内容
    m_event->getLogger()->log(m_event->getLevel(), m_event);
}

6.2.3 其他接口

/**
 * @brief 获取日志属性中的日志内容
 * @return std::stringstream& 
 */
std::stringstream& getSS();
std::stringstream& LogEventWrap::getSS()
{
    return m_event->getSS();
}

/**
 * @brief 获取日志属性智能指针
 * @return std::shared_ptr<LogEvent> 
 */
std::shared_ptr<LogEvent> getEvent();
std::shared_ptr<LogEvent> LogEventWrap::getEvent()
{
    return m_event;
}

C\C++知识点补充复习:tuple 元组和 std::get()函数

概念:tuple是多元组,pair是二元组,这是两个相似的概念。使用std::get<>(tuple)配合取出元组中对应元素的值。

tuple<c0,c1,c2,...> t   
std::get<0>(t)-----> c0类型的值
std::get<1>(t)-----> c1类型的值
std::get<2>(t)-----> c2类型的值

pair<c0, c1> p;
p.first-------->c0类型的值
p.second------->c1类型的值

C\C++知识点补充复习:strftime()函数

功能:根据格式规范 format 格式化分解时间 tm,并将结果放入大小为 max 的字符数组 s 中。格式规范是一个以 null 结尾的字符串,可能包含称为转换规范的特殊字符序列,每个字符序列都由一个 ‘%’ 字符引入,并由一些称为转换说明符字符的其他字符终止。 所有其他字符序列都是普通字符序列。

返回值: (注意返回0不一定代表错误)

  1. 如果结果字符串(包括终止空字节)不超过最大字节数,则 strftime() 返回字节数(不包括终止空字节)放在数组 s 中。
  2. 如果结果字符串的长度(包括终止的空字节)将超过最大字节,则 strftime() 返回 0,并且数组的内容未定义。
   #include <time.h>

   size_t strftime(char *s, size_t max, const char *format,
                   const struct tm *tm);

C\C++知识点补充复习:vasprintf()函数

功能:和sprintf()函数类似,但是能够接受可变参数。

#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <stdio.h>

int asprintf(char **strp, const char *fmt, ...);

int vasprintf(char **strp, const char *fmt, va_list ap);

C\C++知识点补充复习:函数宏、文件宏、行号宏

C/C++提供了三个宏FUNCTION, FILELINE定位程序运行时的错误。程序预编译时预编译器将用所在的函数名,文件名和行号替换。当运行时错误产生后这三个宏分别能返回错误所在的函数,所在的文件名和所在的行号。

getpid()/pthread_self()/syscall(SYS_gettid)区别

gettid 获取的是内核中真实线程ID, 对于多线程进程来说,每个tid实际是不一样的。

而pthread_self获取的是相对于进程的线程控制块的首地址, 只是用来描述统一进程中的不同线程,进程采用虚拟地址空间是有可能产生相同的值的。