一個輕巧高效的多線程c++stream風格異步日誌(一)

一個輕巧高效的多線程c++stream風格異步日誌


前言

本文主要實現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 :445
20180802 12 :42 :50.621359 44321 INFO main just test - test .cpp :446
20180802 12 :42 :50.621364 44321 ERROR main just test - test .cpp :447
  • 時間戳精確到微妙,每條消息經過gettimeofday(2)獲取當前時間.這樣作不會有什麼性能損失,在x86-64Linux上,gettimeofday()不是系統調用,不會陷入內核
  • 打印線程ID,便於分析多線程程序時序,也能夠檢測死鎖(未加入)
  • 打印日誌級別,經過查看有無ERROR日誌快速定位
  • 打印文件名和行號,修復問題不至於搞錯對象
  • 每條日誌儘可能佔一行,便於分析日誌

性能需求

只有日誌庫足夠高效,程序員纔敢在代碼中輸出足夠多的診斷信息,減少運維難度,提高效率.它體如今:多線程

  • 每秒幾千萬條日誌沒有明顯的性能損失
  • 能應對一個進程產生大量日誌數據的場景,如1GB/min。
  • 不阻塞正常的執行流程
  • 在多線程程序中,不形成爭用

爲了實現性能指標,本日誌設計中幾點優化:app

  • 時間戳字符串中的日期和時間兩部分是緩存的,一秒以內的多條日誌只需格式化微妙便可
  • 日誌消息 的前四段是定長的,可避免運行時字串長度的計算
  • 線程id是預先格式化的字符串
  • 原文件名部分經過編譯期計算來得到,避免運行期strrchr()的開銷

Logger實現

LogStream類

LogStream類,主要做用是重載<<操做符,將基本類型的數據格式成字符串經過append接口存入LogBuffer中。

Alt text

 
#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 partially
if ( /*implicit_cast<size_t>*/(avail()) > len)
{
memcpy(m_cur, buf, len);
m_cur += len;
}
}
// write in m_data directly
char* 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 copyable
LogStream& 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類

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.

Alt text

 
#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 file
class 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 function
if (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 copyable
Logger& 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及Logger cpp源碼

 
//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 str
template< 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 time
class 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();
}
相關文章
相關標籤/搜索