本文主要實現muduo的異步日誌.省去boost庫中特性的引用,輕巧簡潔。css
對於編程而言,日誌是必不可少的,對於關鍵進程,日誌一般須要記錄:
1.收到的每條內部消息id.
2.收到的每條外部消息全文.
3.發出的每條消息全文,每條消息都有全局惟一的id.
4.關鍵內部狀態變動,等等.
5.每條日誌都有時間戳,以方便的追蹤分佈式系統中一個時間的前因後果.html
c++日誌大致上有兩種API風格: C/JAVA的printf(fmt, ...)風格:
log_info(「received %d bytes from %s」, len, getName.c_str()); c++的stream<<風格:
LOG_INFO << 「received << len << bytes from << getName.c_str();c++
本文實現的是c++的stream風格, 優勢是使用起來更天然,不用保持格式的一致性.git
常規的通用日誌庫如log4j/logback一般會提供豐富的功能,但不必定是必須的功能.程序員
1.日誌有多種級別:TRACE、DEBUG、INFO、WARN、ERROR、FATAL等。
2.日誌可能會有多個目的地:文件、socket、SMTP等。
3.日誌消息格式可配置。
4.可設置運行時過濾器(filter)、控制不一樣的組件的日誌消息級別和目的地.編程
這四項中,除了第一項以外,其他並不是是必須的功能.本文內容只包含第一項功能.
對於分佈式系統中的服務進程而言,日誌的目的地只有一個:本地文件,往網絡寫日誌消息是不可靠的,另外一個壞處就是網絡帶寬的消耗.緩存
muduo日誌格式以下,本日誌對格式有所改動,但基本信息不變。網絡
日期 時間 微妙 線程 級別 函數名 正文 源文件名: 行號20180802 12 :42 :50.621354 44321 WARN main just test - test .cpp :44520180802 12 :42 :50.621359 44321 INFO main just test - test .cpp :44620180802 12 :42 :50.621364 44321 ERROR main just test - test .cpp :447
只有日誌庫足夠高效,程序員纔敢在代碼中輸出足夠多的診斷信息,減少運維難度,提高效率.它體如今:多線程
爲了實現性能指標,本日誌設計中幾點優化:app
LogStream類,主要做用是重載<<操做符,將基本類型的數據格式成字符串經過append接口存入LogBuffer中。
#ifndef _LOG_STREAM_HH#define _LOG_STREAM_HH#include <stdio.h>#include <string.h> const int kSmallBuffer = 4096;const int kLargeBuffer = 4096; template< int SIZE>class LogBuffer{public:LogBuffer(): m_cur(m_data){} ~LogBuffer(){//printf("%s", m_data);} void append(const char* /*restrict*/ buf, size_t len){// append partiallyif ( /*implicit_cast<size_t>*/(avail()) > len){memcpy(m_cur, buf, len);m_cur += len;}} // write in m_data directlychar* current() { return m_cur; };int avail() const { return static_cast< int> (end() - m_cur); }void add(size_t len) { m_cur += len; }int length() const { return m_cur - m_data;} const char* data() const { return m_data; } private:const char* end() const { return m_data + sizeof(m_data); } char m_data[SIZE];char* m_cur;}; class LogStream{public:LogStream();~LogStream(); typedef LogBuffer<kSmallBuffer> Buffer;typedef LogStream self;self& operator<<( bool v); self& operator<<( short);self& operator<<( unsigned short);self& operator<<( int);self& operator<<( unsigned int);self& operator<<( long);self& operator<<( unsigned long);self& operator<<( long long);self& operator<<( unsigned long long); self& operator<<( const void*); self& operator<<( float v);self& operator<<( double); self& operator<<( char v);self& operator<<( const char *); void append(const char* data, int len) { return m_buffer.append(data, len); }const Buffer& buffer() const { return m_buffer; } private:LogStream( const LogStream& ls); //no copyableLogStream& operator=( const LogStream& ls); template< typename T>void formatInteger(T v); Buffer m_buffer;static const int kMaxNumericSize = 32; }; class Fmt{public:template< typename T>Fmt( const char* fmt, T val); const char* data() const { return m_buf; }int length() const { return m_length; } private:char m_buf[ 32];int m_length;}; inline LogStream& operator<<(LogStream &s, const Fmt& fmt){s.append(fmt.data(), fmt.length());return s;} #endif
Logger類,使用LogStream的<<操做符將文件名,日誌級別,時間等信息格式好提早寫入LogBuffer中.
它其實是一個臨時對象,經過宏定義臨時構造一個logger對象,構造的時候即將上面信息寫入LogBuffer,以後經過stream()接口引用LogStream的<<操做符寫入咱們的日誌信息.
優勢是 :對於日誌級別低的log那麼它將是一個空的操做.
// CAUTION: do not write://// if (good)// LOG_INFO << "Good news";// else// LOG_WARN << "Bad news";//// this expends to//// if (good)// if (logging_INFO)// logInfoStream << "Good news";// else// logWarnStream << "Bad news";//#define LOG_TRACE if ( Logger::logLevel() <= Logger::TRACE) \Logger(__FILE__, __LINE__, Logger::TRACE, __func__) .stream()#define LOG_DEBUG if ( Logger::logLevel() <= Logger::DEBUG) \Logger(__FILE__, __LINE__, Logger::DEBUG, __func__) .stream()#define LOG_INFO if ( Logger::logLevel() <= Logger::INFO) \Logger(__FILE__, __LINE__) .stream()#define LOG_WARN Logger(__FILE__, __LINE__, Logger::WARN) .stream()#define LOG_ERROR Logger(__FILE__, __LINE__, Logger::ERROR) .stream()#define LOG_FATAL Logger(__FILE__, __LINE__, Logger::FATAL) .stream()#define LOG_SYSERR Logger(__FILE__, __LINE__, false) .stream()#define LOG_SYSFATAL Logger(__FILE__, __LINE__, true) .stream()
它的成員結構以下圖所示:
Impl對象是個私有類,主要是爲了閉源,構造Impl的時候後寫入時間戳,日誌級別,文件名等等.
logLevel()、setlogLevel()、爲兩個靜態成員函數,用於設置日誌級別.
setOutput()也是個靜態成員函數,主要做用是重定向日誌的輸出.默認是輸出至stdout.
#ifndef _LOGGER_HH#define _LOGGER_HH #include <string.h>#include "LogStream.hh"#include "TimeStamp.hh" // CAUTION: do not write://// if (good)// LOG_INFO << "Good news";// else// LOG_WARN << "Bad news";//// this expends to//// if (good)// if (logging_INFO)// logInfoStream << "Good news";// else// logWarnStream << "Bad news";//#define LOG_TRACE if (Logger::logLevel() <= Logger::TRACE) \Logger(__FILE__, __LINE__, Logger::TRACE, __func__).stream()#define LOG_DEBUG if (Logger::logLevel() <= Logger::DEBUG) \Logger(__FILE__, __LINE__, Logger::DEBUG, __func__).stream()#define LOG_INFO if (Logger::logLevel() <= Logger::INFO) \Logger(__FILE__, __LINE__).stream()#define LOG_WARN Logger(__FILE__, __LINE__, Logger::WARN).stream()#define LOG_ERROR Logger(__FILE__, __LINE__, Logger::ERROR).stream()#define LOG_FATAL Logger(__FILE__, __LINE__, Logger::FATAL).stream()#define LOG_SYSERR Logger(__FILE__, __LINE__, false).stream()#define LOG_SYSFATAL Logger(__FILE__, __LINE__, true).stream() class Logger{public:enum LogLevel{TRACE,DEBUG,INFO,WARN,ERROR,FATAL,NUM_LOG_LEVELS,}; //compile time calculation of basename of source fileclass SourceFile{public:template< int N>inline SourceFile( const char (&arr)[N]):m_data(arr),m_size(N -1){const char* slash = strrchr(m_data, '/'); // builtin functionif (slash){m_data = slash + 1;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 + 1;}m_size = static_cast< int>(strlen(m_data));} const char* m_data;int m_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(); static void setLogLevel(LogLevel level);static LogLevel logLevel(); LogStream& stream() { return m_impl.m_stream; } typedef void (*outputFunc)( const char *msg, int len);typedef void (*flushFunc)(); static void setOutput(outputFunc);static void setFlush(flushFunc); private:Logger( const Logger &lg); //no copyableLogger& operator=( const Logger &lg); class Impl{public:typedef Logger::LogLevel LogLevel;Impl(LogLevel level, int old_errno, const SourceFile& file, int line);void formatTime();void finish(); TimeStamp m_time;LogStream m_stream;LogLevel m_level;int m_line;SourceFile m_fileBaseName;}; Impl m_impl; }; #endif
完成Logger便可直接使用了。
直接經過宏LOG_DEBUG << 便可打印格式化好的日誌信息. 只是默認輸出到stdout.
後面文章會給出日誌文件類及異步線程類的實現.
//LogStream#include <stdio.h>#include <assert.h>#include <algorithm>#include "LogStream.hh" LogStream::LogStream(){} LogStream::~LogStream(){} LogStream& LogStream:: operator<<( bool v){m_buffer.append(v ? "1" : "0", 1);return * this;} LogStream& LogStream:: operator<<( short v){* this << static_cast< int>(v);return * this;} LogStream& LogStream:: operator<<( unsigned short v){* this << static_cast< unsigned int>(v);return * this;} LogStream& LogStream:: operator<<( int v){formatInteger(v);return * this;} LogStream& LogStream:: operator<<( unsigned int v){formatInteger(v);return * this;} LogStream& LogStream:: operator<<( long v){formatInteger(v);return * this;} LogStream& LogStream:: operator<<( unsigned long v){formatInteger(v);return * this;} LogStream& LogStream:: operator<<( long long v){formatInteger(v);return * this;} LogStream& LogStream:: operator<<( unsigned long long v){formatInteger(v);return * this;} LogStream& LogStream:: operator<<( const void*){printf( "undefine\n");} LogStream& LogStream:: operator<<( float v){* this << static_cast< double>(v);return * this;} LogStream& LogStream:: operator<<( double v){if(m_buffer.avail() >= kMaxNumericSize){int len = snprintf(m_buffer.current(), kMaxNumericSize, "%.12g", v);m_buffer.add(len);}return * this;} LogStream& LogStream:: operator<<( char v){m_buffer.append(&v, 1);return * this;} LogStream& LogStream:: operator<<( const char *str){if(str){m_buffer.append(str, strlen(str));} else{m_buffer.append( "(NULL)", 6);} return * this;} const char digits[] = "9876543210123456789";const char* zero = digits + 9; //convert to strtemplate< typename T>size_t convert( char buf[], T value){T i = value;char *p = buf; do{int lsd = static_cast< int>(i % 10);i /= 10;*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){if(m_buffer.avail() >= kMaxNumericSize){size_t len = convert(m_buffer.current(), v);m_buffer.add(len);}} template< typename T>Fmt::Fmt( const char* fmt, T val){m_length = snprintf(m_buf, sizeof(m_buf), fmt, val);assert( static_cast<size_t>(m_length) < sizeof(m_buf));} // Explicit instantiations template Fmt::Fmt( const char* fmt, char); template Fmt::Fmt( const char* fmt, short);template Fmt::Fmt( const char* fmt, unsigned short);template Fmt::Fmt( const char* fmt, int);template Fmt::Fmt( const char* fmt, unsigned int);template Fmt::Fmt( const char* fmt, long);template Fmt::Fmt( const char* fmt, unsigned long);template Fmt::Fmt( const char* fmt, long long);template Fmt::Fmt( const char* fmt, unsigned long long); template Fmt::Fmt( const char* fmt, float);template Fmt::Fmt( const char* fmt, double); //Logger #include <time.h>#include <stdint.h>#include <stdlib.h>#include <assert.h>#include <stdio.h>#include <errno.h>#include <string.h>#include "TimeStamp.hh"#include "Logger.hh" __thread char t_time[ 64];__thread time_t t_lastSecond;__thread char t_errnobuf[ 512]; const char* strerror_tl( int savedErrno){return strerror_r(savedErrno, t_errnobuf, sizeof(t_errnobuf));} Logger::LogLevel g_logLevel = Logger::INFO; void Logger::setLogLevel(LogLevel level){g_logLevel = level;} Logger::LogLevel Logger::logLevel(){return g_logLevel;} const char* LogLevelName[Logger::NUM_LOG_LEVELS] ={"[TRACE]","[DEBUG]","[INFO ]","[WARN ]","[ERROR]","[FATAL]",}; // helper class for known string length at compile timeclass T{public:T( const char* str, unsigned len):m_str(str),m_len(len){assert(strlen(str) == m_len);} const char* m_str;const unsigned m_len;}; void defaultOutput( const char *msg, int len){size_t n = fwrite(msg, 1, len, stdout);( void)n;} void defaultFlush(){fflush(stdout);} Logger::outputFunc g_output = defaultOutput;Logger::flushFunc g_flush = defaultFlush; void Logger::setOutput(outputFunc out){g_output = out;} void Logger::setFlush(flushFunc flush){g_flush = flush;} Logger::Logger(SourceFile file, int line): m_impl(INFO, 0, file, line){} Logger::Logger(SourceFile file, int line, LogLevel level): m_impl(level, 0, file, line){} Logger::Logger(SourceFile file, int line, bool toAbort): m_impl(toAbort? FATAL:ERROR, errno, file, line){} Logger::Logger(SourceFile file, int line, LogLevel level, const char* func): m_impl(level, 0, file, line){m_impl.m_stream << '[' << func << "] ";} Logger::~Logger(){m_impl.finish();const LogStream::Buffer& buf(stream(). buffer());g_output(buf.data(), buf.length());if (m_impl.m_level == FATAL){g_flush();abort();}} Logger::Impl::Impl(LogLevel level, int savedErrno, const SourceFile& file, int line): m_time(TimeStamp::now()),m_stream(),m_level(level),m_fileBaseName(file),m_line( line){formatTime();m_stream << LogLevelName[level] << ' ';m_stream << '[' << m_fileBaseName.m_data << ':' << m_line << "] ";if (savedErrno != 0){m_stream << strerror_tl(savedErrno) << " (errno=" << savedErrno << ") ";}} void Logger::Impl::finish(){m_stream<< '\n';} void Logger::Impl::formatTime(){int64_t microSecondsSinceEpoch = m_time.microSecondsSinceEpoch();time_t seconds = static_cast<time_t>(microSecondsSinceEpoch / TimeStamp::kMicroSecondsPerSecond);int microseconds = static_cast< int>(microSecondsSinceEpoch % TimeStamp::kMicroSecondsPerSecond);if (seconds != t_lastSecond){t_lastSecond = seconds;struct tm tm_time; ::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 + 1900, tm_time.tm_mon + 1, tm_time.tm_mday,tm_time.tm_hour + 8, tm_time.tm_min, tm_time.tm_sec);assert(len == 19); ( void)len;} Fmt us( ".%06d ", microseconds);assert(us.length() == 8);m_stream << t_time << us.data();}