前面面试的时候被要求设计一个日志系统,日志信息可以发送到多个目的地,比如文件存储、网络、控制台、数据库等。我的大概的思路是下面这样的,上代码。请大神们多多指教。
#pragma once
#include <queue>
#include <vector>
#include <mutex>
#include <string>
#include <stdio.h>
#include <thread>
#include <array>
#include <stdarg.h>
#include <condition_variable>
const int BUFF_SIZE = 1024;
enum LOG_LEVEL
{
LOG_INFO,
LOG_DEBUG,
LOG_WARN,
LOG_ERROR,
};
class AbsOutput
{
public:
virtual void mvWrite(const std::string& str) = 0;
virtual bool mbGetError() const { return false; }
protected:
bool mbError = false;
};
class LogDispatch
{
public:
LogDispatch(std::shared_ptr<AbsOutput> io, LOG_LEVEL lv)
: mopIO(io)
, mLevel(lv > LOG_ERROR ? LOG_ERROR : lv < LOG_INFO ? LOG_INFO : lv)
, mbExit(false)
{
mThread = std::thread(&LogDispatch::threadFunc, this);
}
~LogDispatch()
{
mbExit = true;
moCondition.notify_one();
if (mThread.joinable()) mThread.join();
std::cout << "output exit" << std::endl;
}
void mvAdd(std::string& msg)
{
{
std::unique_lock<std::mutex> lock(moQueMutex);
mqueMsg.push(msg);
}
moCondition.notify_one();
}
bool mbGetError() const
{
if (mopIO) return mopIO->mbGetError();
return true;
}
virtual void mvFlush()
{
std::queue<std::string> vec_msg;
{
std::unique_lock<std::mutex> lock(moQueMutex);
vec_msg.swap(mqueMsg);
}
while (!vec_msg.empty())
{
std::string msg = vec_msg.front();
mopIO->mvWrite(msg);
vec_msg.pop();
}
}
void mvLoop()
{
while (!mbExit)
{
if (mqueMsg.empty())
{
std::unique_lock<std::mutex> lock(moWaitMut);
{
moCondition.wait(lock);
}
}
if (!mqueMsg.empty()) mvFlush();
if (mbExit) break;
}
}
static void threadFunc(LogDispatch* out)
{
out->mvLoop();
}
LOG_LEVEL mGetLevel() { return mLevel; }
protected:
std::queue<std::string> mqueMsg;
std::mutex moQueMutex;
std::mutex moWaitMut;
std::condition_variable moCondition;
LOG_LEVEL mLevel;
std::shared_ptr<AbsOutput> mopIO;
bool mbExit;
std::thread mThread;
};
class FileOutput : public AbsOutput
{
public:
FileOutput(const std::string&& strFile)
: AbsOutput()
, mstrFile(strFile)
{
mopFd = fopen(strFile.c_str(), "a+");
if (!mopFd)
{
mbError = true;
}
}
~FileOutput()
{
if (mopFd != NULL) fclose(mopFd);
std::cout << "FileOutput exit" << std::endl;
}
virtual bool mbGetError() const { return mbError; }
virtual void mvWrite(const std::string& msg)
{
fwrite(msg.c_str(), sizeof(char), msg.length(), mopFd);
fflush(mopFd);
}
private:
std::string mstrFile;
FILE* mopFd;
bool mbExit;
};
class SocketOutput : public AbsOutput
{
public:
SocketOutput(int fd, LOG_LEVEL lv)
: AbsOutput()
, miFd(fd)
{
}
virtual void mvWrite(const std::string& msg)
{
//send(miFd, msg.c_str(), msg.length(), 0);
}
private:
int miFd;
};
class Log
{
public:
void mvWrite(std::string str, LOG_LEVEL lv)
{
if (mvecOutput.empty()) return;
for (std::shared_ptr<LogDispatch> out : mvecOutput)
{
if (out == NULL) continue;
if (!out->mbGetError()) out->mvAdd(str);
}
}
void mvLog(const char* file, const char* function, int iLineNo, LOG_LEVEL lv, const char* msg, ...)
{
std::string s_level = msGetLogLevel(lv);
const std::string s_t = msGetCurSysTime();
char buff[BUFF_SIZE] = { 0 };
int n = snprintf(buff, BUFF_SIZE, "%s %s %s %d %s[", s_t.c_str(), file, function, iLineNo, s_level.c_str());
va_list lst_va;
va_start(lst_va, msg);
int m = vsnprintf(buff + n, BUFF_SIZE - 3, msg, lst_va); // -3保证后面加]和\n不溢出
buff[n + m] = ']';
buff[n + m + 1] = '\n';
std::string s(buff);
mvWrite(s, lv);
}
void mvRegister(std::shared_ptr<AbsOutput> out, LOG_LEVEL lv)
{
std::shared_ptr<LogDispatch> sp(new LogDispatch(out, lv));
mvecOutput.push_back(sp);
}
static Log* mopGetInstance()
{
static Log log;
return &log;
}
std::string msGetLogLevel(LOG_LEVEL lv)
{
static std::array<std::string, 4> arr{"Info ", "Debug", "WARN " , "ERROR"};
return arr[int(lv)];
}
private:
const std::string msGetCurSysTime()
{
// 获取当时系统时间
auto t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
struct tm* ptm = localtime(&t);
char date[60] = { 0 };
sprintf(date, "%d-%02d-%02d %02d:%02d:%02d",
(int)ptm->tm_year + 1900, (int)ptm->tm_mon + 1, (int)ptm->tm_mday,
(int)ptm->tm_hour, (int)ptm->tm_min, (int)ptm->tm_sec);
return move(std::string(date));
}
std::vector<std::shared_ptr<LogDispatch> > mvecOutput;
std::mutex moMutex;
};
#define LOG_DEBUG(msg, ...) \
{\
Log::mopGetInstance()->mvLog(__FILE__, __FUNCTION__, __LINE__, LOG_DEBUG, msg, ##__VA_ARGS__); \
}
/**/
#define LOG_REGISTER(out, lv) \
{ \
Log::mopGetInstance()->mvRegister(out, lv); \
}
void t1()
{
for (int i = 0; i < 10000; i++)
{
LOG_DEBUG(" ddddddddddddddddddddddddddddddddddddddddddd");
}
}
void test()
{
std::shared_ptr<AbsOutput> out1(new FileOutput(std::string("test.log")));
LOG_REGISTER(out1, LOG_INFO);
auto start = std::chrono::system_clock::now();
std::vector<std::thread> vec;
for (int i = 0; i < 10; i++)
{
std::thread s(t1);
vec.push_back(std::move(s));
}
for (size_t i = 0; i < vec.size(); i++)
{
vec[i].join();
}
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> diff = end - start;
std::string s = "used time:" + std::to_string(diff.count());
LOG_DEBUG(s.c_str());
}