muduo源码解析11-logger类
logger:
class logger
{
};
在说这个logger类之前,先看1个关键的内部类 Impl
private: //logger内部数据实现类Impl,内部含有以下成员变量
//时间戳,logstream数据流,日志级别,源文件行号,源文件名字.
class Impl
{
public:
typedef logger::loglevel LogLevel;
//构造函数,最重要的地方,负责把日志头信息写入到m_stream中
//m_stream<<日志级别,old_errno,文件名,文件行号.
Impl(LogLevel level,int old_errno,const SourceFile& file,int line);
//得到时间,并且把时间记录到m_stream中去
void formatTime();
//好吧关于timezone我实在是没搞懂,反正都是格式化时间,再把时间字符串写入到logstream里面去
//干脆自己动手实现一下就好了,自己实现了myformatTime放弃使用formatTime
void myformatTime();
string m_time2; //自己的时间字符串
//一行日志结束:m_stream<<" - "<<m_basename<<":"<<m_line<<'\n';
void finish(); timestamp m_time;
logstream m_stream;
LogLevel m_level;
int m_line;
SourceFile m_basename;
};
这个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成员变量:
private:
//logger唯一的数据成员,里面包含了上面的Impl信息
Impl m_impl;
Impl中的logstream m_stream是重点,负责日志数据的保存。
logger成员函数:
需要注意一下,源码中作者提供了两个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成员变量:
private:
typedef detail::FixedBuffer<detail::kSmallBuffer> Buffer;
Buffer m_buffer;//内部数据成员,类型是FixedBuffer<4000>
logstream成员函数:
public:
//日志级别,一共6个级别
enum loglevel
{
TRACE,
DEBUG,
INFO,
WARN,
ERROR,
FATAL,
NUM_LOG_LEVELS //表示日志级别的个数为6
}; //SourceFile类的作用就是从文件路径中获取文件名,例如在/home/zqc/123.cc中获取123.cc
class SourceFile
{
public:
template<int N>
SourceFile(const char (&arr)[N])
:m_data(arr),m_size(N-)
{
//从右往左找到'/'及其之后字符 /123.h
const char* slash=strrchr(m_data,'/');
if(slash)
{
m_data=slash+;
m_size-=static_cast<int>(m_data-arr);
}
}
explicit SourceFile(const char* filename)
:m_data(filename)
{
const char* slash = strrchr(filename, '/');
if (slash)
{
m_data = slash + ;
}
m_size = static_cast<int>(strlen(m_data));
} const char* m_data; //文件名,不含路径
int m_size; //文件名长度
}; //不同的构造函数,内部全部是使用logger::Impl构造函数完成把日志头信息写到logstream里面去.
//除了源文件名file,行号line,还可以把loglevel,函数名都写到logstream里面
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(); //返回内部数据流m_stream类型即为LogStream类型
logstream& stream(){return m_impl.m_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);
SourceFile类其实就是把filepath转成filename,例如/home/zqc/123.cc转换成123.cc
logger宏定义:
#define LOG_TRACE if (mymuduo::logger::logLevel() <= mymuduo::logger::TRACE) \
mymuduo::logger(__FILE__, __LINE__, mymuduo::logger::TRACE, __func__).stream()
#define LOG_DEBUG if (mymuduo::logger::logLevel() <= mymuduo::logger::DEBUG) \
mymuduo::logger(__FILE__, __LINE__, mymuduo::logger::DEBUG, __func__).stream()
#define LOG_INFO if (mymuduo::logger::logLevel() <= mymuduo::logger::INFO) \
mymuduo::logger(__FILE__, __LINE__).stream()
#define LOG_WARN mymuduo::logger(__FILE__, __LINE__, mymuduo::logger::WARN).stream()
#define LOG_ERROR mymuduo::logger(__FILE__, __LINE__, mymuduo::logger::ERROR).stream()
#define LOG_FATAL mymuduo::logger(__FILE__, __LINE__, mymuduo::logger::FATAL).stream()
#define LOG_SYSERR mymuduo::logger(__FILE__, __LINE__, false).stream()
#define LOG_SYSFATAL mymuduo::logger(__FILE__, __LINE__, true).stream()
为了便于操作,不用每次写日志都额外创建一个临时变量mymuduo::logger。
logger源文件(很长,不过我都写了注释):
#include "logging.h"
#include"base/timezone.h"
#include"base/currentthread.h" #include<errno.h>
#include<stdio.h>
#include<string.h> #include<sstream> namespace mymuduo { //下面这几个变量和strerror_tl是为了把错误码信息写入到logstream中去而定义的
__thread char t_errnobuf[];
__thread char t_time[];
__thread time_t t_lastSecond; const char* strerror_tl(int savedErrno)
{
return strerror_r(savedErrno, t_errnobuf, sizeof t_errnobuf);
} //日志级别初始化,如果用户未自定义宏定义,默认日志级别为 INFO
logger::loglevel initLogLevel()
{
if (::getenv("MUDUO_LOG_TRACE"))
return logger::TRACE;
else if (::getenv("MUDUO_LOG_DEBUG"))
return logger::DEBUG;
else
return logger::INFO;
} //定义全局日志级别变量,并用initLogLevel()初始化
logger::loglevel g_loglevel=initLogLevel(); //全局变量:日志级别数组
const char* LogLevelName[logger::NUM_LOG_LEVELS]=
{
"TRACE ",
"DEBUG ",
"INFO ",
"WARN ",
"ERROR ",
"FATAL ",
}; //编译期间用于已知字符串长度的帮助类
//不明白这个类有什么意义,自己实现了一个只能拷贝复制操作的最简单string类型
class T
{
public:
T(const char* str,unsigned len):m_str(str),m_len(len)
{
assert(strlen(m_str)==m_len);
}
const char* m_str;
const unsigned m_len;
}; //重载logstream的<<操作符,这里又新加了两种类型 SourceFile文件名类 和 T精简字符串类
inline logstream& operator<<(logstream& s,const logger::SourceFile& v)
{
s.append(v.m_data,v.m_size);
return s;
} inline logstream& operator<<(logstream& s,T v)
{
s.append(v.m_str,v.m_len);
return s;
} //默认的日志输出,向stdout控制台中输出
void defaultOutput(const char* msg,int len)
{
size_t n=fwrite(msg,,len,stdout);
//FIXME check n
(void)n;
} //清空stdout缓冲区
void defaultFlush()
{
fflush(stdout);
}
//全局变量:两个函数指针,分别指向日志输出操作函数和日志清空缓冲区操作函数
//如果用户不自己实现这两个函数,就用默认的output和flush函数,输出到stdout中去
logger::outputFunc g_output=defaultOutput;
logger::flushFunc g_flush=defaultFlush;
//全局变量:时区...
timezone g_logTimeZone; }//namespace mymuduo using namespace mymuduo; //很重要的构造函数,除了类成员的初始化,还负责把各种信息写入到logstream中去
logger::Impl::Impl(logger::loglevel level,int savedErrno,const SourceFile& file,int line)
:m_time(timestamp::now()),m_stream(),m_level(level),
m_line(line),m_basename(file)
{
//写入时间信息,用我自己的
myformatTime();
//formatTime();
currentthread::tid();
//写入线程信息
m_stream << T(currentthread::tidString(), currentthread::tidStringLength());
//写入日志级别
m_stream << T(LogLevelName[level], );
//错误码不为0可写入错误码信息
if (savedErrno != )
{
m_stream << strerror_tl(savedErrno) << " (errno=" << savedErrno << ") ";
}
//全部都是写到logstream中去 } //把时间写入到logstream里面去,这个时区可能有问题,不过可以自己重新实现这个函数
//只需要把类似于 %4d%02d%02d %02d:%02d:%02d 这种格式的时间字符串写入到logstream中即可
void logger::Impl::formatTime()
{
int64_t microSecondsSinceEpoch = m_time.microSecSinceEpoch();
time_t seconds = static_cast<time_t>(microSecondsSinceEpoch / timestamp::microSecInSec);
int microseconds = static_cast<int>(microSecondsSinceEpoch % timestamp::microSecInSec);
if (seconds != t_lastSecond)
{
t_lastSecond = seconds;
struct tm tm_time;
if (g_logTimeZone.valid())
{
tm_time = g_logTimeZone.toLocalTime(seconds);
}
else
{
::gmtime_r(&seconds, &tm_time); // FIXME TimeZone::fromUtcTime
} int len = snprintf(t_time, sizeof(t_time), "%4d%02d%02d %02d:%02d:%02d",
tm_time.tm_year + , tm_time.tm_mon + , tm_time.tm_mday,
tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
assert(len == ); (void)len;
} if (g_logTimeZone.valid())
{
Fmt us(".%06d ", microseconds);
assert(us.length() == );
m_stream << T(t_time, ) << T(us.data(), );
}
else
{
Fmt us(".%06dZ ", microseconds);
assert(us.length() == );
m_stream << T(t_time, ) << T(us.data(), );
}
}
void logger::Impl::myformatTime()
{
m_time2=timestamp::now().toFormattedString();
m_stream<<m_time2<<" ";
}
//表明一条日志写入logstream结束.
void logger::Impl::finish()
{
m_stream<<" - "<<m_basename<<":"<<m_line<<'\n';
} logger::logger(SourceFile file,int line)
:m_impl(INFO,,file,line)
{ } logger::logger(SourceFile file,int line,loglevel level,const char* func)
:m_impl(level,,file,line)
{
m_impl.m_stream<<func<<" ";
} logger::logger(SourceFile file, int line, loglevel level)
: m_impl(level, , file, line)
{
} logger::logger(SourceFile file, int line, bool toAbort)
: m_impl(toAbort?FATAL:ERROR, errno, file, line)
{
} logger::~logger()
{
m_impl.finish();
const logstream::Buffer& buf(stream().buffer());
//把剩下的数据output出去,output由用户自定义或者使用默认stdout
g_output(buf.data(),buf.length());
if(m_impl.m_level==FATAL)
{
g_flush();
abort();
}
}
//设置日志级别
void logger::setLogLevel(logger::loglevel level)
{
g_loglevel = level;
}
//设置日志输出操作函数
void logger::setOutput(outputFunc out)
{
g_output = out;
}
//设置日志清空缓冲区操作函数
void logger::setFlush(flushFunc flush)
{
g_flush = flush;
}
//设置时区
void logger::setTimeZone(const timezone& tz)
{
g_logTimeZone = tz;
}
测试:
#include"base/logging.h"
#include<iostream>
using namespace std; namespace mymuduo{
namespace currentthread {
void cacheTid()
{
}
}
} using namespace mymuduo; int main()
{ //测试日志类logger,logger在构造函数里面完成Impl的初始化,并把相关日志信息写入logstream
//在析构时调用Impl.finish(),并且调用output函数将logstream中的日志信息写入到日志地,这里默认是stdout //通过创建临时变量mymuduo::logger来实现日志,日志信息>>logstream>>stdout;
//设置日志级别
std::cout<<"构建临时对象:\n";
mymuduo::logger::setLogLevel(mymuduo::logger::WARN); if(mymuduo::logger::logLevel()<=mymuduo::logger::TRACE)
mymuduo::logger(__FILE__,__LINE__,mymuduo::logger::TRACE).stream()<<"日志1"; if(mymuduo::logger::logLevel()<=mymuduo::logger::DEBUG)
mymuduo::logger(__FILE__,__LINE__,mymuduo::logger::DEBUG).stream()<<"日志2"; if(mymuduo::logger::logLevel()<=mymuduo::logger::INFO)
mymuduo::logger(__FILE__,__LINE__,mymuduo::logger::INFO).stream()<<"日志3"; if(mymuduo::logger::logLevel()<=mymuduo::logger::WARN)
mymuduo::logger(__FILE__,__LINE__,mymuduo::logger::WARN).stream()<<"日志4"; if(mymuduo::logger::logLevel()<=mymuduo::logger::ERROR)
mymuduo::logger(__FILE__,__LINE__,mymuduo::logger::ERROR).stream()<<"日志5"; mymuduo::logger::setLogLevel(mymuduo::logger::TRACE);
std::cout<<"宏定义:\n";
//通过宏定义来实现日志
LOG_TRACE<<"日志1";
LOG_DEBUG<<"日志2";
LOG_INFO<<"日志3";
LOG_WARN<<"日志4";
LOG_ERROR<<"日志5"; std::cout<<"over...\n";
}
打印结果:
构建临时对象:
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类的更多相关文章
- muduo源码解析5-mutex相关类
mutexlock和mutexlockguard class mutexlock:noncopyable { }: class mutexlockguard:noncopyable { }: 作用: ...
- AOP源码解析:AspectJAwareAdvisorAutoProxyCreator类的介绍
AspectJAwareAdvisorAutoProxyCreator 的类图 上图中一些 类/接口 的介绍: AspectJAwareAdvisorAutoProxyCreator : 公开了Asp ...
- Mybatis源码解析3——核心类SqlSessionFactory,看完我悟了
这是昨晚的武汉,晚上九点钟拍的,疫情又一次来袭,曾经熙熙攘攘的夜市也变得冷冷清清,但比前几周要好很多了.希望大家都能保护好自己,保护好身边的人,生活不可能像你想象的那么好,但也不会像你想象的那么糟. ...
- muduo源码解析10-logstream类
FixedBuffer和logstream class FixedBuffer:noncopyable { }: class logstream:noncopyable { }: 先说一下包含的头文件 ...
- AOP源码解析:AspectJExpressionPointcutAdvisor类
先看看 AspectJExpressionPointcutAdvisor 的类图 再了解一下切点(Pointcut)表达式,它指定触发advice的方法,可以精确到返回参数,参数类型,方法名 1 pa ...
- Netty源码解析 -- 内存对齐类SizeClasses
在学习Netty内存池之前,我们先了解一下Netty的内存对齐类SizeClasses,它为Netty内存池中的内存块提供大小对齐,索引计算等服务方法. 源码分析基于Netty 4.1.52 Nett ...
- Spring源码解析 – @Configuration配置类及注解Bean的解析
在分析Spring 容器创建过程时,我们知道容器默认会加载一些后置处理器PostPRocessor,以AnnotationConfigApplicationContext为例,在构造函数中初始化rea ...
- java源码解析之Object类
一.Object类概述 Object类是java中类层次的根,是所有类的基类.在编译时会自动导入.Object中的方法如下: 二.方法详解 Object的方法可以分成两类,一类是被关键字fin ...
- Bulma 源码解析之 .columns 类
{说明} 这一部分的源码内容被我简化了,另外我还额外添加了一个辅助类 is-grow. .columns // 修饰类 &.is-centered justify-content: cente ...
随机推荐
- Fortify Audit Workbench 笔记 Path Manipulation
Path Manipulation Abstract 通过用户输入控制 file system 操作所用的路径,借此攻击者可以访问或修改其他受保护的系统资源. Explanation 当满足以下两个条 ...
- PHP jdtogregorian() 函数
------------恢复内容开始------------ 实例 把格利高里历法的日期转换为儒略日计数,然后再转换回格利高里历法的日期: <?php$jd=gregoriantojd(6,20 ...
- PHP mysqli_set_charset() 函数
设置默认客户端字符集: <?php 高佣联盟 www.cgewang.com // 假定数据库用户名:root,密码:123456,数据库:RUNOOB $con=mysqli_connect( ...
- C/C++编程笔记:C++入门知识丨继承和派生
本篇要学习的内容和知识结构概览 继承和派生的概念 派生 通过特殊化已有的类来建立新类的过程, 叫做”类的派生”, 原有的类叫做”基类”, 新建立的类叫做”派生类”. 从类的成员角度看, 派生类自动地将 ...
- JVM系列之:JIT中的Virtual Call
目录 简介 Virtual Call和它的本质 Virtual Call和classic call Virtual Call优化单实现方法的例子 Virtual Call优化多实现方法的例子 总结 简 ...
- 新鲜整理的Java学习大礼包!!锵锵锵锵~
第一部分:Java视频资源! 前端 HTML5新元素之Canvas详解 https://www.bilibili.com/video/BV1TE41177TE HTML5之WebStorage详解 h ...
- 001_HyperLedger Fabric环境安装
HyperLedger Fabric的环境,有解决三大问题 第一,是系统环境,这里我们选择的是centos7 第二,是开发环境,这里我们选择的是Go语言 第三,是运行环境,这里我们选择的是Docker ...
- WC2020 Cu 记
由于今年的 WC 既不 W 也不 C,所以其实应该叫吸吸F线上推广 3M 原则记 Day1 上午听了一会儿课跑去写题了,写着写着就摸了起来. 下午也摸了 晚上员交发现有好多听过的和好多好多没听过的 怎 ...
- ALGEBRA-前言
“当你读一页不到一个小时的话,可能是你读太快了” 哈哈 可以 慢慢品
- 2020-07-09:mysql如何开启慢查询?
福哥答案2020-07-09: 1.参数说明 slow_query_log 慢查询开启状态slow_query_log_file 慢查询日志存放的位置(这个目录需要MySQL的运行帐号的可写权限,一般 ...