East - Log Module

East项目的日志模块主要有以下几个类:
Log Module:

  • LogAppender - 日志输出地:可以派生出专门的类处理,如FileAppender等

  • LogEvent - 日志事件: 包含一条log完整的信息,时间,行号,线程号等

  • LogFormatter - 日志格式化器: 包含一个模式串,决定了log的格式

  • Logger - 日志类:管理LogAppender和LogFormatter

  • LoggerMgr - 日志管理类: 统一存放所有的Logger, 默认有root logger

  • LogEventWrap - 日志时间包装类: 输出日志的核心类, 析构时打印日志

整个模块的类图如下

East Log Module

Note:
每一个Logger默认有一个名称,并且支持配置,如formatter,appenders等。

Logger默认构造含有formatter和level,但是并没有默认的appender。

如果我们从LoggerMgr中获取一个不存在的Logger,会去创建一个Logger,如果使用这个Logger去输出日志,则会使用LoggerMgr中创建的root logger(默认formatter和StdoutAppender)。

日志输出的流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#define ELOG_LEVEL(logger, level)                                    \
if (nullptr != logger && logger->getLevel() <= level) \
East::LogEventWrap(std::make_shared<East::LogEvent>( \
logger, level, __FILE__, __LINE__, 0, \
static_cast<uint32_t>(East::GetThreadId()), \
static_cast<uint32_t>(East::GetFiberId()), \
static_cast<uint64_t>(time(0)))) \
.getSStream()

#define ELOG_DEBUG(logger) ELOG_LEVEL(logger, East::LogLevel::DEBUG)
#define ELOG_ROOT() East::LogMgr::GetInst()->getRoot()

LogEventWrap::~LogEventWrap() {
if (nullptr != m_event && nullptr != m_event->getLogger()) {
m_event->getLogger()->Log(m_event->getLevel(), m_event);
}
}


当我们有如下调用时
East::Logger::sptr g_logger = ELOG_ROOT();
ELOG_INFO(g_logger) << "main begin";

我们会构造一个临时的LogEventWrap对象,构造时会按照当前的上下文填写日志信息构造一个LogEvent,
当作用域结束后,这个临时对象就会析构,在析构函数中我们会使用logger中的Log函数将LogEvent中的
日志信息输入到对应的LogAppender中。

这种方法我们称之为RAII日志包裹器,简洁优雅并且无需我们自行调用Log函数,而且异常安全。
缺点就是如果我们有实时打印日志的需求,那么需要我们添加作用域让LogEventWrap提前析构。

如果保证线程安全?
我们有多个logger, 每个logger可能有多个appender,我们的服务器框架肯定是支持多线程的,如果保证线程安全?

我们的Logger,LoggerMgr, LogAppender都含有一个m_mutex成员。

  • LoggerMgr是一个单例类,存放所有的Logger,那么我们需要一个锁来保证添加/删除 Logger时是正确的
  • Logger包含多个LogAppender, 同样的,涉及LogAppender操作时,也需要锁,并且输出log时也需要保证数据正确
  • LogAppender负责日志输出,需要锁来保证并发写的安全
    注意, 关于m_mutex我们可以自由选择用什么工具:互斥量,读写锁,自旋锁等

实际测试中,我们发现互斥量的性能是最好的:
四个线程

1
2
3
4
5
无控制:         写log大概 100MB/s

使用Mutex: 写log大概 40MB/s

使用SpinLock: 写log大概 25MB/s

理论上来说写log,冲突发生较少,所以spinlock效率较高才合理,但是四个线程下mutex的确快很多,
但是我增加到10个线程测试结果还是一样,Mutex会快很多

spinlock适用于非常短的临界区,我们测试下:

1
2
3
4
5
bg: 5个线程, 每个线程都对一个全局变量执行++ 1000 0000次

使用Mutex执行十次的平均耗时是:4874.5 ms

使用SpinLock执行十次的平均耗时是:9112 ms

原因就是高竞争其实Spinlock会让CPU过度忙等待,而mutex会让出CPU,在我们这种场景下线程切换其实不太频繁,所以mutex效率更高。

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2015-2025 Xudong0722
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信