【brpc学习实践十】streaming log实战

news2024/11/26 7:47:08

实战实例

通常我们在服务还没正式起来时,会用brpc流式log打印,支持对日志输出到ostream对象中(默认std)。同时会在服务初始化时配置LogSink,实现自己的log,这样后续都可以将输出重定向至自己的log.

int init(int argc, char** argv, const char* product_name, const char* mod_name) {
    // 读日志文件配置(设置baidu-rpc底层使用com_log)
    logging::SetLogSink(logging::ComlogSink::GetInstance());
    google::SetCommandLineOption("flagfile", "conf/gflags.conf");

    // config
    std::string conf_dir = "./conf";
    std::string conf_comlog = "log.conf";
 
    int ret = E_ERROR;
    do {
        // comlog加载
        std::string comlog_conf_file = conf_dir + "/" + conf_comlog;
        ret = logging::ComlogSink::GetInstance()->SetupFromConfig(comlog_conf_file.c_str());
        if (0 != ret) {
            LOG(FATAL) << "init log by conf:./conf/log.conf failed"; 
            break;
        }
        //LogRecordFactory conf
        comcfg::Configure log_conf;
        int ret = log_conf.load(conf_dir.c_str(), conf_comlog.c_str());
        if (0 != ret) {
            LOG(FATAL) << "Fail to load log conf file:" << conf_dir << "/" << conf_comlog;
            break;
        }
        bus::log::LogRecordFactory local_data_factory;
        ret = local_data_factory.init(log_conf);
        if (0 != ret) {
            LOG(FATAL) << "Init LogRecordFactory failed";
            break;
        }
        LOG(NOTICE) << "Init LogRecordFactory Succeed";
    } while(false);

Name

streaming_log - Print log to std::ostreams

SYNOPSIS

#include <butil/logging.h>

LOG(FATAL) << "Fatal error occurred! contexts=" << ...;
LOG(WARNING) << "Unusual thing happened ..." << ...;
LOG(TRACE) << "Something just took place..." << ...;
LOG(TRACE) << "Items:" << noflush;
LOG_IF(NOTICE, n > 10) << "This log will only be printed when n > 10";
PLOG(FATAL) << "Fail to call function setting errno";
VLOG(1) << "verbose log tier 1";
CHECK_GT(1, 2) << "1 can't be greater than 2";
 
LOG_EVERY_SECOND(INFO) << "High-frequent logs";
LOG_EVERY_N(ERROR, 10) << "High-frequent logs";
LOG_FIRST_N(INFO, 20) << "Logs that prints for at most 20 times";
LOG_ONCE(WARNING) << "Logs that only prints once";

DESCRIPTION

流式日志是打印复杂对象或模板对象的不二之选。大部分业务对象都很复杂,如果用printf形式的函数打印,你需要先把对象转成string,才能以%s输出。但string组合起来既不方便(比如没法append数字),还得分配大量的临时内存(string导致的)。C++中解决这个问题的方法便是“把日志流式地送入std::ostream对象”。比如为了打印对象A,那么我们得实现如下的函数:

std::ostream& operator<<(std::ostream& os, const A& a);

这个函数的意思是把对象a打印入os,并返回os。之所以返回os,是因为operator<<对应了二元操作 << (左结合),当我们写下os << a << b << c;时,它相当于operator<<(operator<<(operator<<(os, a), b), c); 很明显,operator<<需要不断地返回os(的引用),才能完成这个过程。这个过程一般称为chaining。在不支持重载二元运算符的语言中,你可能会看到一些更繁琐的形式,比如os.print(a).print(b).print©。

我们在operator<<的实现中也使用chaining。事实上,流式打印一个复杂对象就像DFS一棵树一样:逐个调用儿子节点的operator<<,儿子又逐个调用孙子节点的operator<<,以此类推。比如对象A有两个成员变量B和C,打印A的过程就是把其中的B和C对象送入ostream中:

struct A {
    B b;
    C c;
};
std::ostream& operator<<(std::ostream& os, const A& a) {
    return os << "A{b=" << a.b << ", c=" << a.c << "}";
}

B和C的结构及打印函数分别如下:

struct B {
    int value;
};
std::ostream& operator<<(std::ostream& os, const B& b) {
    return os << "B{value=" << b.value << "}";
}
 
struct C {
    string name;
};
std::ostream& operator<<(std::ostream& os, const C& c) {
    return os << "C{name=" << c.name << "}";
}

那么打印某个A对象的结果可能是

A{b=B{value=10}, c=C{name=tom}}

在打印过程中,我们不需要分配临时内存,因为对象都被直接送入了最终要送入的那个ostream对象。当然ostream对象自身的内存管理是另一会儿事了。

OK,我们通过ostream把对象的打印过程串联起来了,最常见的std::cout和std::cerr都继承了ostream,所以实现了上面函数的对象就可以输出到std::cout和std::cerr了。换句话说如果日志流也继承了ostream,那么那些对象也可以打入日志了。流式日志正是通过继承std::ostream,把对象打入日志的,在目前的实现中,送入日志流的日志被记录在thread-local的缓冲中,在完成一条日志后会被刷入屏幕或logging::LogSink,这个实现是线程安全的。

LOG

如果你用过glog,应该是不用学习的,因为宏名称和glog是一致的,如下打印一条FATAL。注意不需要加上std::endl。

LOG(FATAL) << "Fatal error occurred! contexts=" << ...;
LOG(WARNING) << "Unusual thing happened ..." << ...;
LOG(TRACE) << "Something just took place..." << ...;

streaming log的日志等级与glog映射关系如下:
在这里插入图片描述

PLOG

PLOG和LOG的不同之处在于,它会在日志后加上错误码的信息,类似于printf中的%m。在posix系统中,错误码就是errno。

int fd = open("foo.conf", O_RDONLY);   // foo.conf does not exist, errno was set to ENOENT
if (fd < 0) { 
    PLOG(FATAL) << "Fail to open foo.conf";    // "Fail to open foo.conf: No such file or directory"
    return -1;
}

noflush

如果你暂时不希望刷到屏幕,加上noflush。这一般会用在打印循环中:

LOG(TRACE) << "Items:" << noflush;
for (iterator it = items.begin(); it != items.end(); ++it) {
    LOG(TRACE) << ' ' << *it << noflush;
}
LOG(TRACE);

前两次TRACE日志都没有刷到屏幕,而是还记录在thread-local缓冲中,第三次TRACE日志则把缓冲都刷入了屏幕。如果items里面有三个元素,不加noflush的打印结果可能是这样的:

TRACE: ... Items:
TRACE: ...  item1
TRACE: ...  item2
TRACE: ...  item3

加了是这样的:

TRACE: ... Items: item1 item2 item3 

noflush支持bthread,可以实现类似于UB的pushnotice的效果,即检索线程一路打印都暂不刷出(加上noflush),直到最后检索结束时再一次性刷出。注意,如果检索过程是异步的,就不应该使用noflush,因为异步显然会跨越bthread,使noflush仍然失效。

注意:如果编译时开启了glog选项,则不支持noflush。

LOG_IF

LOG_IF(log_level, condition)只有当condition成立时才会打印,相当于if (condition) { LOG() << …; },但更加简短。比如:

LOG_IF(NOTICE, n > 10) << "This log will only be printed when n > 10";

XXX_EVERY_SECOND

XXX可以是LOG,LOG_IF,PLOG,SYSLOG,VLOG,DLOG等。这类日志每秒最多打印一次,可放在频繁运行热点处探查运行状态。第一次必打印,比普通LOG增加调用一次gettimeofday(30ns左右)的开销。

LOG_EVERY_SECOND(INFO) << "High-frequent logs";

XXX_EVERY_N

XXX可以是LOG,LOG_IF,PLOG,SYSLOG,VLOG,DLOG等。这类日志每触发N次才打印一次,可放在频繁运行热点处探查运行状态。第一次必打印,比普通LOG增加一次relaxed原子加的开销。这个宏是线程安全的,即不同线程同时运行这段代码时对N的限制也是准确的,glog中的不是。

LOG_EVERY_N(ERROR, 10) << "High-frequent logs";

XXX_FIRST_N

XXX可以是LOG,LOG_IF,PLOG,SYSLOG,VLOG,DLOG等。这类日志最多打印N次。在N次前比普通LOG增加一次relaxed原子加的开销,N次后基本无开销。

LOG_FIRST_N(ERROR, 20) << "Logs that prints for at most 20 times";

XXX_ONCE

XXX可以是LOG,LOG_IF,PLOG,SYSLOG,VLOG,DLOG等。这类日志最多打印1次。等价于XXX_FIRST_N(…, 1)

LOG_ONCE(ERROR) << "Logs that only prints once";

VLOG

VLOG(verbose_level)是分层的详细日志,通过两个gflags:–verbose和*–verbose_module*控制需要打印的层(注意glog是–v和–vmodule)。只有当–verbose指定的值大于等于verbose_level时,对应的VLOG才会打印。比如

VLOG(0) << "verbose log tier 0";
VLOG(1) << "verbose log tier 1";
VLOG(2) << "verbose log tier 2";

当–verbose=1时,前两条会打印,最后一条不会。–verbose_module可以覆盖某个模块的级别,模块指去掉扩展名的文件名或文件路径。比如:

--verbose=1 --verbose_module="channel=2,server=3" # 打印channel.cpp中<=2,server.cpp中<=3,其他文件<=1的VLOG
–verbose=1 --verbose_module=“src/brpc/channel=2,server=3” # 当不同目录下有同名文件时,可以加上路径

–verbose和–verbose_module可以通过google::SetCommandLineOption动态设置。

VLOG有一个变种VLOG2让用户指定虚拟文件路径,比如:

// public/foo/bar.cpp
VLOG2("a/b/c", 2) << "being filtered by a/b/c rather than public/foo/bar";
VLOG和VLOG2也有相应的VLOG_IF和VLOG2_IF。

DLOG

所有的日志宏都有debug版本,以D开头,比如DLOG,DVLOG,当定义了NDEBUG后,这些日志不会打印。

千万别在D开头的日志流上有重要的副作用。

“不会打印”指的是连参数都不会评估。如果你的参数是有副作用的,那么当定义了NDEBUG后,这些副作用都不会发生。比如DLOG(FATAL) << foo(); 其中foo是一个函数,它修改一个字典,反正必不可少,但当定义了NDEBUG后,foo就运行不到了。
CHECK

日志另一个重要变种是CHECK(expression),当expression为false时,会打印一条FATAL日志。类似gtest中的ASSERT,也有CHECK_EQ, CHECK_GT等变种。当CHECK失败后,其后的日志流会被打印。

CHECK_LT(1, 2) << "This is definitely true, this log will never be seen";
CHECK_GT(1, 2) << "1 can't be greater than 2";

运行后你应该看到一条FATAL日志和调用处的call stack:

FATAL: ... Check failed: 1 > 2 (1 vs 2). 1 can't be greater than 2
#0 0x000000afaa23 butil::debug::StackTrace::StackTrace()
#1 0x000000c29fec logging::LogStream::FlushWithoutReset()
#2 0x000000c2b8e6 logging::LogStream::Flush()
#3 0x000000c2bd63 logging::DestroyLogStream()
#4 0x000000c2a52d logging::LogMessage::~LogMessage()
#5 0x000000a716b2 (anonymous namespace)::StreamingLogTest_check_Test::TestBody()
#6 0x000000d16d04 testing::internal::HandleSehExceptionsInMethodIfSupported<>()
#7 0x000000d19e96 testing::internal::HandleExceptionsInMethodIfSupported<>()
#8 0x000000d08cd4 testing::Test::Run()
#9 0x000000d08dfe testing::TestInfo::Run()
#10 0x000000d08ec4 testing::TestCase::Run()
#11 0x000000d123c7 testing::internal::UnitTestImpl::RunAllTests()
#12 0x000000d16d94 testing::internal::HandleSehExceptionsInMethodIfSupported<>()

callstack中的第二列是代码地址,你可以使用addr2line查看对应的文件行数:

$ addr2line -e ./test_base 0x000000a716b2 
/home/gejun/latest_baidu_rpc/public/common/test/test_streaming_log.cpp:223

你应该根据比较关系使用具体的CHECK_XX,这样当出现错误时,你可以看到更详细的信息,比如:

int x = 1;
int y = 2;
CHECK_GT(x, y);  // Check failed: x > y (1 vs 2).
CHECK(x > y);    // Check failed: x > y.

和DLOG类似,你不应该在DCHECK的日志流中包含重要的副作用。

LogSink

streaming log通过logging::SetLogSink修改日志刷入的目标,默认是屏幕。用户可以继承LogSink,实现自己的日志打印逻辑。我们默认提供了个LogSink实现:

StringSink

同时继承了LogSink和string,把日志内容存放在string中,主要用于单测,这个case说明了StringSink的典型用法:

TEST_F(StreamingLogTest, log_at) {
::logging::StringSink log_str;
::logging::LogSink* old_sink = ::logging::SetLogSink(&log_str);
LOG_AT(FATAL, “specified_file.cc”, 12345) << “file/line is specified”;
// the file:line part should be using the argument given by us.
ASSERT_NE(std::string::npos, log_str.find(“specified_file.cc:12345”));
// restore the old sink.
::logging::SetLogSink(old_sink);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1255183.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

北塞浦路斯土耳其共和国关于成立欧洲数字股票交易所企业交流会

在地中海的温暖波涛中&#xff0c;北塞浦路斯土耳其共和国这个古老而充满活力的国家正成为全球关注的焦点。2023年11月22日至11月24日&#xff0c;为期三天的北塞浦路斯土耳其共和国关于成立欧洲数字股票交易所企业交流会隆重谢幕&#xff0c;北塞副总统&#xff0c;经济部长&a…

【C++】类型转换 ③ ( 重新解释类型转换 reinterpret_cast | 指针类型数据转换 )

文章目录 一、重新解释类型转换 reinterpret_cast1、指针数据类型转换 - C 语言隐式类型转换报错 ( 转换失败 )2、指针数据类型转换 - C 语言显示类型强制转换 ( 转换成功 )3、指针数据类型转换 - C 静态类型转换 static_cast ( 转换失败 )4、指针数据类型转换 - C 重新解释类型…

思维模型 古烈治效应

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。见异思迁。 1 古烈治效应的应用 1.1 古烈治效应之心理学研究 在一项研究中&#xff0c;研究者让男性和女性参与者分别观看一系列异性的照片&#xff0c;并评估他们的吸引力。在观看完所有…

ns-3安装教程

1️⃣ VMware安装Ubuntu虚拟机 VMware安装Ubuntu 18.04虚拟机 6.桥接模式网络配置往后都不看 VMware16 安装Ubuntu22.04.1 服务器版操作系统教程 这块主要看怎么从清华镜像下载ubuntu 本人安装的是ubuntu22.04.3 2️⃣ 安装ns3 【ns-3】零基础安装教程 安装依赖库时遇到了问题 …

1998-2021年全国各省PM2.5平均浓度数据

1998-2021年全国各省PM2.5平均浓度数据 1、时间&#xff1a;1998-2021年 2、指标&#xff1a;省、省代码、年份、均值、总和、最小值、最大值、标准差 3、来源&#xff1a;华盛顿大学大气成分分析小组 4、范围&#xff1a;34省市 5、指标说明&#xff1a; PM2.5是指大气中…

Mybatis反射核心类Reflector

Reflector类负责对一个类进行反射解析&#xff0c;并将解析后的结果在属性中存储起来。 一个类反射解析后都有哪些属性呢&#xff1f;我们可以通过Reflector类定义的属性来查看 public class Reflector {// 要被反射解析的类private final Class<?> type;// 可读属性列…

linux系统初始化本地git,创建ssh-key

step1, 在linux系统配置你的git信息 sudo apt install -y git//step1 git config --global user.name your_name // github官网注册的用户名 git config --global user.email your_email //gitub官网注册绑定的邮箱 git config --list //可以查看刚才你的配置内容…

前端实现埋点

前端实现埋点 如何去了解用户呢&#xff1f;最直接有效的方式就是了解用户的行为&#xff0c;了解用户在网站中做了什么&#xff0c;呆了多久。而如何去实现这一操作&#xff0c;这就涉及到我们前端的埋点了。 埋点方式 什么是埋点&#xff1f; 所谓埋点是数据采集领域&…

【限时免费】20天拿下华为OD笔试之【DP/贪心】2023B-观看文艺汇演-200分【欧弟算法】全网注释最详细分类最全的华为OD真题题解

【DP/贪心】2023B-观看文艺汇演 题目描述与示例 某公园将举行多场文艺表演&#xff0c;很多演出都是同时进行&#xff0c;一个人只能同时观看一场演出&#xff0c;且不能迟到早退&#xff0c;由于演出分布在不同的演出场地&#xff0c;所以连续观看的演出最少有 15 分钟的时间…

难怪被人卷了不知道啊!这么学自动化测试,一个星期就搞定了!!!

目前自动化测试并不属于新鲜的事物&#xff0c;或者说自动化测试的各种方法论已经层出不穷&#xff0c;但是&#xff0c;能够明白自动化测试并很好落地实施的团队还不是非常多&#xff0c;我们接来下用通俗的方式来介绍自动化测试…… 首先我们从招聘岗位需求说起。看近期的职…

[论文阅读]CBAM——代码实现和讲解

CBAM 论文网址&#xff1a;CBAM 论文代码&#xff1a;CBAM 本文提出了一种卷积块注意力模块&#xff08;CBAM&#xff09;&#xff0c;它是卷积神经网络&#xff08;CNN&#xff09;的一种轻量级、高效的注意力模块。该模块沿着通道和空间两个独立维度依次推导注意力图&#x…

探索深度学习:从理论到实践的全面指南

探索深度学习&#xff1a;从理论到实践的全面指南 摘要&#xff1a; 本文旨在提供一个关于深度学习的全面指南&#xff0c;带领读者从理论基础到实践应用全方位了解这一技术。我们将介绍深度学习的历史、基本原理、常用算法和应用场景&#xff0c;并通过Python代码示例和Tens…

Grafana采用Nginx反向代理

一、场景介绍 在常规操作中&#xff0c;一般情况下不会放开许多端口给外部访问&#xff0c;特别是直接 ip:port 的方式开放访问。但是 Grafana 的请求方式在默认情况下是没有任何规律可寻的。 为了满足业务需求&#xff08;后续通过 Nginx 统一一个接口暴露 N 个服务&#xf…

WordPress最廉价优化整站的加载速度

为什么说一个站不优化就等于一个人做整个团队的事务导致项目进展慢&#xff0c;网站也是如此 图片、静态文件、php分离加速&#xff0c;加载速度并不是很快但是很协调比单个网站加载速度快许多 一、图片单域名加载设置上传文件路径和域名 以下代码添加在主题目录&#xff1a;fu…

PyQt6实战开发之旅-代码均可运行

学习感悟 由于官方文档是英文的&#xff0c;所以学习起来不是很直观。网上的中文教程也都有点偏重就轻&#xff0c;去从头学习细枝末节不是很必要。假如每个控件组件讲十分钟&#xff0c;几百个控件可想而知。最关键的是有python基础&#xff0c;能理解类与继承&#xff0c;函…

leetcode9.回文数

回文数 0.题目1.WJQ的思路2.实现过程2.0 原始值怎么一个个取出来&#xff1f;2.1 取出来的数如何存到新的数字后面&#xff1f;2.2完整的反转得到新数的过程 3.完整的代码4.可运行的代码5.算法还可以优化的部分 0.题目 给你一个整数 x &#xff0c;如果 x 是一个回文整数&…

基于STC12C5A60S2系列1T 8051单片按页写IIC总线器件24C02并显示在液晶显示器LCD1602上应用

基于STC12C5A60S2系列1T 8051单片机按页写IIC总线器件24C02并显示在液晶显示器LCD1602上应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍液晶显示器LCD1602简单介绍…

开通橱窗还能开抖店吗?怎么开通?一篇详解!

我是电商珠珠 开通商品橱窗之后还能开抖店吗&#xff1f;商品橱窗和抖音小店可以同时开吗&#xff1f; 一部分人最初的时候&#xff0c;都觉得直播带货很火&#xff0c;所以就自己去买粉丝或是发视频积攒粉丝&#xff0c;等粉丝够了发现&#xff0c;好像和当初想的不太一样&a…

docker (简介、dcoker详细安装步骤)- day01

一、 为什么出现 Docker是基于Go语言实现的云开源项目。 Docker的主要目标是“Build&#xff0c;Ship and Run Any App,Anywhere”&#xff0c;也就是通过对应用组件的封装、分发、部署、运行等生命周期的管理&#xff0c;使用户的APP&#xff08;可以是一个WEB应用或数据库应…

Java基于springboot+vue开发服装商城小程序

演示视频&#xff1a; 小程序 https://www.bilibili.com/video/BV1rM411o7m4/?share_sourcecopy_web&vd_source11344bb73ef9b33550b8202d07ae139b 管理员 https://www.bilibili.com/video/BV1fc411D7V3/?share_sourcecopy_web&vd_source11344bb73ef9b33550b8202d07ae…