接 上一章 ,我们定义了日志的基础信息,接下来就开始完成日志输出部分。
对外我们会提供各种方便的访问接口,而内部将提供一个统一的日志数据格式化接口,如:
void LogInternal(Level level, std::chrono::system_clock::time_point timestamp, const WCHAR* source, const WCHAR* msg);
这个内部接口,将完成数据的格式化,写入文件、输出到控制台等功能。
其他参数较为简单,让我们看看参数 timestamp,它的类型是std::chrono::system_clock::time_point ,std::chrono 是什么?
std::chrono
它是 C++ 11 引入的日期时间模版库,强大而灵活。
基本概念
时钟:clock
为不同目的设计了多个时钟,较为常用的为如下三个。
- 系统时钟 - system_clock
可以简单的理解为系统当前时间的时钟,它是可变的,如手动调整,受到时钟调整和时区的影响。
适用于获取系统当前的时间信息。
注意,因为它是可调整的,意味着它的取值可能会回退,会跳跃。
时钟的起点是什么?
C++20前,并没有对这个时间点的起始做出规定,从C++20开始,这个时间被确定为Unix Time。不过在20之前,大部分编译器的实现都使用Unix时间(Unix Time)作为这个时钟源的纪元(epoch)起点,因此一般来说可以将其视为Unix时间戳进行使用。
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
// 格式化时间
std::time_t tt = std::chrono::system_clock::to_time_t(now);
std::tm* ptm = std::localtime(&tt);
std::cout << "Current time is: " << std::put_time(ptm,"%c") << std::endl;
- 稳定时钟 - steady_clock
它和 system_clock的区别是,它是单调递增的,不受任何时钟调整或时区的影响。
适合用于测量时间区间
当你更关心时间流逝的长度,而不关心现在具体几时几分,那么就很适合用 system_clock ,比如定时健身二十分钟,看一段代码耗时多久,我们这就来封装一个检查代码耗时的函数:
#include <chrono>
template <typename Functor>
std::chrono::milliseconds::rep execTime(Functor func)
{
auto start = std::chrono::high_resolution_clock::now();
func();
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
return duration.count();
}
调用:
int sum(int n) {
int result = 0;
for (int i = 1; i <= n; ++i) {
result += i;
}
return result;
}
int main() {
auto duration = execTime([](){ sum(1000000); });
std::cout << "Function execution time: " << duration << " milliseconds" << std::endl;
return 0;
}
- 高精度时钟 - high_resolution_clock
当系统中存在多个时钟时, hight_definition_clock 使用精度最高的clock(但具体使用哪一个clock,取决于平台或库的实现)。
适合用于需要高精度时间计算
但是,目前在大多数平台上它仅仅是system_clock
或steady_clock
的别名,并没有提供更高精度的时间度量。
时间间隔:duration
表示一段时间,即两个时间点之间的时间长度。
template <class Rep, class Period = ratio<1> > class duration;
Rep:表示数值类型,用来表示Period的数量,比如int、float、double。
Period:是 ratio 类型,ratio 又是什么呢?
精度 :ratio
模板类std::ratio及相关的模板类(如std::ratio_add)提供编译时有理数算术支持。此模板的每个实例化都准确表示任一有限有理数。它们都是用来表示比例关系的模板类。
简单的说,std::ratio代表一个比例,或者说比率。其实就是将给定的两个整数分别除以它们的最大公约数得到一个分数(分子及分母)。
template <intmax_t N, intmax_t D = 1> class ratio;
N表示分子,D表示分母,默认以秒为单位,即 N/D 秒
那么,结合上面讲过的 duration ,我们可以这样定义“一分钟”这个时间段:
typedef duration <Rep, ratio<60,1>> minutes;
其实,这正是chrono命名空间的定义。
typedef duration <Rep, ratio<3600,1>> hours;
typedef duration <Rep, ratio<60,1>> minutes;
typedef duration <Rep, ratio<1,1>> seconds;
typedef duration <Rep, ratio<1,1000>> milliseconds;
typedef duration <Rep, ratio<1,1000000>> microseconds;
typedef duration <Rep, ratio<1,1000000000>> nanoseconds;
由此,我们可以简单的使用各个 duration:
chrono::minutes mintu{5};//5分钟
chrono::seconds sec{10};//10秒钟
chrono::milliseconds mills{500};//500毫秒
auto dul = sec - mills;//两者差值,单位默认转到更小的ms
时间点:time_point
time_point表示一个特定的时间点,可以理解为时间的戳记,它表示自纪元以来的时间量。纪元是指定的起点时间,对于std::chrono::system_clock
来说,纪元通常是1970年1月1日午夜。time_point类的模板参数包括所采用的时钟类型和时间单位。比如:std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>
表示系统时钟下的纳秒级时间点。
最终,timestamp 格式化部分代码如下:
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(timestamp.time_since_epoch());
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(milliseconds).count();
auto time = localtime(&seconds);
WCHAR timestampSz[128];
size_t len = _snwprintf_s(
timestampSz,
_TRUNCATE,
L"%02i:%02i:%02i.%03llu",
time->tm_hour,
time->tm_min,
time->tm_sec,
milliseconds.count() % 1000);
格式化一条完整的日志信息
const WCHAR* levelSz =
(entry.level == Level::Error) ? L"ERROR" :
(entry.level == Level::Warning) ? L"WARNINIG" :
(entry.level == Level::Fatal) ? L"Fatal" :
L"DEBUG"
;
std::wstring message = levelSz;
message += L" (";
message.append(entry.timestamp);
message += L") ";
message += entry.tag;
message += L": ";
message += entry.messsage;
message += L'\n';
// 输出控制台、调试器或写到文件中
语法糖
考虑到时间长度,日期等类型使用的复杂性,chrono 库大量使用了自定义字面量,简化了时间相关的操作,如:
using namespace std::literals::chrono_literals;
auto ten_second = 10s;
auto five_minutes = 5min;
auto dur = five_minutes + ten_second;
目前(C++20)支持的字面量包括y
(年)d
(日)h
,min
,s
,ms
,us
,ns
还有一个用法就是定义日期
auto ymd2 = 2021y / September / 20; // 2021-09-20
是不是看起来简单的有些古怪,古怪的很简单。
至此,我们将 C++(11及之后)关于时间的操作做了简单的梳理,而这仅仅是最为基础的部分,std::chrono中还封装了大量的时间定义和时间操作工具函数,有待我们的挖掘。
参考
Date and time utilities - cppreference.com