muduo网络库学习(九)日志类Logger和LogStream,将日志信息打印到屏幕

2023-10-29

每一个成熟的项目都有大大小小的日志系统,在关键的地方打印日志信息,常用来跟踪程序运行,查找错误原因等,可以节省大量的debug时间

muduo的日志信息有5个级别

  • TRACE,细粒度最高的日志信息,打印的最详细
  • DEBUG,细粒度级别上对调试有帮助的日志信息
  • INFO,粗粒度级别上强调程序的运行信息
  • WARN,程序能正常运行,但存在潜在风险的信息
  • ERROR,执行出错,但不影响程序继续执行的错误信息
  • FATAL,将导致程序退出的严重信息

muduo的日志格式为
这里写图片描述


在muduo的很多源文件中,多存在着输出日志信息的语句,例如

  LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " << now.toString();
  LOG_DEBUG << "EventLoop created " << this << " in thread " << threadId_;
  LOG_INFO << "TcpServer::newConnection [" << name_
           << "] - new connection [" << connName
           << "] from " << peerAddr.toIpPort();
  LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP";
  LOG_ERROR << "TimerQueue::handleRead() reads " << n << " bytes instead of 8";
  LOG_FATAL << "Another EventLoop " << t_loopInThisThread
            << " exists in this thread " << threadId_;

这些形如LOG_*的调用实际上是宏定义

/* 
 * __FILE__:返回所在文件名
 * __LINE__:返回所在行数
 * __func__:返回所在函数名
 * 
 * 这些都是无名对象,当使用LOG_* << "***"时,
 * 1.构造Logger类型的临时对象,返回LogStream类型变量
 * 2.调用LogStream重载的operator<<操作符,将数据写入到LogStream的Buffer中
 * 3.当前语句结束,Logger临时对象析构,调用Logger析构函数,将LogStream中的数据输出
 */
#define LOG_TRACE if (muduo::Logger::logLevel() <= muduo::Logger::TRACE) \
  muduo::Logger(__FILE__, __LINE__, muduo::Logger::TRACE, __func__).stream()
#define LOG_DEBUG if (muduo::Logger::logLevel() <= muduo::Logger::DEBUG) \
  muduo::Logger(__FILE__, __LINE__, muduo::Logger::DEBUG, __func__).stream()
#define LOG_INFO if (muduo::Logger::logLevel() <= muduo::Logger::INFO) \
  muduo::Logger(__FILE__, __LINE__).stream()
#define LOG_WARN muduo::Logger(__FILE__, __LINE__, muduo::Logger::WARN).stream()
#define LOG_ERROR muduo::Logger(__FILE__, __LINE__, muduo::Logger::ERROR).stream()
#define LOG_FATAL muduo::Logger(__FILE__, __LINE__, muduo::Logger::FATAL).stream()
#define LOG_SYSERR muduo::Logger(__FILE__, __LINE__, false).stream()
#define LOG_SYSFATAL muduo::Logger(__FILE__, __LINE__, true).stream()

当使用LOG_*时,在编译期会被宏定义后面的语句替换。而实际上是创建了Logger的临时对象,创建后调用stream函数,可以猜测,stream函数返回的对象重载了operator << 函数,可以应对各种日志信息的输出,函数定义如下,实际返回的是LogStream对象

  /* 返回Impl的LogStream对象 */
  LogStream& stream() { return impl_.stream_; }

LogStream对象重载了各种operator << 函数,可以处理多种类型的信息。而在LogStream中,存在着一个缓冲区Buffer用于保存这些日志信息

/* 使用FixedBuffer,默认缓冲区大小为kSmallBuffer,重载各种operator<<操作 */
class LogStream : noncopyable
{
  typedef LogStream self;
 public:
  /* 缓冲区的类型,是个固定大小的缓冲区,由字符数组实现 */
  typedef detail::FixedBuffer<detail::kSmallBuffer> Buffer;

  /* 重载的operator<<函数,将日志信息存放在缓冲区中 */
  self& operator<<(const char* str)
  {
    if (str)
    {
      buffer_.append(str, strlen(str));
    }
    else
    {
      buffer_.append("(null)", 6);
    }
    return *this;
  }

  /* ... */

 private:

  /* 用于存储日志信息的缓冲区 */
  Buffer buffer_;

  static const int kMaxNumericSize = 32;
};

对于临时对象,所在语句结束后就被析构了,所以对于日志信息的输出,肯定都交给Logger对象的析构函数处理了
Logger的定义如下,Logger定义6个日志级别,分别对应不同的LOG_*。同时使用了Impl技法将对象和数据分离,Impl保存着所有Logger需要的数据。SourceFile保存着调用LOG_*语句所在的源文件名和行号,也是一个内部类。

class TimeZone;

class Logger
{
 public:
  /* 日志级别 */
  enum LogLevel
  {
    TRACE,  /* 细粒度最高的信息 */
    DEBUG,  /* 对调试有帮助的事件信息 */
    INFO,   /* 粗粒度级别上强调程序的运行信息 */
    WARN,   /* 程序能正常运行,但是有潜在危险的信息 */
    ERROR,  /* 程序出错,但是不影响系统运行的信息 */
    FATAL,  /* 将导致程序停止运行的严重信息 */
    NUM_LOG_LEVELS, /* 日志级别个数 */
  };

  // compile time calculation of basename of source file
  /* 内部类,保存调用LOG_WARN<<之类的那条语句所在文件名 */
  class SourceFile
  {
   public:
    template<int N>
    inline SourceFile(const char (&arr)[N])
      : data_(arr),
        size_(N-1)
    {
      const char* slash = strrchr(data_, '/'); // builtin function
      if (slash)
      {
        data_ = slash + 1;
        size_ -= static_cast<int>(data_ - arr);
      }
    }

    explicit SourceFile(const char* filename)
      : data_(filename)
    {
      const char* slash = strrchr(filename, '/');
      if (slash)
      {
        data_ = slash + 1;
      }
      size_ = static_cast<int>(strlen(data_));
    }

    const char* data_;
    int size_;
  };

  Logger(SourceFile file, int line);
  Logger(SourceFile file, int line, LogLevel level);
  Logger(SourceFile file, int line, LogLevel level, const char* func);
  Logger(SourceFile file, int line, bool toAbort);
  ~Logger();

  /* 返回Impl的LogStream对象 */
  LogStream& stream() { return impl_.stream_; }

  /* 返回日志级别及设置日志级别 */
  static LogLevel logLevel();
  static void setLogLevel(LogLevel level);

  /* 输出函数,将日志信息输出 */
  typedef void (*OutputFunc)(const char* msg, int len);
  /* 刷新缓冲区 */
  typedef void (*FlushFunc)();
  static void setOutput(OutputFunc);
  static void setFlush(FlushFunc);
  static void setTimeZone(const TimeZone& tz);

 private:

/* 
 * Impl技法,数据和对象分离
 * 通常应该是在类定义中声明class Impl;
 * 然后创建Impl对象,Impl impl_;
 * 最后在.cpp文件中实现Logger::Impl的定义
 * 
 * Impl对象存储的就是Logger所需要的所有数据
 */
class Impl
{
 public:
  typedef Logger::LogLevel LogLevel;
  Impl(LogLevel level, int old_errno, const SourceFile& file, int line);
  void formatTime();
  void finish();

  /* UTC时间,记录写入日志的时间 */
  Timestamp time_;
  /* 将日志信息存在缓冲区中,使用LOG_WARN是会返回Logger().stream(),就是返回这个LogStream */
  LogStream stream_;
  /* 日志级别,TRACE, DEBUG, WARN... */
  LogLevel level_;
  int line_;
  /* 
   * SourceFile也是Logger的内部类 why ?
   * 保存调用LOG_WARN<<语句的源文件名
   */
  SourceFile basename_;
};

  Impl impl_;

};

Impl的功能不只局限在保存Logger的数据,同时也在被创建时为日志信息添加前缀,通常是日期,时间,线程号,级别等。注意,在创建Logger对象时,会调用Impl的构造函数,在构造函数中就已经将日志前缀写入LogStream中,而调用stream函数返回LogStream对象时,日志正文信息就只会写在后面,这也是想要的效果。
Impl构造函数如下,用于写入各种前缀信息

/* Impl的构造函数,Impl主要负责日志的格式化 */
Logger::Impl::Impl(LogLevel level, int savedErrno, const SourceFile& file, int line)
  : time_(Timestamp::now()), /* 当前时间 */
    stream_(),  /* LogStream流 */
    level_(level),  /* 日志级别 */
    line_(line),    /* 调用LOG_* << 所在行,由__LINE__获取 */
    basename_(file) /* 调用LOG_* << 所在文件名,由__FILE__获取 */
{
  /* 格式化当前时间,写入LogStream中 */
  formatTime();
  /* 缓存线程id到成员变量中,当获取时直接返回 */
  CurrentThread::tid();
  /* 将线程id和日志级别写入LogStream */
  stream_ << T(CurrentThread::tidString(), CurrentThread::tidStringLength());
  stream_ << T(LogLevelName[level], 6);
  /* 如果有错误,写入错误信息 */
  if (savedErrno != 0)
  {
    stream_ << strerror_tl(savedErrno) << " (errno=" << savedErrno << ") ";
  }
}

由于是临时对象,所以析构函数接管了所有日志输出的任务

  1. 为日志信息添加后缀,通常是源文件名和所在行号
  2. LogStream的缓冲区中回去所有日志信息,包括前缀和后缀
  3. 调用输出函数,默认将日志信息打印到屏幕上
  4. 如果日志级别是FATAL,那么会立即刷新缓冲区,同时发送abort终止程序运行
/* 
 * Logger析构函数,将LogStream中的数据打印出来
 * 根据日志级别决定要不要立刻刷新
 */
Logger::~Logger()
{
  /* 当前日志输出结束,添加后缀(所在文件名和行号) */
  impl_.finish();
  /* 从LogStream的Buffer中拿出日志信息 */
  const LogStream::Buffer& buf(stream().buffer());
  /* 将信息输出到屏幕上 */
  g_output(buf.data(), buf.length());
  /* 如果当前日志级别是FATAL,表示是个终止程序的严重错误,将输出缓冲区的信息刷新到屏幕上,结束程序 */
  if (impl_.level_ == FATAL)
  {
    g_flush();
    abort();
  }
}

打印函数如下,默认打印到屏幕上,可以使用Logger::setOuputLogger::setFlush设置自定义打印和刷新函数,将日志信息重定向到文件等,这就涉及muduo另几个日志类,用于写入文件和滚动文件。

/*
 * 默认输出和刷新位置,将信息打印到屏幕上
 */
void defaultOutput(const char* msg, int len)
{
  size_t n = fwrite(msg, 1, len, stdout);
  //FIXME check n
  (void)n;
}

void defaultFlush()
{
  fflush(stdout);
}

至此日志信息会打印到屏幕上,Logger临时对象也被析构,程序继续执行(如果不是FATAL的话)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

muduo网络库学习(九)日志类Logger和LogStream,将日志信息打印到屏幕 的相关文章

  • [Python 与 炒股] TuShare 使用篇之三

    2016年新年第一贴 大年夜搞这个只能说明春晚实在是有点无聊 在之前的blog里写了一个最简单的例子 http blog csdn net robertsong2004 article details 50642655 现在试一下简单的分析

随机推荐

  • 渗透测试-01信息收集

    0x01信息收集 1 什么是信息收集 信息收集是指通过各种方式获取所需要的信息 以便我们在后续的渗透过程更好的进行 比如目标的站点IP 中间件 脚本语言 端口 邮箱等等 信息收集包含资产收集但不限于资产收集 2 信息收集的意义 1 信息收集
  • 使用 easyjson,生成 xxx_easyjson.go 文件之后,对测试结果所产生的影响

    文章评论 原文地址 https blog csdn net luslin1711 article details 90244468 正文 博主 你好 文中的测试结果 似乎不是很正确 由于评论区字数的限制 我另开一篇文章 请您解惑 以下是我的
  • 轻量级c语言开源日志库log.c介绍 - 实现不同级别和参数化日志打印

    前言 c语言没有现成的日志库 如果要记录日志 需要自己封装一个日志库 如果要实现日志级别和参数打印 还是比较麻烦的 正好在github找到了一个c语言开源日志库 可以实现日志级别打印 参数打印 而且还会记录日期和行号 最重要的是代码非常少
  • Google API 设计指南-文档

    翻译自 API Design Guide Documentation 这一章是为 API 添加内部文档的指南 大部分 API 有概述 教程和更高级别的参考文档 此指南不讨论 API 名 资源名和方法名的信息请查看命名约定 注释格式 在 pr
  • OpenGL ES几个概念-顶点着色器、片元着色器、EGL

    一 OpenGL ES OpenGL ES是使用在手机端和嵌入式里的3D图形应用程序编程接口 是跨平台的API OpenGL ES是OpenGL的简化版本 OpenGL2 x 版本相比 1 x 版本有较大差异 1 x 版本为 fixed f
  • Kylin Flink Cube 引擎的前世今生

    Apache Kylin 是一个开源的 分布式的分析型数据仓库 提供Hadoop Spark 之上的 SQL 查询接口及多维分析 OLAP 能力以支持超大规模数据 它能在亚秒内查询巨大的表 Kylin 的核心思想是 预计算 将数据按照指定的
  • C++11之智能指针(unique_ptr、shared_ptr、weak_ptr、auto_ptr)浅谈内存管理

    目录 前言 智能指针 使用方法 unique ptr 实现unique ptr类 使用uniquePtr shared ptr 实现SharedPtr 使用shared ptr weak ptr 使用weak ptr 前言 下面这段代码看起
  • Windows下同时安装python 2 和 3 详细教程 ——为了GitHack工具 同时安装python2和3的过程记录

    搜了半天发现 GitHack竟然真的只能在python2中运行 心痛 不得不去下python2 我之前下的是python3 文章目录 1 下载 安装 python2和3 2 配置python 2 x 的环境变量 3 将python 2 x
  • 投票==公平???

    前言 我们在团队中遇到意见分歧时 通常会通过投票机制以期来得到一个公平 公正的让所有人都能信服的解决方案 但是 这样的方案是否真的绝对公平 只有道德上的相对民主 没有制度上的绝对公平 求同存异才能长治久安 关于投票的经典场景 古雅典陶片放逐
  • TP-LINK交换机登录Web页面的操作方法

    TP LINK交换机登录Web页面的操作方法 之前小编介绍了TP LINK路由器登录Web管理页面的操作步骤 以及路由器无法登录的解决方法 详见文章 Tp link路由器管理界面无法登陆原因解析 下面栏目小编具体说说TP LINK交换机登录
  • Matlab设计数字滤波器入门

    一个3阶低通滤波器由下面差分方程描述 y n 0 0181 x n 0 0543 x n 1 0 0543 x n 2 0 0181 x n 3 1 76 y n 1 1 1829 y n 2 0 2781 y n 3 画出这个滤波器的幅度
  • VUE的基本使用(上)

    一 开发环境配置 VSCode 插件安装 jshint js代码规范检查 Beautify 一键美化代码的插件 Vetur vue文件识别插件 Javascript ES6 code snippets ES6语法提示 Auto Rename
  • 计算机ip保留地址,ip地址显示为保留地址怎么解决

    ip地址显示为保留地址怎么解决 2016 09 05 10 28 标签 dhcp ip地址 ip是保留地址怎么解决 ip作为计算机网络相互连接进行通信的协议 当dhcp超出系统规定的时间后 便会自动分配一个保留地址作为ip 如果主机ip地址
  • 涂鸦WIFI模组方案(MCU SDK)

    摘自涂鸦官方视频教程 https www bilibili com video BV1pb41117LD spm id from 333 999 0 0等 摘自 涂鸦IoT开发平台MCU开发接入 Wi Fi App面板 地址 https w
  • window.close()无效,原因剖析

    官方解释 https developer mozilla org en US docs Web API Window close 简单的说就是 window close 方法只能关闭由window open 或者浏览器直接输入url打开的页
  • 【敬伟ps教程】图层进阶知识

    文章目录 图层过滤和锁定 图层链接 图层编组 图层合并 图层盖印 图层复合 图层剪贴蒙版 图层过滤和锁定 图层过滤可以根据图层不同的性质进行查看管理 图层锁定即是对图层或图层某部分进行操作保护 按钮分别为 锁定透明像素 禁止对透明区域进行操
  • 使用JavaScript实现一个简单的计时器

    简介 计时器是前端开发中常见的功能之一 它可以帮助我们记录时间并进行倒计时等操作 在本篇博客中 我将向大家介绍如何使用JavaScript实现一个简单的计时器 实现步骤 第一步 创建HTML结构 首先 我们需要在HTML中创建一个用于显示计
  • 大语言模型参数说明(Temperature,Top p,Top k)

    如下是一些模型的运行界面 在使用OpenAI的接口 常常遇见一些参数 搞清它们的含义促使我们更进一步理解输出的结果 学习传送带 1 Temperature 用于调整随机从生成模型中抽样的程度 因此每次点击 生成 时 相同的提示可能会产生不同
  • app性能

    性能获取办法 appium webview context execute script perfmance api appium selenium的ExecuteScript Api 注入js return JSON stringify
  • muduo网络库学习(九)日志类Logger和LogStream,将日志信息打印到屏幕

    每一个成熟的项目都有大大小小的日志系统 在关键的地方打印日志信息 常用来跟踪程序运行 查找错误原因等 可以节省大量的debug时间 muduo的日志信息有5个级别 TRACE 细粒度最高的日志信息 打印的最详细 DEBUG 细粒度级别上对调