目录
在上一篇我们介绍了 日志格式配置方式。
在 easylogging++的 功能介绍 中我们提过,easylogging++日志分两种:用户日志 和 syslog
日志,而用户日志又可分为 普通日志(分层日志) 和 verbose
日志。
在前面我们介绍过 VERBOSE 日志宏 的实现,今天我们来看看 VERBOSE
日志相关的信息是如何管理的。
VERBOSE 日志是什么?
前面我们提到的记录日志的方式大部分时候都是
分层级日志
,根据不同的日志级别(Trace
,Debug
,Fatal
,Error
,Warning
,Info
),统一对所有文件进行日志输出控制,可以单独控制某一个级别是否能进行日志输出,也可以禁用所有级别的日志输出,但也仅此而已了。
我们 没办法针对某一类型的文件进行日志输出,也就是说无法更细粒度控制日志输出的文件范围,而 VERBOSE 日志正是为了解决这个问题 。
VERBOSE
日志提供了两个属性来来进行细粒度的控制:
1) 控制日志输出作用的文件范围
2)VLevel
级别:类似于分层日志当中的日志级别
不同于分层日志当中的日志级别,VLevel
的取值范围为0-9
。
VERBOSE
日志使用模块(我这里称之为module
)的机制来将两者有机结合起来,也就是说我们可以给模块指定文件范围和对应的VLevel
。module
的格式如下:<module name>=<verbose level>
其中的
module name
就是源文件匹配规则, 支持部分正则表达式规则(支持其中的?
和*
通配符)。verboser level
是进行VERBOSE
日志输出时,针对符合这种匹配规则能够使用的最大的VLevel
级别 。
而VERBOSE
日志可以同时设定多个这样的模块,这就是VModule
。VModule
格式如下:<module1>,<module2>,<module3>... #各个模块之间用英文逗号","隔开
举个例子:
main=5,parse*=6
这里给
VModule
设置了2
个模块,分别为:
1) 模块 1:main=5
表示文件匹配规则为"main"
字符串,而对应的最大VLevel
为5
。
2) 模块 2:parse*=6
表示文件匹配规则为"parse*"
字符串,而对应的最大VLevel
为6
。如果用一句话总结什么是
VERBOSE
日志,那就是VERBOSE
日志是模块日志 。
VERBOSE 日志的实现
在 easylogging++的 总体设计 中我们曾提过:VRegistry
类是 VERBOSE 日志相关配置的管理类(保存基准 VLevel
以及 vModule
相关信息),今天我们就来详细分析一下这个类的实现。
VRegistry 类的成员变量
base::type::VerboseLevel m_level; // 当VMODULE未指定时(m_modules为空)时,用作基准的VLevel判断是否能写VERBOSE日志
base::type::EnumType *m_pFlags; // 日志库的全局的logging flag(通过构造函数的参数传递进来)
std::unordered_map<std::string, base::type::VerboseLevel> m_modules; // 保存VMODULE经过调整后的的模块信息,key-文件匹配规则,value-VLevel
VRegistry 类的成员函数
构造函数
VRegistry::VRegistry(base::type::VerboseLevel level, base::type::EnumType *pFlags) : m_level(level), m_pFlags(pFlags) { }
设置基准的 VLevel
/// @brief Sets verbose level. Accepted range is 0-9 void VRegistry::setLevel(base::type::VerboseLevel level) { base::threading::ScopedLock scopedLock(lock()); if (level > 9) m_level = base::consts::kMaxVerboseLevel; else m_level = level; }
获取基准的 VLevel
inline base::type::VerboseLevel level(void) const { return m_level; }
清空模块信息
inline void clearModules(void) { base::threading::ScopedLock scopedLock(lock()); m_modules.clear(); }
配置模块信息
// 参数modules的格式为“<module1>,<module2>,<module3>...” void VRegistry::setModules(const char *modules) { base::threading::ScopedLock scopedLock(lock()); auto addSuffix = [](std::stringstream &ss, const char *sfx, const char *prev) { // 文件匹配规则如果是prev后缀,则去掉prev后缀 if (prev != nullptr && base::utils::Str::endsWith(ss.str(), std::string(prev))) { std::string chr(ss.str().substr(0, ss.str().size() - strlen(prev))); ss.str(std::string("")); ss << chr; } // 文件匹配规则如果是sfx后缀,则去掉sfx后缀 if (base::utils::Str::endsWith(ss.str(), std::string(sfx))) { std::string chr(ss.str().substr(0, ss.str().size() - strlen(sfx))); ss.str(std::string("")); ss << chr; } // 文件匹配规则再次添加sfx后缀 ss << sfx; }; // 上面的addSuffix这个lambda表达式的功能代码有些重复 // 无非就是针对两个后缀依次判断了,prev是一定清除的,而sfx是需要保留的,完全可以实现的更精简点: /* auto addSuffix = [](std::stringstream& ss, const char* sfx, const char* prev) { std::string chr = ss.str(); //文件匹配规则如果是prev后缀,则去掉prev后缀 if (prev != nullptr && base::utils::Str::endsWith(chr, std::string(prev))) { chr = chr.substr(0, chr.size() - strlen(prev)); } //调整后的文件匹配规则如果不是sfx后缀,则添加sfx后缀 if (!base::utils::Str::endsWith(chr, std::string(sfx))) { chr += sfx; } ss.str(chr); }; */ auto insert = [&](std::stringstream &ss, base::type::VerboseLevel level) { // 没有禁用添加文件扩展名 if (!base::utils::hasFlag(LoggingFlag::DisableVModulesExtensions, *m_pFlags)) { // 原始文件匹配文件匹配规添加.h后缀 addSuffix(ss, ".h", nullptr); m_modules.insert(std::make_pair(ss.str(), level)); // 原始文件匹配规添加.c后缀 addSuffix(ss, ".c", ".h"); m_modules.insert(std::make_pair(ss.str(), level)); // 原始文件匹配规添加.cpp后缀 addSuffix(ss, ".cpp", ".c"); m_modules.insert(std::make_pair(ss.str(), level)); // 原始文件匹配规添加.cc后缀 addSuffix(ss, ".cc", ".cpp"); m_modules.insert(std::make_pair(ss.str(), level)); // 原始文件匹配规添加.cxx后缀 addSuffix(ss, ".cxx", ".cc"); m_modules.insert(std::make_pair(ss.str(), level)); // 原始文件匹配规添加.-inl.h后缀 addSuffix(ss, ".-inl.h", ".cxx"); m_modules.insert(std::make_pair(ss.str(), level)); // 原始文件匹配规添加.hxx后缀 addSuffix(ss, ".hxx", ".-inl.h"); m_modules.insert(std::make_pair(ss.str(), level)); // 原始文件匹配规添加.hpp后缀 addSuffix(ss, ".hpp", ".hxx"); m_modules.insert(std::make_pair(ss.str(), level)); // 原始文件匹配规添加.hh后缀 addSuffix(ss, ".hh", ".hpp"); } m_modules.insert(std::make_pair(ss.str(), level)); }; // 下面的这段代码就是对VModule字符串的一个解析,对照着VModule的格式(<module1>,<module2>,<module3>...)看就很清楚了。 bool isMod = true; bool isLevel = false; std::stringstream ss; int level = -1; for (; *modules; ++modules) { switch (*modules) { case '=': isLevel = true; isMod = false; break; case ',': isLevel = false; isMod = true; if (!ss.str().empty() && level != -1) { insert(ss, static_cast<base::type::VerboseLevel>(level)); ss.str(std::string("")); level = -1; } break; default: if (isMod) { ss << *modules; } else if (isLevel) { if (isdigit(*modules)) { level = static_cast<base::type::VerboseLevel>(*modules) - 48; } } break; } } if (!ss.str().empty() && level != -1) { insert(ss, static_cast<base::type::VerboseLevel>(level)); } }
针对上面这个配置过程的结果,我再举个例子说明一下:
如:vmodule
的格式是:"*main*=3,*base*=4"
。
配置之后m_modules
当中存放的键值对如下:{"*base*.h", 4} {"*main*.h", 3} {"*base*.hpp", 4} {"*main*.hpp", 3} {"*base*.hxx", 4} {"*main*.hxx", 3} {"*base*.cxx", 4} {"*main*.cxx", 3} {"*base*.cpp", 4} {"*main*.cpp", 3} {"*base*.c", 4} {"*main*.c", 3} {"*base*.-inl.h", 4} {"*main*.-inl.h", 3} {"*base*.cc", 4} {"*main*.cc", 3} {"*base*.hh", 4} {"*main*.hh", 3}
顺序不一定是上面的顺序,但一定是这些键值对。
判断某个文件在某个 Verbose Level 下是否允许进行 VERBOSE 日志输出
bool VRegistry::allowed(base::type::VerboseLevel vlevel, const char *file) { base::threading::ScopedLock scopedLock(lock()); if (m_modules.empty() || file == nullptr) { // 没有设置VMODULE或者文件名为空,则根据基准VLevel来判断 return vlevel <= m_level; } else { char baseFilename[base::consts::kSourceFilenameMaxLength] = ""; // 将文件名调整为指定长度大小以内的文件名 base::utils::File::buildBaseFilename(file, baseFilename); // 然后再依次和模块规则集(m_modules)里面每条规则作匹配, std::unordered_map<std::string, base::type::VerboseLevel>::iterator it = m_modules.begin(); for (; it != m_modules.end(); ++it) { // base::utils::Str::wildCardMatch支持通配符(*和?) if (base::utils::Str::wildCardMatch(baseFilename, it->first.c_str())) { return vlevel <= it->second; } } // 设置了VMODULE,但模块规则集(m_modules)里面的规则都匹配不上当前文件时,如果设置了AllowVerboseIfModuleNotSpecified,也允许在对应的Verbose Level下进行VERBOSE日志输出 if (base::utils::hasFlag(LoggingFlag::AllowVerboseIfModuleNotSpecified, *m_pFlags)) { return true; } // 其他情况则不允许在对应的Verbose Level下进行VERBOSE日志输出 return false; } }
获取模块信息
inline const std::unordered_map<std::string, base::type::VerboseLevel> &modules(void) const { return m_modules; }
是否启用了 VMODULE
/// @brief Whether or not vModules enabled inline bool vModulesEnabled(void) { return !base::utils::hasFlag(LoggingFlag::DisableVModules, *m_pFlags); }
通过命令行参数设置 VMODULE
// commandLineArgs是命令行参数解析器,命令行参数解析的结果已经保存在里面了 void VRegistry::setFromArgs(const base::utils::CommandLineArgs *commandLineArgs) { if (commandLineArgs->hasParam("-v") || commandLineArgs->hasParam("--verbose") || commandLineArgs->hasParam("-V") || commandLineArgs->hasParam("--VERBOSE")) { setLevel(base::consts::kMaxVerboseLevel); } else if (commandLineArgs->hasParamWithValue("--v")) { setLevel(static_cast<base::type::VerboseLevel>(atoi(commandLineArgs->getParamValue("--v")))); } else if (commandLineArgs->hasParamWithValue("--V")) { setLevel(static_cast<base::type::VerboseLevel>(atoi(commandLineArgs->getParamValue("--V")))); } else if ((commandLineArgs->hasParamWithValue("-vmodule")) && vModulesEnabled()) { setModules(commandLineArgs->getParamValue("-vmodule")); } else if (commandLineArgs->hasParamWithValue("-VMODULE") && vModulesEnabled()) { setModules(commandLineArgs->getParamValue("-VMODULE")); } }
CommandLineArgs
类在 日志格式配置方式 中已经详细介绍过了,这里就不多说了。
vModulesEnabled
接口在前面也介绍过了。
对外提供的 VERBOSE 日志配置的接口
VERBOSE
日志配置相关的接口主要是由el::loggers
工具类提供的。相关接口如下:void Loggers::setVerboseLevel(base::type::VerboseLevel level) { ELPP->vRegistry()->setLevel(level); } base::type::VerboseLevel Loggers::verboseLevel(void) { return ELPP->vRegistry()->level(); } void Loggers::setVModules(const char *modules) { if (ELPP->vRegistry()->vModulesEnabled()) { ELPP->vRegistry()->setModules(modules); } } void Loggers::clearVModules(void) { ELPP->vRegistry()->clearModules(); }
ELPP
宏在 偶尔日志宏 中已经介绍过了,是 easylogging++的全局管理类。ELPP->vRegistry()
是全局管理类的 VRegistry 对象成员,这里就是对VRegistry
类相关接口做了简单的包装而已。
至此,VERBOSE
日志信息的管理就介绍完了。下一篇我们开始介绍性能跟踪的实现。