muduo网络库学习笔记(8):高效日志类的封装

2023-11-03

前言

在服务端编程中,日志是必不可少的。
开发过程中,日志的存在能方便我们调试错误和更好地理解程序;运行过程中,日志能帮助我们诊断系统故障并处理、记录系统运行状态。

muduo日志类封装细节

(1)日志消息有多种级别(level),如TRACE、DEBUG、INFO、WARN、ERROR、FATAL。日志的输出级别在运行时可调。

代码片段1:返回当前日志级别
文件名:Logging.cc

Logger::LogLevel initLogLevel()
{
  if (::getenv("MUDUO_LOG_TRACE"))  // 获取环境变量MUDUO_LOG_TRACE
    return Logger::TRACE;
  else if (::getenv("MUDUO_LOG_DEBUG"))
    return Logger::DEBUG;
  else
    return Logger::INFO;
}

(2)日志类Logger的使用流程
Logger使用时序图如下:
这里写图片描述

Logger类主要负责日志的级别等,它的内部嵌套类Impl(疑问:为什么要采用内部嵌套类的设计?)则负责实际的实现。使用时,首先构造一个匿名的Logger对象,然后调用stream()函数返回一个LogStream对象,LogStream对象再调用重载的<<运算符来输出日志。事实上,日志先输出到缓冲区,然后才输出到标准输出或文件。匿名的Logger对象在销毁时调用析构函数,析构函数调用g_output和g_flush输出到日志对应的设备。

代码片段2:Logger的析构函数
文件名:Logging.cc

Logger::~Logger()
{
  impl_.finish();
  const LogStream::Buffer& buf(stream().buffer());  // 获取缓冲区
  g_output(buf.data(), buf.length());  // 默认输出到stdout

  // 当日志级别为FATAL时,flush设备缓冲区并终止程序
  if (impl_.level_ == FATAL) 
  {
    g_flush();
    abort();
  }
}

Logger类的使用示例:

#define LOG_INFO if (muduo::Logger::logLevel() <= muduo::Logger::INFO) \
  muduo::Logger(__FILE__, __LINE__).stream()

LOG_INFO << "info ..."; // 使用方式
muduo::Logger(__FILE__, __LINE__).stream() << "info ...";  // 传递代码所在的文件名和行号参数

(3)重载<<运算符
以输出int类型的<<运算符为例,它并不是直接存放int类型的数据,而是转换为string类型后再存放到buffer:

代码片段3:重载<<运算符
文件名:LogStream.cc

......

// 通过调用convert函数将整数转换为字符串
template<typename T>
size_t convert(char buf[], T value)
{
  T i = value;
  char* p = buf;

  do
  {
    int lsd = static_cast<int>(i % 10);  // 得到最后一个数字,last digit
    i /= 10;
    // const char digits[] = "9876543210123456789";
    // (疑问:digits数组为什么是"9876543210123456789",而不直接赋为"0123456789"?)
    // const char* zero = digits + 9;
    // 假如此时获取的lsd值为5,指针zero指向digits[]中的'0'
    // zero[lsd]再偏移lsd即5个位置,便获取到了字符'5',保存到了buf中
    *p++ = zero[lsd];
  } while (i != 0);

  // 为负数则添加负号
  if (value < 0)
  {
    *p++ = '-';
  }
  *p = '\0';
  std::reverse(buf, p);  // 将字符串逆转

  return p - buf;
}

......

template<typename T>
void LogStream::formatInteger(T v)
{
  // kMaxNumericSize的值为32,即如果buffer的空间足够大
  if (buffer_.avail() >= kMaxNumericSize)
  {
    size_t len = convert(buffer_.current(), v);
    buffer_.add(len);
  }
}

......

LogStream& LogStream::operator<<(int v)
{
  formatInteger(v);  // 调用formatInteger()函数
  return *this;  // 返回LogStream对象的指针
}

(4)FixedBuffer的设计
这里写图片描述
FixedBuffer的实现为一个模板类,传入一个非类型参数SIZE表示缓冲区的大小。通过成员 data_首地址、cur_指针、end()完成对缓冲区的各项操作,例如:

代码片段4:模版类FixedBuffer的成员函数avail()返回当前可用的空间
文件名:LogStream.h

int avail() const 
{ 
    return static_cast<int>(end() - cur_); 
}

(5)日志滚动
muduo库日志滚动的条件通常有两个:
文件大小 - 例如每写满1G换下一个文件
时间 - 例如每天零点新建一个文件,不管前一个文件是否写满

I.日志文件文件名的设计
例:logfile_test.20120603-144022.hostname.3605.log

第一部分如“logfile_test”是日志文件的basename;
第二部分如“20120603-144022”是日志的创建时间(UTC时间);
第三部分如“hostname”是主机名称;
第四部分如“3605”是进程id;
最后是日志后缀名“.log”。

代码片段5:获取日志文件名
文件名:LogFile.cc

string LogFile::getLogFileName(const string& basename, time_t* now)
{
  string filename;
  // 预留basename的size加上64字节的空间
  filename.reserve(basename.size() + 64);
  filename = basename;

  char timebuf[32];
  char pidbuf[32];
  struct tm tm;
  *now = time(NULL);
  gmtime_r(now, &tm);  // 线程安全,获取日志创建时间
  strftime(timebuf, sizeof timebuf, ".%Y%m%d-%H%M%S.", &tm);  // 将时间格式化
  filename += timebuf;
  filename += ProcessInfo::hostname();  // 用到了gethostname()返回主机名
  snprintf(pidbuf, sizeof pidbuf, ".%d", ProcessInfo::pid());
  filename += pidbuf;
  filename += ".log";

  return filename;
}

II.日志的滚动实现

代码片段6:日志的滚动
文件名:LogFile.cc

void LogFile::rollFile()
{
  time_t now = 0;
  string filename = getLogFileName(basename_, &now);

  // 注意,这里先除以kRollPerSeconds_、后乘kRollPerSeconds_表示
  // 对齐至kRollPerSeconds_(24*60*60)整数倍,也就是时间调整到当天零点。
  time_t start = now / kRollPerSeconds_ * kRollPerSeconds_;

  // 如果now大于上一次滚动日志文件时间就滚动
  if (now > lastRoll_)
  {
    lastRoll_ = now;  // lastRoll_是上一次滚动日志文件时间
    lastFlush_ = now;  // lastFlush_是上一次日志写入文件时间
    startOfPeriod_ = start;  // startOfPeriod_是开始记录日志时间(调整至零点的时间)
    file_.reset(new File(filename));
  }
}
代码片段7:写入日志时,判断是否需要滚动日志
文件名:LogFile.cc

void LogFile::append_unlocked(const char* logline, int len)
{
  file_->append(logline, len);

  // 写入的字节数大于rollSize_时要滚动
  if (file_->writtenBytes() > rollSize_)
  {
    rollFile();
  }
  else
  { 
    // 计数值count_超过kCheckTimeRoll_时也要判断是否需要滚动
    if (count_ > kCheckTimeRoll_)
    {
      count_ = 0;
      time_t now = ::time(NULL);
      time_t thisPeriod_ = now / kRollPerSeconds_ * kRollPerSeconds_;
      if (thisPeriod_ != startOfPeriod_)
      {
        rollFile();
      }
      // 大于flush的间隔时间时则写入日志,不滚动
      else if (now - lastFlush_ > flushInterval_)
      {
        lastFlush_ = now;
        file_->flush();
      }
    }
    else
    {
      ++count_;
    }
  }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

muduo网络库学习笔记(8):高效日志类的封装 的相关文章

随机推荐

  • 《Kafka权威指南》——初识 Kafka

    发布与订阅消息系统 在正式讨论Apache Kafka 以下简称Kafka 之前 先来了解发布与订阅消息系统的概念 并认识这个系统的重要性 数据 消息 的发送者 发布者 不会直接把消息发送给接收 者 这是发布与订阅消息系统的一个特点 发布者
  • 性能测试很难吗?一文带你学会性能测试核心流程和概念

    在很多人的概念中 性能测试就是使用Loadrunner Jmeter等工具进行压测 然后得到测试结果即可 但仔细想想 对谁进行测试 测试目的是什么 监测指标有哪些 得到的结果如何分析 怎样的结果算通过 等等 所以使用工具压测只是性能测试最基
  • 泰迪杯C题第二问

    面包多 https mianbaoduo com o bread Ypmbl5Zq
  • java 8 新特性,接口默认方法、lambda表达式、Optional、stream

    java 8 新特性 当面试官让我说几个java 8 的新特性 我巴拉巴拉把知道的都说了 然而 面试官接着问 stream里面如果按照分类过滤怎么做呢 map 是什么 嘀 扫码成功 哎呀 地铁里面的空调真不错啊 真不错 果然 只做到了解是不
  • qt常见问题

    1所有能够接受和发送消息的class必须存在有消息循环的线程环境中 2对象的消息处理默认环境是是存在于创建这个对象的线程环境中的 对于第二点如何理解 比如 subthread subthread connect this sigA this
  • Chisel学习2构建过程与测试

    要开始学习更有趣的Chisel代码 我们首先需要学习如何编译Chisel程序 如何生成在FPGA中执行的Verilog代码 以及如何编写调试测试并验证我们的电路是正确的 Chisel是用Scala编写的 因此任何支持Scala的构建过程都可
  • 基于Vue三大互联网企业级开箱即用中后台解决方案对比字节跳动Arco Design Pro,蚂蚁集团Ant Design Pro和腾讯TDesign Starter

    基于Vue中后台解决方案越来越多开源了 随着各大互联网企业的Vue UI框架开源和普及后 都又有新的中后台解决方案也开源 方便开发者使用了 下面来作一个主观和客观的对比一下 字节跳动Arco Pro 蚂蚁集团Ant Design Pro 腾
  • Android 新增一个自定义分区

    在某个项目中 有一个需求 需要新增一个xxx分区 这个分区类似于vendor oem分区 名字为指定的 此处有点好奇 为什么不直接使用oem分区 而是另外弄一个分区名出来 功能实现点 在root目录下新增分区的挂载目录 将自定义分区的内容生
  • Flask-数据库-SQLAlchemy

    SQLAlchemy是flask的扩展 是一个功能强大的OR映射器 支持多种数据库后台 可以将类的对象映射至数据库表 使用这个工具 可以创建数据库 创建表 并为数据库添加数据 进行查询操作等 参考 Flask SQLAlchemy Flas
  • kafka(一)kafka的基础与常用配置

    文章目录 一 kafka基础内容 二 kafka 中重要的参数配置 2 1 log dirs 2 2 unclean leader election enable 2 3 message max bytes 2 4 request requ
  • 基于LMI的输出反馈H∞控制及其仿真(含实现程序)

    目录 1 H 输出反馈控制 1 1 框架结构 1 2 广义系统 1 3 等价的LMI问题 2 参考文献 3 文献案例实现 3 1 二阶系统 3 2 H 最优输出反馈控制器 3 2 1 程序 3 1 2 时域和频域分析 3 3 H 次优输出反
  • 阿里Java学习路线:阶段 2:数据库开发-数据库及SQL/MySQL基础:课时8:DDL(数据定义语言)之操作数据库

    3 DDL 数据定义语言 3 1 基本操作 l 查看所有数据库名称 SHOW DATABASES l 切换数据库 USE mydb1 切换到mydb1数据库 3 2 操作数据库 l 创建数据库 CREATE DATABASE IF NOT
  • openai-chatGPT的API调用异常处理

    因为目前openai对地区限制的原因 即使设置了全局代理使用API调用时 还是会出现科学上网代理的错误问题 openai库 0 26 5 错误提示 raise error APIConnectionError openai error AP
  • 程序包org.apache.tools.zip不存在

    可以看出 org apache tools zip 是 ant jar里面的 然后在pom配置文件里面加上
  • LocalDateTime 相关方法

    文章目录 1 获取日期 时间 日期时间 2 获取年月日时分秒 3 给定数值生成日期时间 4 格式化 日期时间 gt 字符串 5 解析 字符串 gt 日期时间 6 设定具体值 7 日期时间的加减 8 小测试 日期 gt 日期时间 注意 本文中
  • 2021年9月电子学会Python等级考试试卷(三级)答案解析

    青少年软件编程 Python 等级考试试卷 三级 分数 100 00 题数 38 一 单选题 共25题 每题2分 共50分 1 使用map函数可以实现列表数据元素类型的转换 而无需通过循环 则将列表L 1 3 5 7 9 转换为列表 1 3
  • 微信小程序大作业 咖啡小店 适合初学者使用

    咖啡小店微信小程序 能正常运行无错误 含导航栏 轮播 咖啡售卖等等功能 详情如下图所示 下载链接在文末 点我下载资源 https download csdn net download weixin 43474701 58819009
  • c语言strcat()/strcat_s()函数详解

    前言 先看下strcat strcat s 函数调用报错 放大一点 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 C4996 strcat This function or variable may be unsafe Consi
  • 环境篇-Linux下安装OpenSSL

    本文属于 OpenSSL加密算法库使用系列教程 之一 欢迎查看其它文章 linux下 一般系统都自带了OpenSSL 可以直接使用 包括 终端直接使用OpenSSL命令 直接调用OpenSSL库 故 一般不需要再安装 若系统中没有安装Ope
  • muduo网络库学习笔记(8):高效日志类的封装

    前言 在服务端编程中 日志是必不可少的 开发过程中 日志的存在能方便我们调试错误和更好地理解程序 运行过程中 日志能帮助我们诊断系统故障并处理 记录系统运行状态 muduo日志类封装细节 1 日志消息有多种级别 level 如TRACE D