Skip to main content
Version: nightly 🚧

日志

turbo Logging 库提供了编写短文本消息的工具关于stderr磁盘文件其他接收器的程序状态(通过 扩展 API)。

API 概述

LOG()CHECK() 宏系列是 API 的核心。每个形成 可以选择将附加数据流入的语句的开头 就像std::cout一样。

流入单个宏的所有数据将被连接并写入日志文件作为一条消息,带有由元数据形成的 前缀 (时间、文件/行等)。特别是,与“std::cout”不同,该库在每条消息的末尾提供一个换行符,因此您通常不应该结束 使用 \nstd::endl 记录语句。任何正在流入的换行符 将显示在日志文件中。

有关更详细的信息,请参阅头文件。

LOG()

LOG() 将严重性级别作为参数,它定义了粒度和 要记录的日志记录信息的类型。四个基本严重性级别是INFO, “警告”、“错误”和“致命”。 FATAL 并不是随意命名的;它导致 日志库在记录流消息后终止进程。 请参阅下文,了解有关日志级别 的更多信息,包括最佳实践 用于挑选一个。

LOG(INFO) << "Kumo 你好!";

这将在日志中生成一条消息,如下所示:

I0926 09:00:00.000000 12345 foo.cc:10] Kumo 你好!

元数据的格式记录在下面

CHECK()

CHECK() 是一个断言。它的严重性始终是FATAL,因此它是唯一的参数 是一个应该为真的条件。如果不是,CHECK() 写入日志并终止该进程。它在所有构建模式中都处于活动状态(与 C 不同) assert() 宏)并将失败记录到应用程序日志中,方式与LOG() 但包含有关失败原因和失败位置的附加信息。

FATAL一样,应谨慎使用“CHECK()”断言(尤其是在服务器代码)并且仅在最好实际终止的情况下 处理而不是尝试恢复:例如无法恢复,或者记忆腐败可能会损坏用户数据。请注意,您还应该知道在哪里 您的代码可能正在运行;命令行实用程序或批处理中的CHECK()处理作业比面向用户的服务中的“CHECK()”需要更少的谨慎。如果 您不确定代码将在哪里运行(如果您正在编写实用程序库,例如),保守一点并假设它将用于面向生产的 服务,并应尽可能避免CHECK()

CHECK(!filenames_sorted.empty()) << "no files matched";
ProcessFile(filenames_sorted.front());

这将在日志中生成一条消息,如下所示:

F0926 09:00:01.000000   12345 foo.cc:100] Check failed: !filenames_sorted.empty() no files matched
E0926 09:00:01.150000 12345 process_state.cc:1133] *** SIGABRT received by PID 12345 (TID 12345) on cpu 0 from PID 12345; stack trace: ***
E0926 09:00:01.250000 12345 process_state.cc:1136] PC: @ 0xdeadbeef (unknown) raise
@ 0xdeadbeef 1920 FailureSignalHandler()
@ 0xdeadc0w5 2377680 (unknown)
(more stack frames follow)

请注意,此日志条目使用前缀F表示日志级别 FATAL。条件本身的文本记录在流式操作数之前。 此外,堆栈跟踪记录的严重性为ERROR(在前缀为ERROR的行上) 在FATAL消息之后但在进程终止之前使用E)。

特殊的二参数形式拼写为CHECK_EQ()CHECK_GT()CHECK_STREQ() (对于char*字符串)等可用于对比较进行断言 在可流式传输、可比较的类型之间。除了论证的文本之外, 这些形式记录参数的实际值。

int x = 3, y = 5;
CHECK_EQ(2 * x, y) << "oops!";

这将在日志中生成一条消息,如下所示:

F0926 09:00:02.000000   12345 foo.cc:20] Check failed: 2 * x == y (6 vs. 5) oops!

日志级别

turbo::LogSeverity 类型表示严重性级别。 LOG() 的参数是实际上不是这种类型,或者任何类型。一些宏技巧是 用于使LOG(ERROR)工作,而不使用名为的宏或全局符号错误。这是必要的,因为ERROR是由一些流行的定义的 第三方软件包(例如 Microsoft Windows)并且无法重新定义。

有四个适当的日志级别

INFO

对应于turbo::LogSeverity::kInfo。它描述了预期事件对于理解程序的状态很重要,但哪些不是 表明有问题。库,尤其是低级公共库,应谨慎使用此级别,以免他们向每个程序的日志发送垃圾邮件 使用它们。

WARNING

对应于turbo::LogSeverity::kWarning。它描述了可能发生的意外事件

ERROR

对应于turbo::LogSeverity::kError。它描述了意外的问题事件该程序能够从中恢复。 ERROR消息应该是可操作的, 这意味着他们应该描述软件或其内容的实际问题配置(而不是例如用户输入)和组合 消息、文件名和行号以及周围的消息应该是至少足以理解所报告的事件。

FATAL

对应于turbo::LogSeverity::kFatalCHECK 的隐式严重性级别 失败。它描述了不可恢复的问题。在此级别记录终止该进程。应谨慎使用“FATAL”日志记录级别 认真对待服务,特别是面向用户的服务,以及图书馆此类服务中可能包含的代码。每个致命日志都是一个潜在的 如果很大一部分服务工作同时发生,就会发生中断。
致命日志记录通常更适合开发人员工具,某些批处理作业以及作业启动时的失败。也就是说,进程终止和 中断总是比未定义的行为(可能包括用户数据损坏和/或安全或隐私事件),因此FATAL是有时甚至在服务器和库代码中也适用作为最后的手段 对无法以任何其他方式处理的意外行为的响应。

还有两个伪级别

DFATAL

"debug fatal")对应到 turbo::kLogDebugFatal。在优化构建中它的值是ERROR(例如在生产)和其他版本(例如测试)中的FATAL。它可以用于 确保意外事件导致测试失败(通过终止过程),但不损害生产。由于生产工作将继续发生DFATAL故障后,请确保恢复优雅地。

QFATAL

"quiet fatal")没有相应的 turbo::LogSeverity 值。它的行为类似于FATAL,除了 没有记录堆栈跟踪并且不运行atexit()处理程序。这是通常是启动时发生错误的最佳选择(例如标志 验证),其中控制流无趣且不必要诊断。

动态日志级别

如果您想使用 C++ 表达式指定严重性级别,例如使得 使用的级别在运行时会有所不同,您也可以这样做:

LOG(LEVEL(MoonPhase() == kFullMoon ? turbo::LogSeverity::kFatal
: turbo::LogSeverity::kError))
<< "Spooky error!";

VLOG()

VLOG()(“详细日志”)用于运行时可配置的调试日志记录。这宏采用非负整数详细级别作为参数 - INFO 暗示了严重性。详细级别值是任意的,但是较低的值对应于更容易看到的消息。非零详细级别是默认情况下禁用, 并且禁用VLOG()的性能成本非常小,因此,在 Kumo 的大部分部分中,自由使用“VLOG()”是可以接受的,没有风险 严重的性能下降。

Foo::Foo(int num_bars) {
VLOG(4) << "Constructing a new Foo with " << num_bars << " Bars";
for (int i = 0; i < num_bars; i++) bars_.push_back(MakeBar(this));
}

设置“--verbosity”标志将打开位于或低于该位置的所有VLOG()消息指定级别。这可能会使您的日志难以阅读和/或填写 你的磁盘。 --vmodule 标志允许为不同的模块设置不同的级别源文件;它需要一个以逗号分隔的key=value对列表,其中每个 键是与文件名匹配的全局变量,每个值都是详细级别这应该对匹配文件有效。详细日志记录级别也可以 在运行时使用 turbo::set_vlog_levelturbo::set_global_vlog_level 进行更改:

class FooTest : public testing::Test {
protected:
FooTest() {
// Crank up the `VLOG()` level for `Foo` since it does not log much otherwise:
turbo::set_vlog_level("foo_impl", 4);
}
};

其他宏变体

日志记录 API 包含许多用于特殊情况的附加宏。

  • QCHECK() 的工作方式与 CHECK() 类似,但与 QFATAL 和 QFATAL 具有相同的变体。 FATAL:它不会记录堆栈跟踪或运行 atexit() 处理程序 失败。

     int main (int argc, char**argv) {
    turbo::ParseCommandLine(argc, argv);
    QCHECK(!turbo::get_flag(FLAGS_path).empty()) << "--path is required";
    ...
  • PLOG()PCHECK() 自动将描述 errno 的字符串附加到 记录的消息。它们对于设置的系统库调用很有用失败时的errno指示失败的性质。他们的名字是 旨在与perror库函数保持一致。


    const int fd = open(path.c_str(), O_RDONLY);
    PCHECK(fd != -1) << "Failed to open " << path;

    const ssize_t bytes_read = read(fd, buf, sizeof(buf));
    PCHECK(bytes_read != -1) << "Failed to read from " << path;

    const int close_ret = close(fd);
    if (close_ret == -1) PLOG(WARNING) << "Failed to close " << path;
  • DLOG()(“调试日志”)和DCHECK()从二进制文件中完全消失在优化的构建中。请注意,DLOG(FATAL)DCHECK() 有很大的差异。 与LOG(DFATAL)不同的语义。
    调试日志记录有助于提供调试测试时有用的信息但在生产中收集(例如获取竞争锁)成本高昂:

DLOG(INFO) << server.State();

小心DCHECK();如果值得在测试中检查它可能是也值得在生产中检查:

DCHECK(ptr != nullptr);
ptr->Method();

DCHECK有时对于检查非常热的不变量很有用代码路径,其中必须假设测试中的检查来验证中的行为生产。
就像 assert() 一样,确保不要依赖于副作用的评估在DCHECKDLOG语句中:

DCHECK(server.Start());
// In an optimized build, no attempt will have been made to start the
// server!
    • LOG_IF() 添加了一个条件参数,相当于 if陈述。与if和三元运算符一样,条件将是 根据上下文转换为bool。还有PLOG_IF()DLOG_IF()变体存在。
    LOG_IF(INFO, turbo::get_flag(FLAGS_dry_run))
    << "--dry_run set; no changes will be made";
  • LOG_EVERY_N()LOG_FIRST_N()LOG_EVERY_N_SEC()LOG_EVERY_POW_2() 添加了更复杂的条件,这些条件不容易实现 用一个简单的if语句复制。其中每一个都维护一个静态存储中的每条语句状态对象,用于确定是否到了重新登录的时间。它们是线程安全的。
    令牌COUNTER可以流式传输到这些中;它将被替换为单调递增执行次数通过这个声明,包括记录发生的时间和没有的时候。具有附加条件的宏变体(例如 LOG_IF_EVERY_N()) 也存在,与 VLOG() 的许多组合也是如此,PLOG()DLOG()
    LOG_EVERY_N(WARNING, 1000) << "Got a packet with a bad CRC (" << COUNTER
    << " total)";

变异方法

LOG()CHECK() 宏支持一些可以改变它们的可链接方法行为。

  • .AtLocation(std::string_view 文件,int 行)
    覆盖从调用站点推断的位置。指向的字符串file 在语句结束之前必须有效。

  • .NoPrefix()
    省略此行中的 前缀。前缀包含元数据关于记录的数据,例如源代码位置和时间戳。

  • .WithVerbosity(int verbose_level)
    设置记录消息的详细程度字段,就像它是由VLOG(详细级别)。与“VLOG()”不同,此方法不影响是否 当指定的"verbose_level”被禁用时,语句将被评估。唯一的影响是使用“LogSink”实现 turbo::LogSink::verbosity() 值。价值 可以指定turbo::LogEntry::kNoVerbosityLevel来标记消息不

  • .WithTimestamp(turbo::Time timestamp)
    使用指定的时间戳而不是当时收集的时间戳 执行。

  • .WithThreadID(turbo::LogEntry::tid_t tid)
    使用指定的线程 ID,而不是在执行时收集的线程 ID 执行。

  • .WithMetadataFrom(const turbo::LogEntry &entry)
    从指定的 turbo::LogEntry 复制所有元数据(但不复制数据)。
    这可用于更改消息的严重性,但它有一些 限制:

    • TURBO_MIN_LOG_LEVEL 根据传递到 LOG 的严重性进行评估 (或“CHECK”的隐式“FATAL”级别)。
    • LOG(FATAL)CHECK 无条件终止进程,即使 严重程度稍后会更改。
  • .WithPerror()
    在记录的消息中附加冒号、空格、文本描述 errno 的当前值(如 strerror(3)),以及 错误。结果与PLOG()PCHECK()相当。

  • .ToSinkAlso(turbo::LogSink* sink)
    将此消息发送到*sink以及它会发送的任何其他接收器 否则已发送至。 sink 不能为空。

  • .ToSinkOnly(turbo::LogSink* sink)
    将此消息发送到*sink,而不发送到其他消息。 sink 不能为空。

日志消息输出

日志前缀

每条消息均使用以下形式的元数据进行记录:

I0926 09:00:00.000000   12345 foo.cc:10] Hello world!

前缀以I开头,代表INFO日志级别,组合日期为0926。时间以微秒为单位,在机器本地 时区。 12345 是线程 ID 号。 foo.cc:10 是源代码LOG()语句出现的位置,括号和空格是 消息本身之前的固定分隔符。

可以使用FLAGS_log_with_prefix标志全局抑制前缀,或者使用单个消息.NoPrefix()mutator 方法

stderr 输出

默认情况下,包含并注册了写入stderrLogSink。globals.h 声明了一些旋钮来控制该接收器的严重性级别 写入 stderr 并丢弃。