muduo网络库里实现了自己的日志系统

为什么网络程序要有日志?

后台的网络服务没有UI界面,出现如:连接断开,请求超时等问题时需要靠日志进行定位,再查找原因

为什么muduo要自己实现一套日志库?

  • muduo是单线程Reactor模型的高性能网络库,日志不能阻塞IO.
  • muduo设计是轻量化,要避免第三方库依赖,所以要避免引入第三方日志库

Timestamp

为什么要设计时间戳?

  • 每一条日志都需要携带时间,否则将失去排查的意义。
  • 因为需要精准记录在极短时间多个事件(连接建立、消息收发、超时触发)发生的先后顺序,普通秒级时间戳无法满足,需自己设计微秒级时间戳。
  • 统一不同模块(日志,网络IO)的时间接口。

Timestamp.h

1
2
3
4
5
#pragma once

#include <string>
#include <cstdint> // 明确int64_t类型
#include <chrono> // 现代C++时间库

#pragma once 是防止头文件被重复包含

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Timestamp
{
public:
Timestamp();
explicit Timestamp(int64_t microSecondsSinceEpoch);

static Timestamp now();
std::string toString() const;

int64_t getMicroSecondsSinceEpoch() const { return microSecondsSinceEpoch_; }

private:
int64_t microSecondsSinceEpoch_; //存储微秒级时间戳,保证精度
std::string formatTime(time_t seconds, int microSeconds) const;
};

microSeconsSinceEpoch_ 用来存储从计算机领域约定的时间起点(1970年1月1日 00:00:00)到当前时间的总微秒数,用int64_t 是因为微妙存储的数值会很大,用int 容易溢出。

带参数的构造函数前加explicit 防止隐式转换。

now()函数是核心,设置成静态方法,通过类直接访问,获取当前时间

toString()是对外接口,结合对内实现的formatTime(),将时间戳转化为人类可读的字符串。

Timestamp.cc

头文件包含

1
2
3
4
5
#include "Timestamp.h"

#include <mutex>
#include <cstdio>
#include <ctime>

构造函数的实现

1
2
3
4
5
Timestamp::Timestamp() : microSecondsSinceEpoch_(0) {}

Timestamp::Timestamp(int64_t microSecondsSinceEpoch)
: microSecondsSinceEpoch_(microSecondsSinceEpoch)
{}

通过参数列表给成员参数进行初始化,默认构造初始化赋0。

1
2
3
4
5
6
std::string Timestamp::toString() const
{
time_t second = microSecondsSinceEpoch_ / 1000000;
int micro_second = microSecondsSinceEpoch_ % 1000000;
return formatTime(second, micro_second);
}

将微秒级时间戳拆分为秒的部分和微妙的部分,再传入formatTime()将时间进行格式化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
std::string Timestamp::formatTime(time_t seconds, int microSeconds) const
{
struct tm tm_time;

localtime_r(&seconds, &tm_time);

char buf[128] = { 0 };
snprintf(buf, sizeof buf, "%4d/%02d/%02d %02d/%02d/%02d.%06d",
tm_time.tm_year + 1900, // 年份:tm_year是从1900开始的偏移量
tm_time.tm_mon + 1, // 月份:tm_mon是0-11,需+1
tm_time.tm_mday, // 日期:1-31
tm_time.tm_hour, // 小时:0-23
tm_time.tm_min, // 分钟:0-59
tm_time.tm_sec, // 秒:0-59
microSeconds // 微秒
);

return std::string(buf);
}
  • struct tm是一个可以分别存储年月日时分秒的结构体。
  • localtime_rtoString里拆解出的second秒级时间戳拆分为tm结构体,且在Linux下线程安全。
  • 然后再进行字符串拼接,并将拼接的char数组构造为string对象。
1
2
3
4
5
6
7
Timestamp Timestamp::now()
{
auto now = std::chrono::system_clock::now();
auto epoch = now.time_since_epoch();
int64_t micro_sec = std::chrono::duration_cast<std::chrono::microseconds>(epoch).count();
return Timestamp(micro_sec);
}

调用 C++11 标准库chrono的系统时钟接口,获取当前操作系统的时间点(time_point),然后计算now这个时间点距离时间(1970-01-01 00:00:00 )的时间间隔(duration),强制类型转换,把系统默认精度的duration(比如纳秒级)转换成微秒级 duration,.countmicroseconds类型中提取裸整数数值,并封装为Timestamp对象返回。

测试代码

1
2
3
4
5
int main() {
Timestamp ts = Timestamp::now();
std::cout << "格式化时间:" << ts.toString() << std::endl;
return 0;
}

Timestamp

拼接时分秒也可以用” : “。

源码地址

Timestamp.h:http://gitee.com/lpzdinghai/lpzmuduo/blob/master/Timestamp.h

Timestamp.cc:http://gitee.com/lpzdinghai/lpzmuduo/blob/master/Timestamp.cc