logger:

  1. class logger
  2. {
  3. };

在说这个logger类之前,先看1个关键的内部类 Impl

  1. private:
  2.  
  3. //logger内部数据实现类Impl,内部含有以下成员变量
  4. //时间戳,logstream数据流,日志级别,源文件行号,源文件名字.
  5. class Impl
  6. {
  7. public:
  8. typedef logger::loglevel LogLevel;
  9. //构造函数,最重要的地方,负责把日志头信息写入到m_stream中
  10. //m_stream<<日志级别,old_errno,文件名,文件行号.
  11. Impl(LogLevel level,int old_errno,const SourceFile& file,int line);
  12. //得到时间,并且把时间记录到m_stream中去
  13. void formatTime();
  14. //好吧关于timezone我实在是没搞懂,反正都是格式化时间,再把时间字符串写入到logstream里面去
  15. //干脆自己动手实现一下就好了,自己实现了myformatTime放弃使用formatTime
  16. void myformatTime();
  17. string m_time2; //自己的时间字符串
  18. //一行日志结束:m_stream<<" - "<<m_basename<<":"<<m_line<<'\n';
  19. void finish();
  20.  
  21. timestamp m_time;
  22. logstream m_stream;
  23. LogLevel m_level;
  24. int m_line;
  25. SourceFile m_basename;
  26. };

这个Impl类是logger的核心,它内部的m_buffer数据成员保存具体的日志信息,可以说这个Impl类就是把日志头信息写入到logstream中去。

在构造函数Impl中负责把时间信息,线程信息(如果有),日志级别,错误码信息(如果有)写入到logsteeam

在finish函数中负责把源文件名,行号写入到logstream中去。

logger作用:

这是一个日志类

一条普通日志格式如下
2020年08月24日 星期1 18:25:29.1598264729 WARN 日志4 - main.cpp:36
分别表示:时间,日志级别,日志内容 - 源文件名称:行号

日志数据的存储通过logstream成员变量来实现

目的是实现这样一个日志类:
在构造函数里面把日期基本信息例如时间,日志级别信息写入到logstream中
在析构函数里面先把源文件名,行号写入到logstream中,再把logstream中数据写入到日志输出地例如文件,stdout
从构造开始到析构结束只算一条日志.中间可以通过logger.stream()<<"new 日志" 操作来写入具体日志信息

logger类为用户提供自定义日志输出操作和日志缓冲区清空操作函数的实现,
代码中用下面两个函数实现回调函数的绑定
static void setOutput(outputFunc); 参数为函数指针
static void setFlush(flushFunc);
outputFunc,flushFunc应当在在一条日志完成之后被调用,用于实现日志的持久化(到文件/stdout)

注意日志级别并不是成员变量而是全局变量,这样有个好处就是loglevel的设定对所有的logger类生效
而不是只对所属的logger类生效

logger成员变量:

  1. private:
  2. //logger唯一的数据成员,里面包含了上面的Impl信息
  3. Impl m_impl;

Impl中的logstream m_stream是重点,负责日志数据的保存。

logger成员函数:

  1. 需要注意一下,源码中作者提供了两个FixedBuffer大小

const int kSmallBuffer=4000;
const int kLargeBuffer=4000*1000;

template class FixedBuffer<kSmallBuffer>;
template class FixedBuffer<kLargeBuffer>;

logstream作用:

logstream 类:
数据成员只有一个,就是上面的FixedBuffer<4000>;
成员函数:
基本上就是重载了一大堆<<操作符,可以实现把各种基本的数据类型保存到内部m_buffer中

logstream成员变量:

  1. private:
  2. typedef detail::FixedBuffer<detail::kSmallBuffer> Buffer;
  3. Buffer m_buffer;//内部数据成员,类型是FixedBuffer<4000>

logstream成员函数:

  1. public:
  2. //日志级别,一共6个级别
  3. enum loglevel
  4. {
  5. TRACE,
  6. DEBUG,
  7. INFO,
  8. WARN,
  9. ERROR,
  10. FATAL,
  11. NUM_LOG_LEVELS //表示日志级别的个数为6
  12. };
  13.  
  14. //SourceFile类的作用就是从文件路径中获取文件名,例如在/home/zqc/123.cc中获取123.cc
  15. class SourceFile
  16. {
  17. public:
  18. template<int N>
  19. SourceFile(const char (&arr)[N])
  20. :m_data(arr),m_size(N-)
  21. {
  22. //从右往左找到'/'及其之后字符 /123.h
  23. const char* slash=strrchr(m_data,'/');
  24. if(slash)
  25. {
  26. m_data=slash+;
  27. m_size-=static_cast<int>(m_data-arr);
  28. }
  29. }
  30. explicit SourceFile(const char* filename)
  31. :m_data(filename)
  32. {
  33. const char* slash = strrchr(filename, '/');
  34. if (slash)
  35. {
  36. m_data = slash + ;
  37. }
  38. m_size = static_cast<int>(strlen(m_data));
  39. }
  40.  
  41. const char* m_data; //文件名,不含路径
  42. int m_size; //文件名长度
  43. };
  44.  
  45. //不同的构造函数,内部全部是使用logger::Impl构造函数完成把日志头信息写到logstream里面去.
  46. //除了源文件名file,行号line,还可以把loglevel,函数名都写到logstream里面
  47. logger(SourceFile file,int line);
  48. logger(SourceFile file,int line,loglevel level);
  49. logger(SourceFile file,int line,loglevel level,const char* func);
  50. logger(SourceFile file,int line,bool toAbort);
  51. ~logger();
  52.  
  53. //返回内部数据流m_stream类型即为LogStream类型
  54. logstream& stream(){return m_impl.m_stream;}
  55.  
  56. //返回日志级别
  57. static loglevel logLevel();
  58. //设置日志级别
  59. static void setLogLevel(loglevel level);
  60.  
  61. //函数指针,用户可自定义日志输出地和清空日志输出地缓冲区
  62. typedef void (*outputFunc)(const char* msg,int len);
  63. typedef void (*flushFunc)();
  64. //使用用户自定义的函数来设置日志输出地点和清空缓冲区操作
  65. static void setOutput(outputFunc);
  66. static void setFlush(flushFunc);
  67. //设置时区
  68. static void setTimeZone(const timezone& tz);

SourceFile类其实就是把filepath转成filename,例如/home/zqc/123.cc转换成123.cc

logger宏定义:

  1. #define LOG_TRACE if (mymuduo::logger::logLevel() <= mymuduo::logger::TRACE) \
  2. mymuduo::logger(__FILE__, __LINE__, mymuduo::logger::TRACE, __func__).stream()
  3. #define LOG_DEBUG if (mymuduo::logger::logLevel() <= mymuduo::logger::DEBUG) \
  4. mymuduo::logger(__FILE__, __LINE__, mymuduo::logger::DEBUG, __func__).stream()
  5. #define LOG_INFO if (mymuduo::logger::logLevel() <= mymuduo::logger::INFO) \
  6. mymuduo::logger(__FILE__, __LINE__).stream()
  7. #define LOG_WARN mymuduo::logger(__FILE__, __LINE__, mymuduo::logger::WARN).stream()
  8. #define LOG_ERROR mymuduo::logger(__FILE__, __LINE__, mymuduo::logger::ERROR).stream()
  9. #define LOG_FATAL mymuduo::logger(__FILE__, __LINE__, mymuduo::logger::FATAL).stream()
  10. #define LOG_SYSERR mymuduo::logger(__FILE__, __LINE__, false).stream()
  11. #define LOG_SYSFATAL mymuduo::logger(__FILE__, __LINE__, true).stream()

为了便于操作,不用每次写日志都额外创建一个临时变量mymuduo::logger。

logger源文件(很长,不过我都写了注释):

  1. #include "logging.h"
  2. #include"base/timezone.h"
  3. #include"base/currentthread.h"
  4.  
  5. #include<errno.h>
  6. #include<stdio.h>
  7. #include<string.h>
  8.  
  9. #include<sstream>
  10.  
  11. namespace mymuduo {
  12.  
  13. //下面这几个变量和strerror_tl是为了把错误码信息写入到logstream中去而定义的
  14. __thread char t_errnobuf[];
  15. __thread char t_time[];
  16. __thread time_t t_lastSecond;
  17.  
  18. const char* strerror_tl(int savedErrno)
  19. {
  20. return strerror_r(savedErrno, t_errnobuf, sizeof t_errnobuf);
  21. }
  22.  
  23. //日志级别初始化,如果用户未自定义宏定义,默认日志级别为 INFO
  24. logger::loglevel initLogLevel()
  25. {
  26. if (::getenv("MUDUO_LOG_TRACE"))
  27. return logger::TRACE;
  28. else if (::getenv("MUDUO_LOG_DEBUG"))
  29. return logger::DEBUG;
  30. else
  31. return logger::INFO;
  32. }
  33.  
  34. //定义全局日志级别变量,并用initLogLevel()初始化
  35. logger::loglevel g_loglevel=initLogLevel();
  36.  
  37. //全局变量:日志级别数组
  38. const char* LogLevelName[logger::NUM_LOG_LEVELS]=
  39. {
  40. "TRACE ",
  41. "DEBUG ",
  42. "INFO ",
  43. "WARN ",
  44. "ERROR ",
  45. "FATAL ",
  46. };
  47.  
  48. //编译期间用于已知字符串长度的帮助类
  49. //不明白这个类有什么意义,自己实现了一个只能拷贝复制操作的最简单string类型
  50. class T
  51. {
  52. public:
  53. T(const char* str,unsigned len):m_str(str),m_len(len)
  54. {
  55. assert(strlen(m_str)==m_len);
  56. }
  57. const char* m_str;
  58. const unsigned m_len;
  59. };
  60.  
  61. //重载logstream的<<操作符,这里又新加了两种类型 SourceFile文件名类 和 T精简字符串类
  62. inline logstream& operator<<(logstream& s,const logger::SourceFile& v)
  63. {
  64. s.append(v.m_data,v.m_size);
  65. return s;
  66. }
  67.  
  68. inline logstream& operator<<(logstream& s,T v)
  69. {
  70. s.append(v.m_str,v.m_len);
  71. return s;
  72. }
  73.  
  74. //默认的日志输出,向stdout控制台中输出
  75. void defaultOutput(const char* msg,int len)
  76. {
  77. size_t n=fwrite(msg,,len,stdout);
  78. //FIXME check n
  79. (void)n;
  80. }
  81.  
  82. //清空stdout缓冲区
  83. void defaultFlush()
  84. {
  85. fflush(stdout);
  86. }
  87. //全局变量:两个函数指针,分别指向日志输出操作函数和日志清空缓冲区操作函数
  88. //如果用户不自己实现这两个函数,就用默认的output和flush函数,输出到stdout中去
  89. logger::outputFunc g_output=defaultOutput;
  90. logger::flushFunc g_flush=defaultFlush;
  91. //全局变量:时区...
  92. timezone g_logTimeZone;
  93.  
  94. }//namespace mymuduo
  95.  
  96. using namespace mymuduo;
  97.  
  98. //很重要的构造函数,除了类成员的初始化,还负责把各种信息写入到logstream中去
  99. logger::Impl::Impl(logger::loglevel level,int savedErrno,const SourceFile& file,int line)
  100. :m_time(timestamp::now()),m_stream(),m_level(level),
  101. m_line(line),m_basename(file)
  102. {
  103. //写入时间信息,用我自己的
  104. myformatTime();
  105. //formatTime();
  106. currentthread::tid();
  107. //写入线程信息
  108. m_stream << T(currentthread::tidString(), currentthread::tidStringLength());
  109. //写入日志级别
  110. m_stream << T(LogLevelName[level], );
  111. //错误码不为0可写入错误码信息
  112. if (savedErrno != )
  113. {
  114. m_stream << strerror_tl(savedErrno) << " (errno=" << savedErrno << ") ";
  115. }
  116. //全部都是写到logstream中去
  117.  
  118. }
  119.  
  120. //把时间写入到logstream里面去,这个时区可能有问题,不过可以自己重新实现这个函数
  121. //只需要把类似于 %4d%02d%02d %02d:%02d:%02d 这种格式的时间字符串写入到logstream中即可
  122. void logger::Impl::formatTime()
  123. {
  124. int64_t microSecondsSinceEpoch = m_time.microSecSinceEpoch();
  125. time_t seconds = static_cast<time_t>(microSecondsSinceEpoch / timestamp::microSecInSec);
  126. int microseconds = static_cast<int>(microSecondsSinceEpoch % timestamp::microSecInSec);
  127. if (seconds != t_lastSecond)
  128. {
  129. t_lastSecond = seconds;
  130. struct tm tm_time;
  131. if (g_logTimeZone.valid())
  132. {
  133. tm_time = g_logTimeZone.toLocalTime(seconds);
  134. }
  135. else
  136. {
  137. ::gmtime_r(&seconds, &tm_time); // FIXME TimeZone::fromUtcTime
  138. }
  139.  
  140. int len = snprintf(t_time, sizeof(t_time), "%4d%02d%02d %02d:%02d:%02d",
  141. tm_time.tm_year + , tm_time.tm_mon + , tm_time.tm_mday,
  142. tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
  143. assert(len == ); (void)len;
  144. }
  145.  
  146. if (g_logTimeZone.valid())
  147. {
  148. Fmt us(".%06d ", microseconds);
  149. assert(us.length() == );
  150. m_stream << T(t_time, ) << T(us.data(), );
  151. }
  152. else
  153. {
  154. Fmt us(".%06dZ ", microseconds);
  155. assert(us.length() == );
  156. m_stream << T(t_time, ) << T(us.data(), );
  157. }
  158. }
  159. void logger::Impl::myformatTime()
  160. {
  161. m_time2=timestamp::now().toFormattedString();
  162. m_stream<<m_time2<<" ";
  163. }
  164. //表明一条日志写入logstream结束.
  165. void logger::Impl::finish()
  166. {
  167. m_stream<<" - "<<m_basename<<":"<<m_line<<'\n';
  168. }
  169.  
  170. logger::logger(SourceFile file,int line)
  171. :m_impl(INFO,,file,line)
  172. {
  173.  
  174. }
  175.  
  176. logger::logger(SourceFile file,int line,loglevel level,const char* func)
  177. :m_impl(level,,file,line)
  178. {
  179. m_impl.m_stream<<func<<" ";
  180. }
  181.  
  182. logger::logger(SourceFile file, int line, loglevel level)
  183. : m_impl(level, , file, line)
  184. {
  185. }
  186.  
  187. logger::logger(SourceFile file, int line, bool toAbort)
  188. : m_impl(toAbort?FATAL:ERROR, errno, file, line)
  189. {
  190. }
  191.  
  192. logger::~logger()
  193. {
  194. m_impl.finish();
  195. const logstream::Buffer& buf(stream().buffer());
  196. //把剩下的数据output出去,output由用户自定义或者使用默认stdout
  197. g_output(buf.data(),buf.length());
  198. if(m_impl.m_level==FATAL)
  199. {
  200. g_flush();
  201. abort();
  202. }
  203. }
  204. //设置日志级别
  205. void logger::setLogLevel(logger::loglevel level)
  206. {
  207. g_loglevel = level;
  208. }
  209. //设置日志输出操作函数
  210. void logger::setOutput(outputFunc out)
  211. {
  212. g_output = out;
  213. }
  214. //设置日志清空缓冲区操作函数
  215. void logger::setFlush(flushFunc flush)
  216. {
  217. g_flush = flush;
  218. }
  219. //设置时区
  220. void logger::setTimeZone(const timezone& tz)
  221. {
  222. g_logTimeZone = tz;
  223. }

测试:

  1. #include"base/logging.h"
  2. #include<iostream>
  3. using namespace std;
  4.  
  5. namespace mymuduo{
  6. namespace currentthread {
  7. void cacheTid()
  8. {
  9. }
  10. }
  11. }
  12.  
  13. using namespace mymuduo;
  14.  
  15. int main()
  16. {
  17.  
  18. //测试日志类logger,logger在构造函数里面完成Impl的初始化,并把相关日志信息写入logstream
  19. //在析构时调用Impl.finish(),并且调用output函数将logstream中的日志信息写入到日志地,这里默认是stdout
  20.  
  21. //通过创建临时变量mymuduo::logger来实现日志,日志信息>>logstream>>stdout;
  22. //设置日志级别
  23. std::cout<<"构建临时对象:\n";
  24. mymuduo::logger::setLogLevel(mymuduo::logger::WARN);
  25.  
  26. if(mymuduo::logger::logLevel()<=mymuduo::logger::TRACE)
  27. mymuduo::logger(__FILE__,__LINE__,mymuduo::logger::TRACE).stream()<<"日志1";
  28.  
  29. if(mymuduo::logger::logLevel()<=mymuduo::logger::DEBUG)
  30. mymuduo::logger(__FILE__,__LINE__,mymuduo::logger::DEBUG).stream()<<"日志2";
  31.  
  32. if(mymuduo::logger::logLevel()<=mymuduo::logger::INFO)
  33. mymuduo::logger(__FILE__,__LINE__,mymuduo::logger::INFO).stream()<<"日志3";
  34.  
  35. if(mymuduo::logger::logLevel()<=mymuduo::logger::WARN)
  36. mymuduo::logger(__FILE__,__LINE__,mymuduo::logger::WARN).stream()<<"日志4";
  37.  
  38. if(mymuduo::logger::logLevel()<=mymuduo::logger::ERROR)
  39. mymuduo::logger(__FILE__,__LINE__,mymuduo::logger::ERROR).stream()<<"日志5";
  40.  
  41. mymuduo::logger::setLogLevel(mymuduo::logger::TRACE);
  42. std::cout<<"宏定义:\n";
  43. //通过宏定义来实现日志
  44. LOG_TRACE<<"日志1";
  45. LOG_DEBUG<<"日志2";
  46. LOG_INFO<<"日志3";
  47. LOG_WARN<<"日志4";
  48. LOG_ERROR<<"日志5";
  49.  
  50. std::cout<<"over...\n";
  51. }

打印结果:

构建临时对象:
2020年08月24日 星期1 18:51:39.1598266299 WARN 日志4 - main.cpp:36
2020年08月24日 星期1 18:51:39.1598266299 ERROR 日志5 - main.cpp:39
宏定义:
2020年08月24日 星期1 18:51:39.1598266299 TRACE main 日志1 - main.cpp:44
2020年08月24日 星期1 18:51:39.1598266299 DEBUG main 日志2 - main.cpp:45
2020年08月24日 星期1 18:51:39.1598266299 INFO 日志3 - main.cpp:46
2020年08月24日 星期1 18:51:39.1598266299 WARN 日志4 - main.cpp:47
2020年08月24日 星期1 18:51:39.1598266299 ERROR 日志5 - main.cpp:48
over...

总的来说最重要的就是直到从日志创建再到日志持久化(写入文件/stdout)的流程。

日志的数据保存在 logger.m_impl.m_stream中,因此大部分基于日志的操作肯定都是跟这个成员变量有关。

以一条日志为例子

2020年08月24日 星期1 18:25:29.1598264729 WARN 日志4 - main.cpp:36

下面是具体的过程:

logger构造函数中调用

          Impl构造函数:把日志头时间和级别即2020年08月24日 星期1 18:25:29.1598264729 WARN写入logstream中去

logger.stream()<<"日志信息",把具体的日志信息即日志体写入到logstream中去

logger析构函数中调用

          Impl.finish();把日志尾,源文件名,行号信息写入logstream

          outputFunc(),进行日志持久化处理,把日志信息写入到stdout(默认)或其他地方(用户可以自定义)

这样就完成了一条日志的保存。

muduo源码解析11-logger类的更多相关文章

  1. muduo源码解析5-mutex相关类

    mutexlock和mutexlockguard class mutexlock:noncopyable { }: class mutexlockguard:noncopyable { }: 作用: ...

  2. AOP源码解析:AspectJAwareAdvisorAutoProxyCreator类的介绍

    AspectJAwareAdvisorAutoProxyCreator 的类图 上图中一些 类/接口 的介绍: AspectJAwareAdvisorAutoProxyCreator : 公开了Asp ...

  3. Mybatis源码解析3——核心类SqlSessionFactory,看完我悟了

    这是昨晚的武汉,晚上九点钟拍的,疫情又一次来袭,曾经熙熙攘攘的夜市也变得冷冷清清,但比前几周要好很多了.希望大家都能保护好自己,保护好身边的人,生活不可能像你想象的那么好,但也不会像你想象的那么糟. ...

  4. muduo源码解析10-logstream类

    FixedBuffer和logstream class FixedBuffer:noncopyable { }: class logstream:noncopyable { }: 先说一下包含的头文件 ...

  5. AOP源码解析:AspectJExpressionPointcutAdvisor类

    先看看 AspectJExpressionPointcutAdvisor 的类图 再了解一下切点(Pointcut)表达式,它指定触发advice的方法,可以精确到返回参数,参数类型,方法名 1 pa ...

  6. Netty源码解析 -- 内存对齐类SizeClasses

    在学习Netty内存池之前,我们先了解一下Netty的内存对齐类SizeClasses,它为Netty内存池中的内存块提供大小对齐,索引计算等服务方法. 源码分析基于Netty 4.1.52 Nett ...

  7. Spring源码解析 – @Configuration配置类及注解Bean的解析

    在分析Spring 容器创建过程时,我们知道容器默认会加载一些后置处理器PostPRocessor,以AnnotationConfigApplicationContext为例,在构造函数中初始化rea ...

  8. java源码解析之Object类

    一.Object类概述   Object类是java中类层次的根,是所有类的基类.在编译时会自动导入.Object中的方法如下: 二.方法详解   Object的方法可以分成两类,一类是被关键字fin ...

  9. Bulma 源码解析之 .columns 类

    {说明} 这一部分的源码内容被我简化了,另外我还额外添加了一个辅助类 is-grow. .columns // 修饰类 &.is-centered justify-content: cente ...

随机推荐

  1. MacOS SVN简单入门

    背景:MacOS内置了SVN的客户端和服务器端的软件,下边所使用到的目录需要结合自己电脑的具体情况进行设置,并不是很困难. MacOS SVN简单入门 第一部分,创建本地的SVN测试仓库,并修改相应的 ...

  2. Python time strftime()方法

    描述 Python time strftime() 函数接收以时间元组,并返回以可读字符串表示的当地时间,格式由参数format决定.高佣联盟 www.cgewang.com 语法 strftime( ...

  3. CentOS中配置NFS

    https://www.cnblogs.com/yeungchie/ NFS是Network File System的缩写,即网络文件系统. 它的主要功能是通过网络(一般是局域网)让不同的主机系统之间 ...

  4. C/C++编程笔记:C++入门知识丨继承和派生

    本篇要学习的内容和知识结构概览 继承和派生的概念 派生 通过特殊化已有的类来建立新类的过程, 叫做”类的派生”, 原有的类叫做”基类”, 新建立的类叫做”派生类”. 从类的成员角度看, 派生类自动地将 ...

  5. CF R 639 div2 F Review 贪心 二分

    LINK:Résumé Review 这道题让我眼前一亮没想到二分这么绝. 由于每个\(b_i\)都是局部的 全局只有一个限制\(\sum_{i=1}^nb_i=k\) 所以dp没有什么用 我们只需要 ...

  6. log4j2 自动删除过期日志文件配置及实现原理解析

    日志文件自动删除功能必不可少,当然你可以让运维去做这事,只是这不地道.而日志组件是一个必备组件,让其多做一件删除的工作,无可厚非.本文就来探讨下 log4j 的日志文件自动删除实现吧. 0. 自动删除 ...

  7. Linux的VMWare14中配置Centos7桥接网络环境(网络一)

    1.查看当前初始环境如下:在windows端先查看本机ip   ifconfig

  8. 打印java系统的信息

    System.getProperties() 确定当前系统属性 Properties.list() 将获取到的信息打印到流中 public class Userone { public static ...

  9. 《第22条军规》Catch-22

    也许我们能注意到,英语里“军规”和“圈套”是同一个词. <第二十二条军规>是约瑟夫·海勒的代表作,小说以第二次世界大战期间驻扎在皮亚诺扎岛上的一支美国飞行队为背景,描写飞行员约瑟连(YOY ...

  10. 003.Nginx配置解析

    一 Nginx配置文件 1.1 Nginx主配置 Nginx主配置文件/etc/nginx/nginx.conf是一个纯文本类型的文件,整个配置文件是以区块的形式组织,通常每一个区块以一对大括号{}来 ...