日志模块设计
本篇文章主要参考如下文章,主要是对代码做一个较为详尽的解释
参考链接:https://blog.csdn.net/qq_46495964/article/details/122952567
前言:
日志系统在程序运行中有着非常大的作用,用于记录程序的运行情况,在程序出错后查看日志,方便地定位出错的大概范围。在设计日志系统之前,先考虑一下日志需要输出什么信息呢?什么信息才是有用的信息,都知道写日志是一种对文件的io
操作,所以尽可能避免输出没用的信息。
有用的信息:关键变量的值、运行的位置(哪个文件、哪个函数、哪一行)、时间、线程号、进程号等等。
日志系统的设计
日志的级别
在测试、调试、交付等场景需要输出不同的级别日志。
1
2
3
4
5
6
7
8
9//常见的日志级别
enum LOGLEVEL
{
LOG_LEVEL_NONE,
LOG_LEVEL_ERROR, // error
LOG_LEVEL_WARNING, // warning
LOG_LEVEL_DEBUG, // debug
LOG_LEVEL_INFO, // info
};日志的输出地
日志输出的地方可能不同,终端、控制台、UI界面、文件等等都有。
1
2
3
4
5
6enum LOGTARGET
{
LOG_TERM = 0x00,
LOG_FILE = 0x01,
LOG_UI = 0x10
};日志的作用域
日志做到什么时候都可以输出,可作用于全程序文件,考虑到多线程情况下,必须保证日志的输出需要得到线程安全的保障,所以需要一个全局且唯一的日志器。使用设计模式中的单例模式—–日志器
C++版本的日志系统的实现
Logger.h
1 | 这是一个基本的线程安全日志系统的头文件,其目的是为应用程序提供日志功能。以下是对这个头文件的详细解释: |
1 | /** |
Logger.cpp
1 | 这是日志类的实现文件`Logger.cpp`。它对`Logger.h`中定义的函数进行了具体的实现。我将分步解释这个文件的内容: |
1 | /** |
问题描述
Stop方法: 设置exit_标志为true。 通过条件变量通知日志线程。 等待日志线程结束
请问这里为什么需要通知日志线程
解答:在
Logger
类的实现中,日志线程的工作是从队列中获取日志消息并将其写入文件。为了实现这个功能,它在队列为空时通过cv_.wait(guard)
进入休眠状态,等待新的日志消息的到来。当你调用
Stop
方法时,你希望日志线程能够尽快结束。但有一种情况需要考虑:当Stop
被调用时,队列可能仍然是空的,这意味着日志线程可能处于cv_.wait(guard)
的休眠状态。在这种情况下,即使你设置了
exit_
标志为true
,日志线程仍然不会醒来检查这个标志,因为它正在等待新的日志消息到来。为了确保日志线程能够醒来、检查
exit_
标志并正确地退出,你需要在Stop
方法中通过cv_.notify_one()
通知日志线程。这样,日志线程将被唤醒,然后它可以检查exit_
标志,并根据该标志的值决定是否退出。总之,通知日志线程是为了确保它可以从
cv_.wait(guard)
中醒来,然后检查退出条件并安全地退出。这个程序没有设计线程来将新的日志信息加入队列吗
从提供的代码来看,日志系统中只设计了一个专门的日志线程(在
Logger::Start()
方法中启动),其任务是从队列中提取日志消息并将其写入文件。此外,并没有为添加新日志消息到队列专门设计线程。当你希望写入日志时,只需在你的主程序或任何其他线程中调用
LogInfo()
,LogWarning()
, 或LogError()
这样的宏。这些宏内部会调用Logger::AddToQueue()
,该方法将日志消息格式化并加入到队列中。这种设计的优点是简单且高效,因为它允许多个线程在运行时无缝地向日志系统添加消息,而无需额外的线程。但值得注意的是,
Logger::AddToQueue()
使用了互斥锁mutex_
来保证多个线程同时向队列添加消息时的线程安全。这意味着,即使多个线程同时调用日志宏,Logger::AddToQueue()
中的代码仍然是线程安全的。总的来说,这个日志系统的设计考虑到了多线程环境,允许多个线程同时添加日志消息,但它并没有专门设置一个线程来完成这项任务,而是允许主程序或其他任何线程来直接调用添加日志的宏。
自己的话:你自己程序在运行的时候是不是有多个线程,这些线程就是负责啥时候要调用
LogInfo()
,LogWarning()
, 或LogError()
这样的宏。所以当然不需要额外的线程来操作加入队列操作,只需要一个带锁函数即可。