一个轻巧高效的多线程c++stream风格异步日志(一)

一个轻巧高效的多线程c++stream风格异步日志


前言

本文主要实现muduo的异步日志.省去boost库中特性的引用,轻巧简洁。

对于编程而言,日志是必不可少的,对于关键进程,日志通常需要记录: 
1.收到的每条内部消息id. 
2.收到的每条外部消息全文. 
3.发出的每条消息全文,每条消息都有全局唯一的id. 
4.关键内部状态变更,等等. 
5.每条日志都有时间戳,以方便的追踪分布式系统中一个时间的来龙去脉.

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++的stream风格, 优点是使用起来更自然,不用保持格式的一致性.

功能需求

常规的通用日志库如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。
  • 不阻塞正常的执行流程
  • 在多线程程序中,不造成争用

为了实现性能指标,本日志设计中几点优化:

  • 时间戳字符串中的日期和时间两部分是缓存的,一秒之内的多条日志只需格式化微妙即可
  • 日志消息 的前四段是定长的,可避免运行时字串长度的计算
  • 线程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(); 
} 
 
posted @   靑い空゛  阅读(2813)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示