C++零基础到工程实战(3.6):逻辑实战示例—日志模块
目录一、本节学习内容概要图二、前言三、案例需求与整体思路3.1 日志级别设计3.2 用户需求说明3.3 本案例的核心思想四、完整代码4.1 示例代码4.2 Powershell 运行结果五、代码实现与逻辑分析5.1 先获取用户输入的日志级别5.2 再准备要输出的测试日志5.3 日志过滤的核心判断5.4 为什么这里可以直接比较大小5.5 switch 在这里起什么作用1switch 不是“判断和外面传入的 logLevel 比较”2switch 不会影响“后续输出内容”3代码真实逻辑1先判断能不能输出2如果能输出再决定输出前缀是什么3最后输出六、当前 if 不成立为什么后续日志还能继续输出七、小结7.1 本节涉及的核心知识7.2 这个案例的实战意义7.3 日志流程一、本节学习内容概要图二、前言前面我们已经学习了if / else、switch、enum class、main函数参数传递等内容。如果只是单独看这些语法很多人会觉得“会写了但不知道有什么用”。所以这一节不再只讲单个语法点而是通过一个完整的小案例把这些内容串起来实现一个最基础的日志模块逻辑。在实际工程中日志模块几乎无处不在。程序运行时经常会输出调试信息、普通信息、错误信息、严重错误信息。但并不是所有日志都要一直显示出来通常都会让用户指定一个“最低显示级别”然后程序根据这个级别决定哪些日志该输出哪些日志不输出。本节案例代码核心思路通过命令行参数控制日志级别再结合枚举、条件判断和switch实现一个最基础的日志过滤功能。三、案例需求与整体思路案例的目标让用户控制程序显示哪一级及以上的日志信息。3.1 日志级别设计程序中定义了四种日志级别enum class LogLevel { DEBUG, INFO, ERROR, FATAL };它们默认按顺序对应为DEBUG 0INFO 1ERROR 2FATAL 3也就是说日志级别是从低到高逐渐变严重的。这也是后面能够直接用大小比较来判断日志是否输出的基础。3.2 用户需求说明用户运行程序时可以在命令行Powershell中输入一个参数例如.\test_main_log.exe debug .\test_main_log.exe info .\test_main_log.exe error .\test_main_log.exe fatal这个参数表示用户希望看到的最低日志级别。例如1输入debug表示显示所有日志。2输入info表示只显示INFO及以上日志。3输入error表示只显示ERROR和FATAL日志。4输入fatal表示只显示最严重的FATAL日志。3.3 本案例的核心思想这个案例本质上就是一句话当前日志级别如果大于等于用户设置的最低级别就输出否则就不输出。也就是代码中的核心判断if (level logLevel)四、完整代码4.1 示例代码#include iostream using namespace std; enum class LogLevel { DEBUG, INFO, ERROR, FATAL }; int main(int argc,char *argv[]) { //用户传递日志的最低显示级别 //debug info error fatal // test_main_log info auto logLevel LogLevel::DEBUG; if (argc 1) { string levelstr argv[1]; if (info levelstr) logLevel LogLevel::INFO; else if(error levelstr) logLevel LogLevel::ERROR; else if (fatal levelstr) logLevel LogLevel::FATAL; } ///测试日志1 debug { auto level LogLevel::DEBUG; string context test log 1; if (level logLevel) { string levelstr debug; switch (level) { case LogLevel::INFO: levelstr info;break; case LogLevel::ERROR: levelstr error;break; case LogLevel::FATAL: levelstr fatal;break; } cout levelstr : context endl; } } ///测试日志2 INFO { auto level LogLevel::INFO; string context test log 2; if (level logLevel) { string levelstr debug; switch (level) { case LogLevel::INFO: levelstr info;break; case LogLevel::ERROR: levelstr error;break; case LogLevel::FATAL: levelstr fatal;break; } cout levelstr : context endl; } } ///测试日志3 ERROR { auto level LogLevel::ERROR; string context test log 3; if (level logLevel) { string levelstr debug; switch (level) { case LogLevel::INFO: levelstr info;break; case LogLevel::ERROR: levelstr error;break; case LogLevel::FATAL: levelstr fatal;break; } cout levelstr : context endl; } } ///测试日志4 FATAL { auto level LogLevel::FATAL; string context test log 4; if (level logLevel) { string levelstr debug; switch (level) { case LogLevel::INFO: levelstr info;break; case LogLevel::ERROR: levelstr error;break; case LogLevel::FATAL: levelstr fatal;break; } cout levelstr : context endl; } } }4.2 Powershell 运行结果五、代码实现与逻辑分析5.1 先获取用户输入的日志级别主函数写法如下int main(int argc, char *argv[])这里1argc表示命令行参数个数。2argv表示命令行参数数组。程序一开始先给出默认值auto logLevel LogLevel::DEBUG;这表示如果用户没有传参数那么默认显示所有日志。接着程序判断用户是否输入了额外参数if (argc 1) { string levelstr argv[1]; if (info levelstr) logLevel LogLevel::INFO; else if (error levelstr) logLevel LogLevel::ERROR; else if (fatal levelstr) logLevel LogLevel::FATAL; }这里的含义是如果输入的是info最低显示级别改成INFO如果输入的是error最低显示级别改成ERROR如果输入的是fatal最低显示级别改成FATAL至于debug代码里没有单独写判断是因为默认值本来就是DEBUG所以即使不写也成立。5.2 再准备要输出的测试日志程序里一共模拟了四条日志分别是DEBUG : test log 1 INFO : test log 2 ERROR : test log 3 FATAL : test log 4例如第一条日志的写法是{ auto level LogLevel::DEBUG; string context test log 1; ... }这里level表示这条日志本身的级别context表示日志内容后面的三条日志也完全类似只是级别和内容不同。这样设计的好处是很直观便于我们观察不同级别在不同输入条件下的输出效果。5.3 日志过滤的核心判断每一条日志在输出之前都会先判断if (level logLevel)这里一定要分清两个量1level表示当前这一条日志自己的级别。2logLevel表示用户要求的最低显示级别。例如用户输入.\test_main_log.exe error那么logLevel LogLevel::ERROR;这时程序依次判断DEBUG ERROR不成立不输出INFO ERROR不成立不输出ERROR ERROR成立输出FATAL ERROR成立输出所以最后只能看到error:test log 3 fatal:test log 4这正好符合日志模块的设计目标。5.4 为什么这里可以直接比较大小很多初学者看到这里会有一个疑问枚举类型为什么能直接写比较原因就在于这里的枚举值本身是有顺序的。虽然代码里没有手动写具体数字但默认就是从0开始递增DEBUG 0 INFO 1 ERROR 2 FATAL 3所以LogLevel::ERROR LogLevel::INFO本质上就相当于2 1结果当然为真。也就是说这个案例并不是随便定义一个枚举就能这样比较而是因为日志级别本身就天然具备“从低到高”的顺序关系所以非常适合用枚举来表达。5.5switch在这里起什么作用1switch不是“判断和外面传入的 logLevel 比较”switch(level)判断的对象是当前日志自己的级别level不是外面的logLevel。也就是说switch (level)这里是在看当前这条日志到底是 DEBUG、INFO、ERROR 还是 FATAL。它的作用不是决定“能不能输出”而是决定输出时前面的字符串写成什么。2switch不会影响“后续输出内容”如果level和某个case匹配就把当前这一小段代码里的levelstr改成对应字符串供这一条日志输出使用。它只是改了当前这一条日志输出前缀不会影响后面的其他日志块。比如这一段里string levelstr debug;先默认写成debug。然后switch (level) { case LogLevel::INFO: levelstr info;break; case LogLevel::ERROR: levelstr error;break; case LogLevel::FATAL: levelstr fatal;break; }如果level是INFO就把levelstr改成infoERROR就改成errorFATAL就改成fatal但如果level是DEBUG由于这里没有写case LogLevel::DEBUG就不会进入任何case于是levelstr仍然保持最开始的debug。3代码真实逻辑1先判断能不能输出if (level logLevel)2如果能输出再决定输出前缀是什么switch (level)3最后输出cout levelstr : context endl;六、当前if不成立为什么后续日志还能继续输出这里有一个非常容易混淆的地方需要特别说明。当程序执行到某一条日志时会先判断if (level logLevel)这个判断只负责控制当前这一条日志是否输出并不会让整个程序后面的代码都停止执行。例如第一条日志是auto level LogLevel::DEBUG;如果用户输入的是.\test_main_log.exe info那么此时用户设置的最低显示级别就是logLevel LogLevel::INFO;因此第一条日志对应的判断就是if (LogLevel::DEBUG LogLevel::INFO)这个条件不成立于是结果只是1当前这一条DEBUG日志不输出2当前这个if大括号内部的switch和cout都不会执行3程序继续往下运行去判断下一条日志。也就是说程序并不会因为第一条日志没有通过判断就把后面所有代码都跳过。它只是跳过了“当前这一条日志对应的输出代码块”然后继续执行下面的INFO、ERROR、FATAL日志判断。可以把它理解成下面这样第一条日志判断一次第二条日志再判断一次第三条日志再判断一次第四条日志再判断一次它们彼此是独立的。所以当用户输入info时程序的执行效果实际上是DEBUG INFO // 不成立第一条不输出 INFO INFO // 成立第二条输出 ERROR INFO // 成立第三条输出 FATAL INFO // 成立第四条输出最终结果就是info:test log 2 error:test log 3 fatal:test log 4因此这里一定要明确if语句只决定“当前这一条日志要不要输出”不会阻止程序继续判断后面的日志。七、小结这个日志模块虽然不大但已经把前面学过的多个知识点串联了起来包括7.1 本节涉及的核心知识1使用enum class表示一组固定状态2使用argc和argv获取用户输入3使用if / else if解析命令行参数4使用关系运算完成日志级别过滤5使用switch把枚举值转换成可输出字符串。7.2 这个案例的实战意义在真正的工程开发中日志模块是非常常见的功能。虽然这里写的还是一个简化版但它已经具备了日志系统最核心的思想不是所有日志都无条件输出而是根据设定的级别进行筛选。7.3 日志流程程序启动后先将默认最低日志级别设为LogLevel::DEBUG这意味如果用户没有额外传参程序会显示全部日志。随后程序通过argc和argv读取命令行参数如果用户输入了info、error或fatal就把内部的logLevel修改为对应级别。接着程序并不是一次性统一处理所有日志而是按顺序分别处理四条测试日志DEBUG、INFO、ERROR、FATAL。每处理一条日志都会先取出这条日志自己的级别level再与用户设置的最低显示级别logLevel进行比较即判断if (level logLevel)是否成立。如果成立说明这条日志应该显示于是进入if内部再通过switch(level)把枚举级别转换成对应的字符串例如debug、info、error、fatal最后与日志内容context一起输出如果条件不成立则说明这条日志级别不够不会输出但程序不会停止而是继续往下执行判断下一条日志。正因为每条日志都是独立判断的所以当用户输入info时第一条DEBUG日志会被过滤掉但后面的INFO、ERROR、FATAL仍然会继续判断并正常输出。整个模块的核心思想就是用命令行参数确定最低显示级别用条件判断决定当前日志是否输出用switch负责把日志级别转换成最终显示给用户的文本。