c++ 日志类 线程安全+缓存

根据上一次的测试,有缓存的日志类性能会更好。用到了time.h类函数,所以在linux下就要改动一下了,windows环境下写的。

思路采用(参照muduo库的日志,不过认为他线程不安全,和没用缓存,就改造了下)

1.有一个总的缓存,logboss,为一个恶汉模式的单例类,指针对象为智能指针,析构函数讲缓存写入文件。

2.有一个logger类,作为临时缓存,析构函数,将里面的缓存写入总缓存(在总缓存写入的时候加锁)。如果缓存超过一定限度,就将前面的缓存写入文件先。

    void LogBoss::append( const char* data, int len )
    {
        std::unique_lock<std::mutex> l(mutex_);
        if( buffer_.avail() > len ){
            buffer_.append(data,len);
        }else{
            //write log file
            writeLogFile();
            buffer_.reset();
            buffer_.append(data,len);
        }
    }

3.logger类中,有一个logstream类对象,支持各种基本类型的<<输入,然后将输入转化为字符串放入到这个对象里面缓存数组里。

4.使用一个SourceFile类,只是用来方便的获取文件名。

5.使用一个FixedBuffer类,方便对字符串数组的操作,append(),fflush()之类。

通过这样的宏定义:

#define LOG_TRACE cyc::Logger(__FILE__, __LINE__, cyc::Logger::TRACE, __FUNCTION__).stream()
#define LOG_DEBUG cyc::Logger(__FILE__, __LINE__, cyc::Logger::DEBUG, __FUNCTION__).stream()
class Logger
    {
    public:
        enum LogLevel
        {
            TRACE,
            DEBUG,
            INFO,
            WARN,
            ERROR,
            FATAL,
            NUM_LOG_LEVELS,
        };
        Logger(SourceFile file, int line, LogLevel level, const char* func);
        ~Logger(void);
        LogStream& stream(){ return stream_;}
    private:
        LogStream stream_;
    };

调用方式为:(通过构造一个匿名Logger类对象,通过调用stream()返回一个Logstream对象来使用。

LOG_TRACE << "hello " << " "  __FILE__ << " " << __FUNCTION__ << " " << __LINE__ ;
    LOG_TRACE << "hello2 " << " "  __FILE__ << " " << __FUNCTION__ << " " << __LINE__ ;

opeartor<< 将传入的参数都添加到临时变量的缓存中,缓存是LogStream的成员变量。最后在析构函数中,通过LogBoss的静态公开的方法,获取LogBoss指针,然后将临时变量的缓存加入到总缓存。

Logger::~Logger(void)
    {
        // add "\n"
        stream_ << "\n";
        //output to logboss
        LogBoss::getInstance()->append(stream_.buffer().data(),stream_.buffer().length());
    }

上代码了:

总过5个文件Logger.h,Logger.cpp,LogStream.h,LogStream.cpp,main.cpp

main.cpp

#include <iostream>
#include "Logger.h"
#include <future>
#include <thread>
#include <mutex>
#include <memory>
#include <fstream>
using namespace std;

void fun1(){
    for(int i = 0; i <100;i++){
        cout <<"thread : "<< this_thread::get_id() << " i = " << i << endl;
        {
            LOG_TRACE << i;
        }
    }
}

void fun2(){
    for(int i = 0; i <100;i++){
        cout <<"thread : "<< this_thread::get_id() << " i = " << i << endl;
        {
            LOG_TRACE << i;
        }
    }
}

void fun3(){
    for(int i = 0; i <100;i++){
        cout <<"thread : "<< this_thread::get_id() << " i = " << i << endl;
        {
            LOG_TRACE << i;
        }
    }
}

void getfilename(char * filename,size_t len)
{
    time_t t = time(NULL);
    tm st;
    localtime_s(&st,&t);

    //will fill '\0'
    _snprintf_s(filename,len,_TRUNCATE,"%s\\%d%02d%02d%02d.log",LOGFILE,
        st.tm_year+1900,st.tm_mon+1,st.tm_mday,st.tm_hour);
}

char  filename[32];


int main(){

    getfilename(filename,sizeof(filename));
    {std::ofstream file(filename,std::ios::out);}
    LOG_TRACE << "hello " << " "  __FILE__ << " " << __FUNCTION__ << " " << __LINE__ ;
    LOG_TRACE << "hello2 " << " "  __FILE__ << " " << __FUNCTION__ << " " << __LINE__ ;
    LOG_TRACE << "hello3 ";
    LOG_TRACE << "hello4 ";
    auto f1 = async(launch::async,fun1);
    auto f2 = async(launch::async,fun2);
    auto f3 = async(launch::async,fun3);

    f1.get();
    f2.get();
    f3.get();
    system("pause");
    return 0;
}

Logger.h

#ifndef CYC_BASE_LOGGING_H
#define CYC_BASE_LOGGING_H

#include "LogStream.h"
#include <memory>
#include <mutex>
using namespace cyc;
using std::mutex;
using std::shared_ptr;

#define LOGFILE "log"

namespace cyc{
    const size_t filename_len = 32;
    class LogBoss{
    public:
//需要声明为友元才能使用他的私有构造函数 friend shared_ptr<LogBoss>;
//声明一个总缓存类型,kSmallBuffer = 4096 typedef detail::FixedBuffer<detail::kSmallBuffer> Buffer; void append(const char* data, int len);
   //获取智能指针 static shared_ptr<LogBoss> getInstance(); ~LogBoss(); private:
//调用append()的时候使用的mutex,保证线程安全 mutex mutex_; Buffer buffer_; char file_name_[filename_len];
//使用智能指针是为了,让他在程序关闭后,将目前的缓存写入文件 static shared_ptr<LogBoss> Logp; LogBoss(); void writeLogFile(); }; class Logger { public: // compile time calculation of basename of source file class SourceFile { public: template<int N> inline SourceFile(const char (&arr)[N]) : data_(arr), size_(N-1) { const char* slash = strrchr(data_, '\\'); // builtin function if (slash) { data_ = slash + 1; size_ -= static_cast<int>(data_ - arr); } } explicit SourceFile(const char* filename) : data_(filename) { const char* slash = strrchr(filename, '\\'); if (slash) { data_ = slash + 1; } size_ = static_cast<int>(strlen(data_)); } const char* data_; int size_; };
//目前只使用了两个等级,还没加入设置等级的函数 enum LogLevel { TRACE, DEBUG, INFO, WARN, ERROR, FATAL, NUM_LOG_LEVELS, };
//通过这些参数,来完善日志的格式 Logger(SourceFile file, int line, LogLevel level, const char* func); ~Logger(void); LogStream& stream(){ return stream_;} private: LogStream stream_; }; } #define LOG_TRACE cyc::Logger(__FILE__, __LINE__, cyc::Logger::TRACE, __FUNCTION__).stream() #define LOG_DEBUG cyc::Logger(__FILE__, __LINE__, cyc::Logger::DEBUG, __FUNCTION__).stream() #endif // CYC_BASE_LOGGING_H

Logger.cpp

#include "Logger.h"
#include <fstream>

#include <time.h>
#include <ctime>

namespace cyc{

    std::string printf_loacltime(){
        // equal ctime(&t);
        time_t t = time(NULL);
        std::string ts = std::asctime(localtime(&t));
        ts.resize(ts.size()-1);    //skip trailing newline
        return ts;
    }

    void getfilename(char * filename,size_t len)
    {
        time_t t = time(NULL);
        tm st;
        localtime_s(&st,&t);

        //will fill '\0'
        _snprintf_s(filename,len,_TRUNCATE,"%s\\%d%02d%02d%02d.log",LOGFILE,
            st.tm_year+1900,st.tm_mon+1,st.tm_mday,st.tm_hour);
    }

    const char* LogLevelName[Logger::NUM_LOG_LEVELS] =
    {
        "TRACE ",
        "DEBUG ",
        "INFO  ",
        "WARN  ",
        "ERROR ",
        "FATAL ",
    };
//-------------------------------LogBoss Start---------------------------------------------
    shared_ptr<LogBoss> LogBoss::Logp(new LogBoss);

    LogBoss::LogBoss()
    {

    }

    LogBoss::~LogBoss()
    {
        //need had log folder before
        {
            std::unique_lock<std::mutex> l(mutex_);
            writeLogFile();
        }
    }

    void LogBoss::append( const char* data, int len )
    {
        std::unique_lock<std::mutex> l(mutex_);
        if( buffer_.avail() > len ){
            buffer_.append(data,len);
        }else{
            //write log file
            writeLogFile();
            buffer_.reset();
            buffer_.append(data,len);
        }
    }

    shared_ptr<LogBoss> LogBoss::getInstance()
    {
        return Logp;
    }

    //need lock before use
    void LogBoss::writeLogFile()
    {
        // out put    
        getfilename(file_name_,sizeof(file_name_));
        std::ofstream file(file_name_,std::ios::out | std::ios::app);
        file << buffer_.data();    
    }

//-------------------------------LogBoss End----------------------------------------------



//-------------------------------Logger Start---------------------------------------------

    Logger::Logger( SourceFile file, int line, LogLevel level, const char* func )
    {
//日志格式 stream_ << LogLevelName[level] <<" "<< printf_loacltime() <<" "<<file.data_<<" "<<line <<" "<<func<<" : "; } Logger::~Logger(void) { // add "\n" stream_ << "\n"; //output to logboss LogBoss::getInstance()->append(stream_.buffer().data(),stream_.buffer().length()); } } //-------------------------------Logger End--------------------------------------------

LogStream.h

#ifndef CYC_BASE_LOGSTREAM_H
#define CYC_BASE_LOGSTREAM_H

#include <string>
using std::string;

namespace cyc{

    namespace detail{
        const int kTempBuffer  = 512;
        const int kSmallBuffer = 4096;
        const int kLargeBuffer = 4096*1000;

        template<int SIZE>
        class FixedBuffer
        {
        public:
            FixedBuffer()
                : cur_(data_){}

            ~FixedBuffer(){}

            void append(const char* buf, size_t len)
            {
                // FIXME: append partially,ensure have \0
                if (static_cast<size_t>(avail()) > len)
                {
                    memcpy(cur_, buf, len);
                    cur_ += len;
                    *cur_ = '\0';
                }
            }

            const char* data() const { return data_; }
            int length() const { return static_cast<int>(cur_ - data_); }

            //count the least size,and leave '\n' and '\0'
            int avail() const { return static_cast<int>(end() - cur_ - 2); }

            void reset() { cur_ = data_; }
        private:
            const char* end() const { return data_ + sizeof data_; }

            char data_[SIZE];
            char* cur_;
        private:
            FixedBuffer(FixedBuffer& );
            FixedBuffer& operator=(FixedBuffer&);
        };
    }

    class LogStream
    {
    public:
//声明一个临时缓存的类型,kTempBuffer = 512. typedef detail::FixedBuffer<detail::kTempBuffer> Buffer; LogStream(void); ~LogStream(void);
//这里让他能接受各种基本参数 LogStream& operator<<(short); LogStream& operator<<(unsigned short); LogStream& operator<<(int); LogStream& operator<<(unsigned int); LogStream& operator<<(long); LogStream& operator<<(unsigned long); LogStream& operator<<(long long); LogStream& operator<<(unsigned long long); LogStream& operator<<(const void*); LogStream& operator<<(float v) { *this << static_cast<double>(v); return *this; } LogStream& operator<<(double);
//本质就是,转化为字符,字符串类型,然后调用FixBuffer中的append()方法 LogStream& operator<<(char v) { buffer_.append(&v, 1); return *this; } LogStream& operator<<(const char* v) { buffer_.append(v, strlen(v)); return *this; } LogStream& operator<<(const string& v) { buffer_.append(v.c_str(), v.size()); return *this; } void append(const char* data, int len) { buffer_.append(data, len); } const Buffer& buffer() const { return buffer_; } private: template<typename T> void formatInteger(T); Buffer buffer_; static const int kMaxNumericSize = 32; }; } #endif // CYC_BASE_LOGSTREAM_H

LogStream.cpp

#include "LogStream.h"
#include <cstdio>


using namespace cyc;
using namespace cyc::detail;

namespace cyc
{
    namespace detail
    {

        const char digits[] = "0123456789";

        const char digitsHex[] = "0123456789ABCDEF";

        // Efficient Integer to String Conversions, by Matthew Wilson.
        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++ = digits[lsd];
            } while (i != 0);

            if (value < 0)
            {
                *p++ = '-';
            }
            *p = '\0';
            std::reverse(buf, p);

            return p - buf;
        }

    }
}

LogStream::LogStream(void)
{
}


LogStream::~LogStream(void)
{
}

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* p)
{
    char temp[kMaxNumericSize];
    int len = _snprintf_s(temp, kMaxNumericSize,_TRUNCATE, "0x%08X", p);
    if(len)
        buffer_.append(temp,len);
    return *this;
}

// FIXME: replace this with Grisu3 by Florian Loitsch.
LogStream& LogStream::operator<<(double v)
{
    char temp[kMaxNumericSize];
    int len = _snprintf_s(temp, kMaxNumericSize,_TRUNCATE, "%.12g", v);
    if(len)
        buffer_.append(temp,len);
    return *this;
}

template<typename T>
void LogStream::formatInteger(T v)
{
    char temp[kMaxNumericSize];
    size_t len = convert(temp, v);//在上面的实现中可以到,已经一步步把数据转化为字符放到缓冲区中
    buffer_.append(temp,len);//增加偏移长度
}