转载c++11 简易日志库
原作者链接: ELog简单日志库
- 在原作者基础上修改,屏蔽掉ini配置文件,只需要在项目中包含elog.h、elog.cpp,在文件中引用elog.h就可以使用。
elog.h
#pragma once
#include <map>
#include <list>
#include <mutex>
#include <memory>
#include <string>
#include <atomic>
#include <sstream>
#include <fstream>
#include <thread>
#include <condition_variable>
#include <string.h>
#include <sys/stat.h>
/**
* @brief 使用流式方式将日志级别level的日志写入到logger c++风格
*/
#define ELOG_LEVEL(level) \
if(level != ELOG::LogLevel::UNKNOW) \
ELOG::LogEventWrap( \
ELOG::LogEvent::ptr( \
new ELOG::LogEvent(level, __FILE__, __LINE__, ELOG::Utils::GetTimeStamp(), "") \
)).GetSS()
#define log_debug ELOG_LEVEL(ELOG::LogLevel::DEBUG)
#define log_info ELOG_LEVEL(ELOG::LogLevel::INFO)
#define log_warn ELOG_LEVEL(ELOG::LogLevel::WARN)
#define log_error ELOG_LEVEL(ELOG::LogLevel::ERROR)
#define log_fatal ELOG_LEVEL(ELOG::LogLevel::FATAL)
/**
* @brief 使用格式化方式将日志级别level的日志写入到logger c风格
*/
#define ELOG_FMT_LEVEL(level, fmt, ...) \
if(level != ELOG::LogLevel::UNKNOW) \
ELOG::LogEventWrap( \
ELOG::LogEvent::ptr(new ELOG::LogEvent(level,__FILE__, __LINE__, ELOG::Utils::GetTimeStamp(), "") \
)).GetEvent()->Format(fmt, __VA_ARGS__)
#define ELOG_FMT_DEBUG(fmt, ...) ELOG_FMT_LEVEL(ELOG::LogLevel::DEBUG, fmt, __VA_ARGS__)
#define ELOG_FMT_INFO(fmt, ...) ELOG_FMT_LEVEL(ELOG::LogLevel::INFO, fmt, __VA_ARGS__)
#define ELOG_FMT_WARN(fmt, ...) ELOG_FMT_LEVEL(ELOG::LogLevel::WARN, fmt, __VA_ARGS__)
#define ELOG_FMT_ERROR(fmt, ...) ELOG_FMT_LEVEL(ELOG::LogLevel::ERROR, fmt, __VA_ARGS__)
#define ELOG_FMT_FATAL(fmt, ...) ELOG_FMT_LEVEL(ELOG::LogLevel::FATAL, fmt, __VA_ARGS__)
namespace ELOG
{
/// 输出目标枚举
enum class OUT_MODEL : uint8_t
{
OUT_CONSOLE = 0,
OUT_FILE,
OUT_SOCK ///< 暂不支持
};
class Utils
{
public:
/**
* @brief 根据时间戳获取本地时间 Format: 类似"%Y-%m-%d %H:%M:%S" %Y表示year %m表示月 %d表示日 %H表示时 %M表示分 %S表示秒,其他符号视需求而定
* @return 返回格式化的字符串
*/
static std::string GetTime(time_t p_timestamp, const char *p_szFormat);
/**
* @brief 获取时间戳
* @return 返回时间戳
*/
static time_t GetTimeStamp();
/**
* @brief 获取文件大小,文件已打开则无法获取
* @param[in] strPath 文件名称
* @return 返回文件大小
*/
static long GetFileLength(const std::string &p_strPath);
/**
* @brief 获取线程ID
* @return 返回线程ID字符串
*/
static std::string GetThreadId(const std::thread::id &p_ThreadId);
};
/**
* @brief 日志级别
*/
class LogLevel
{
public:
/// 日志级别
enum Level
{
/// 未知级别
UNKNOW = 0,
/// DEBUG 级别
DEBUG = 1,
/// INFO 级别
INFO = 2,
/// WARN 级别
WARN = 3,
/// ERROR 级别
ERROR = 4,
/// FATAL 级别
FATAL = 5
};
/**
* @brief 将日志级别转成文本输出
* @param[in] level 日志级别
*/
static const char* ToString(LogLevel::Level p_LevelId);
/**
* @brief 将文本转换成日志级别
* @param[in] str 日志级别文本
*/
static LogLevel::Level FromString(const std::string& p_strFrom);
};
/// 每一行日志需要输出的项
/// 时间 级别 线程id 线程名 用户日志 文件名:行号
class LogItem
{
public:
uint32_t m_nLineNum; /// 行号
uint64_t m_llTime; /// 时间戳
std::string msg; /// 用户日志
std::string m_strThreadName; /// 线程名称
std::string m_strFileName; /// 文件名
std::thread::id m_threadId; /// 线程id
LogLevel::Level m_LevelId; /// 日志级别
};
class LogEvent /// 日志事件
{
public:
// 日志文件名
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;
};
using ptr = std::shared_ptr<LogEvent>;
/**
* @brief 构造函数
* @param[in] level 日志级别
* @param[in] file 文件名
* @param[in] line 文件行号
* @param[in] time 日志时间(秒)
* @param[in] thread_name 线程名称
*/
LogEvent(LogLevel::Level p_LevelId, SourceFile p_fileName, uint32_t p_nLine, uint64_t p_time,
const std::string &p_strThread_name = "");
/**
*@brief 返回日志包含项,时间,行号等信息
*/
inline const LogItem &GetLogItem() const { return m_logItem; }
inline void SetContent(const std::string &LogMsg) { m_logItem.msg = LogMsg; }
/**
* @brief 返回日志内容字符串流
*/
inline std::stringstream &GetSS() { return m_ss; }
/**
* @brief 格式化写入日志内容
*/
void Format(const char* p_szfmt, ...);
void Format(const char* p_szfmt, va_list al);
private:
LogItem m_logItem; /// 日志包含项
std::stringstream m_ss; /// 日志内容流
};
/**
* @brief 日志输出目标
*/
class LogAppender
{
public:
using ptr = std::shared_ptr<LogAppender>;
/**
* @brief 析构函数
*/
virtual ~LogAppender() {}
/**
* @brief 开一个线程输出
*/
virtual void Start() final;
/**
* @brief 结束线程
*/
virtual void Stop() final;
/**
* @brief 写入日志
*/
virtual void log(LogEvent::ptr event) = 0;
/**
* @brief 添加新事件,这个函数是需要被多线程调用的
*/
inline void AddEvent(const LogEvent::ptr &event);
protected:
void Run();
protected:
std::mutex m_mutex;
std::atomic_bool m_isRun{ false };
std::condition_variable m_cv;
std::list<LogEvent::ptr> m_listEvent;
};
/**
* @brief 输出管理器
*/
class LogAppenderManager
{
public:
inline void AddAppender(OUT_MODEL out, std::shared_ptr<LogAppender> &logAppender)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_mapLogAppender.insert(std::pair<OUT_MODEL, std::shared_ptr<LogAppender>>(out, logAppender));
}
inline void RemoveAppender(OUT_MODEL out)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_mapLogAppender.erase(out);
}
void Start();
void Stop();
void AddEvent(const LogEvent::ptr &event);
private:
std::mutex m_mutex;
std::map<OUT_MODEL, std::shared_ptr<LogAppender> > m_mapLogAppender;
std::atomic_bool m_isRun{ false };
};
/**
* @brief 输出到控制台的Appender
*/
class StdoutLogAppender : public LogAppender
{
public:
using ptr = std::shared_ptr<StdoutLogAppender>;
StdoutLogAppender();
void log(LogEvent::ptr event);
};
/**
* @brief 输出到文件的Appender
* 思考:写文件有必要单独开一个线程吗?
*/
class FileLogAppender : public LogAppender
{
public:
using ptr = std::shared_ptr<FileLogAppender>;
FileLogAppender(const std::string& p_filename, size_t p_nmaxSize, bool p_boverwrite = false);
~FileLogAppender() override;
void log(LogEvent::ptr p_event) override;
/**
* @brief 重新打开日志文件
* @return 成功返回true
*/
bool ReOpen();
/**
* @brief 关闭日志文件
*/
void Close();
private:
/// 文件路径
std::string m_filename;
/// 文件大小 单位MB
uint32_t m_fileSize{ 0 };
/// 是否覆盖文件
bool m_overwrite{ false };
/// 文件流
std::ofstream m_filestream;
};
class Logger
{
public:
using ptr = std::shared_ptr<Logger>;
/**
* @brief 单例的接口
*/
static std::shared_ptr<Logger> &Instance();
/**
* @brief 日志器析构
* 不写成保护类型或私有类型,是因为使用的智能指针创建的单例,保护后无法delete
*/
~Logger();
/**
* @brief 禁用拷贝和赋值
*/
Logger(const Logger &other) = delete;
Logger(const Logger &&other) = delete;
Logger &operator=(const Logger &other) = delete;
Logger &operator=(const Logger &&other) = delete;
/**
* @brief 日志器初始化
* @param[in] cfgFile 文件名称
*/
void Init(const std::string &p_cfgFile);
/**
* @brief 添加新的日志事件
* @param[in] p 事件指针
*/
void AddEvent(const LogEvent::ptr &p);
private:
/**
* @brief 日志器构造
* 保护起来,不让其他地方进行Logger的实例化操作
*/
Logger();
std::shared_ptr<LogAppenderManager> m_LogAppenderManager; /// 输出管理,对他需不需要进行上锁管理
};
/**
* @brief 日志事件包装器
*/
class LogEventWrap
{
public:
/**
* @brief 构造函数
* @param[in] e 日志事件
*/
LogEventWrap(LogEvent::ptr e);
/**
* @brief 析构函数
*/
~LogEventWrap();
/**
* @brief 获取日志事件
*/
inline LogEvent::ptr GetEvent() const { return m_event; }
/**
* @brief 获取日志内容流
*/
inline std::stringstream &GetSS() const { return m_event->GetSS(); }
private:
/**
* @brief 日志事件
*/
LogEvent::ptr m_event;
};
} // namespace ELOG
elog.cpp
#include "ELog.h"
#include <iostream>
#include <cstdarg>
#include <algorithm>
/// 线程休眠
//#define mSleep(ms) std::this_thread::sleep_for(std::chrono::milliseconds(ms)
//#define uSleep(us) std::this_thread::sleep_for(std::chrono::microseconds(us)
/// 定义换行符
#undef LINE_END_SYMBOL
#ifndef LINE_END_SYMBOL
# ifdef _MSC_VER
# define LINE_END_SYMBOL "\r\n"
# else
# define LINE_END_SYMBOL "\n"
# endif
#endif
/// 定义vasprintf
#if !defined(HAVE_VASPRINTF)
# if defined(_MSC_VER)
int vasprintf(char **ptr, const char *format, va_list ap)
{
int len;
len = _vscprintf_p(format, ap) + 1;
*ptr = (char *)malloc(len * sizeof(char));
if (!*ptr)
{
return -1;
}
return _vsprintf_p(*ptr, len, format, ap);
}
# else
int vasprintf(char **ptr, const char *format, va_list ap)
{
va_list ap_copy;
/* Make sure it is determinate, despite manuals indicating otherwise */
*ptr = nullptr;
va_copy(ap_copy, ap);
int count = vsnprintf(nullptr, 0, format, ap);
if (count >= 0)
{
char* buffer = (char*)malloc(count + 1);
if (buffer != nullptr)
{
count = vsnprintf(buffer, count + 1, format, ap_copy);
if (count < 0)
free(buffer);
else
*ptr = buffer;
}
}
va_end(ap_copy);
return count;
}
# endif
#endif /* !HAVE_VASPRINTF */
#if !defined(HAVE_ASPRINTF)
int asprintf(char **ptr, const char *format, ...)
{
va_list ap;
int ret;
*ptr = nullptr;
va_start(ap, format);
ret = vasprintf(ptr, format, ap);
va_end(ap);
return ret;
}
#endif /* !HAVE_ASPRINTF */
namespace ELOG
{
std::string Utils::GetTime(time_t p_timestamp, const char* p_szFormat)
{
std::tm tm;
#if defined(_MSC_VER)
localtime_s(&tm, &p_timestamp);
#elif defined(__GNUC__)
localtime_r(&p_timestamp, &tm);
#else
/// do nothing
#endif
char buf[64];
strftime(buf, sizeof(buf), p_szFormat, &tm);
return std::string(buf, strlen(buf));
}
time_t Utils::GetTimeStamp()
{
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds> tp = std::chrono::time_point_cast<std::chrono::seconds>(std::chrono::system_clock::now());
return std::chrono::duration_cast<std::chrono::seconds>(tp.time_since_epoch()).count();
}
long Utils::GetFileLength(const std::string &p_strPath)
{
struct stat info;
::stat(p_strPath.c_str(), &info);
return info.st_size;
}
std::string Utils::GetThreadId(const std::thread::id & p_ThreadId)
{
std::ostringstream oss;
oss.setf(std::ios::left); //设置对齐方式为left
oss.width(6); //设置宽度为7,不足用空格填充
oss << p_ThreadId;
return oss.str();
}
const char *LogLevel::ToString(LogLevel::Level p_LevelId)
{
switch (p_LevelId)
{
/// 使用宏函数来简化代码
case LogLevel::Level::DEBUG:
return "DEBUG";
case LogLevel::Level::INFO:
return "INFO ";
case LogLevel::Level::WARN:
return "WARN ";
case LogLevel::Level::ERROR:
return "ERROR";
case LogLevel::Level::FATAL:
return "FATAL";
default:
return "UNKNOW";
}
}
LogLevel::Level LogLevel::FromString(const std::string & p_strFrom)
{
/// 将str字符全部转换成大写,并存储在tStr中
std::string tStr(p_strFrom);
std::transform(tStr.begin(), tStr.end(), tStr.begin(), ::toupper);
#define XX(name, v) \
if(tStr == #v) { \
return LogLevel::Level::name; \
}
XX(DEBUG, DEBUG);
XX(INFO, INFO);
XX(WARN, WARN);
XX(ERROR, ERROR);
XX(FATAL, FATAL);
return LogLevel::Level::UNKNOW;
#undef XX
}
LogEvent::LogEvent(LogLevel::Level p_LevelId, SourceFile p_fileName, uint32_t p_nLine, uint64_t p_time,
const std::string& p_strThread_name)
{
m_logItem.m_strFileName = p_fileName.m_data;
m_logItem.m_nLineNum = p_nLine;
m_logItem.m_llTime = p_time;
m_logItem.m_LevelId = p_LevelId;
m_logItem.m_strThreadName = p_strThread_name;
m_logItem.m_threadId = std::this_thread::get_id();
}
void LogEvent::Format(const char * p_szfmt, ...)
{
va_list al;
va_start(al, p_szfmt);
Format(p_szfmt, al);
va_end(al);
}
void LogEvent::Format(const char * p_szfmt, va_list al)
{
char* buf = nullptr;
int nLen = vasprintf(&buf, p_szfmt, al);
if (nLen != -1)
{
m_ss << std::string(buf, nLen);
free(buf);
}
}
StdoutLogAppender::StdoutLogAppender()
{
#ifdef _MSC_VER
// 控制台显示乱码纠正
system("chcp 65001"); //设置字符集 (使用SetConsoleCP(65001)设置无效,原因未知)
#endif // WIN32
}
void StdoutLogAppender::log(LogEvent::ptr event)
{
/// 时间 级别 线程id 线程名 用户日志 文件名:行号
std::cout << "[" << Utils::GetTime(event->GetLogItem().m_llTime, "%Y-%m-%d %H:%M:%S") << "] ["
<< LogLevel::ToString(event->GetLogItem().m_LevelId) << "] ["
<< Utils::GetThreadId(event->GetLogItem().m_threadId) << "] ["
// << event->GetLogItem().threadName << ":"
<< event->GetLogItem().m_strFileName << ":"
<< event->GetLogItem().m_nLineNum << "] "
<< event->GetLogItem().msg << std::endl;
// std::cout.flush();
}
FileLogAppender::FileLogAppender(const std::string& p_filename, size_t p_nmaxSize, bool p_boverwrite)
: m_filename(p_filename),
m_fileSize(p_nmaxSize),
m_overwrite(p_boverwrite)
{
ReOpen();
}
FileLogAppender::~FileLogAppender()
{
Close();
}
void FileLogAppender::log(LogEvent::ptr p_event)
{
/// 判断文件大小,若超过10M,则将现有文件另存为,下一条日志写新文件里面
// m_filestream.seekp(0, SEEK_END); /// 定位到文件尾部,其实可以不用加这行代码
if (m_filestream.tellp() >= (m_fileSize* 1024 * 1024))
{
/// 关闭文件流
m_filestream.flush();
m_filestream.close();
if (m_overwrite)
{
// 覆盖旧文件
remove(m_filename.c_str());
}
else
{
size_t nPos = m_filename.find_last_of('.');
time_t nCurTime = Utils::GetTimeStamp();
std::string strTime = Utils::GetTime(nCurTime, "%Y%m%d%H%M%S");
std::string strFilenew = m_filename;
std::string strFileold = m_filename;
strFilenew = strFilenew.insert(nPos, strTime);
/// 文件改名
rename(strFileold.c_str(), strFilenew.c_str()); /// 文件名中不能包含特殊字符,否则会调用失败,返回-1
}
}
if (!m_filestream.is_open())
{
ReOpen();
}
/// 写文件
m_filestream << "[" << Utils::GetTime(p_event->GetLogItem().m_llTime, "%Y-%m-%d %H:%M:%S") << "] ["
<< LogLevel::ToString(p_event->GetLogItem().m_LevelId) << "] ["
<< Utils::GetThreadId(p_event->GetLogItem().m_threadId) << "] ["
<< p_event->GetLogItem().m_strFileName << ":"
<< p_event->GetLogItem().m_nLineNum << "] "
<< p_event->GetLogItem().msg << std::endl;
//m_filestream.flush();
}
bool FileLogAppender::ReOpen()
{
if (m_filestream.is_open())
{
m_filestream.close();
}
m_filestream.open(m_filename.c_str(), std::ios::out | std::ios::app | std::ios::binary);
return m_filestream.is_open();
}
void FileLogAppender::Close()
{
if (m_filestream.is_open())
{
m_filestream.flush();
m_filestream.close();
}
}
void LogAppenderManager::Start()
{
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_mapLogAppender.begin();
for (; it != m_mapLogAppender.end(); ++it)
{
it->second->Start();
}
}
void LogAppenderManager::Stop()
{
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_mapLogAppender.begin();
for (; it != m_mapLogAppender.end(); ++it)
{
it->second->Stop();
}
}
void LogAppenderManager::AddEvent(const LogEvent::ptr &event)
{
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_mapLogAppender.begin();
for (; it != m_mapLogAppender.end(); ++it)
{
it->second->AddEvent(event);
}
}
void LogAppender::Start()
{
m_isRun = true;
std::thread([this]()
{
Run();
}).detach();
}
void LogAppender::Stop()
{
m_isRun = false;
m_cv.notify_all();
std::unique_lock<std::mutex> lock(m_mutex);
m_cv.wait(lock);
}
inline void LogAppender::AddEvent(const LogEvent::ptr &event)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_listEvent.push_back(event);
m_cv.notify_one();
}
void LogAppender::Run()
{
while (m_isRun)
{
std::unique_lock<std::mutex> lock(m_mutex);
m_cv.wait_for(lock, std::chrono::milliseconds(1000));
while (!m_listEvent.empty())
{
/// 取当前第一个元素
LogEvent::ptr event = m_listEvent.front();
m_listEvent.pop_front();
log(event);
}
}
m_cv.notify_all();
}
Logger::Logger()
{
/// 创建输出管理
m_LogAppenderManager = std::make_shared<LogAppenderManager>();
/// 创建控制台输出器 //默认就使用控制台输出
LogAppender::ptr p(new StdoutLogAppender());
m_LogAppenderManager->AddAppender(OUT_MODEL::OUT_CONSOLE, p);
/// 启动输出
m_LogAppenderManager->Start();
Init("");
}
std::shared_ptr<Logger> & Logger::Instance()
{
static std::shared_ptr<Logger> instance; // 使用shared_ptr还是unique_ptr,哪个好一点
static std::once_flag s_flag;
std::call_once(s_flag, [&]()
{
instance.reset(new Logger); /// 默认构建的是类T的无参构造函数
});
return instance;
}
Logger::~Logger()
{
m_LogAppenderManager->Stop();
}
void Logger::Init(const std::string & p_cfgFile)
{
// 停止输出
m_LogAppenderManager->Stop();
/// 配置属性
std::string strFileName = "license.log";
std::string strFilePath = "./log";
unsigned int nFileSize = 10; /// 单位 MB
bool bFileOverwrite = false; /// true表示超过设置大小后对日志文件进行覆盖写,丢弃旧的日志
bool bOutFile = true;
bool bOutSocket = false;
bool bOutConsole = true;
struct stat buffer;
if (stat("./log", &buffer) != 0)
{
size_t nPre = 0, nPos;
std::string strDir;
int nMdret;
if (strFilePath[strFilePath.size() - 1] != '/')
{
// force trailing / so we can handle everything in loop
strFilePath += '/';
}
while ((nPos = strFilePath.find_first_of('/', nPre)) != std::string::npos)
{
strDir = strFilePath.substr(0, nPos++);
nPre = nPos;
if (strDir.size() == 0)
continue; // if leading / first time is 0 length
if ((nMdret = ::mkdir(strDir.c_str(), 0755)) && errno != EEXIST)
{
;
}
}
}
输出配置
if (!bOutConsole) // 用户配置文件不需要输出到控制台
{
// 因为配置文件中去掉了控制台的输出,所以这个地方要把构造函数中添加的Appender去掉
m_LogAppenderManager->RemoveAppender(OUT_MODEL::OUT_CONSOLE);
}
if (bOutFile)
{
std::string strFile = strFilePath + '/' + strFileName;
std::replace(strFile.begin(), strFile.end(), '\\', '/');
/// 去除多余的'/'
int nPos1 = 0;
while (true)
{
nPos1 = strFile.find('/', nPos1);
if (nPos1 < 0)
{
break;
}
int nPos2 = strFile.find('/', nPos1 + 1);
if (nPos1 == nPos2 - 1) /// 相邻
{
strFile.erase(nPos1, 1);
}
++nPos1;
}
/// 创建控制台输出器
LogAppender::ptr p(new FileLogAppender(strFile, nFileSize, bFileOverwrite));
m_LogAppenderManager->AddAppender(OUT_MODEL::OUT_FILE, p);
}
// 启动输出
m_LogAppenderManager->Start();
}
void Logger::AddEvent(const LogEvent::ptr &p)
{
m_LogAppenderManager->AddEvent(p);
}
LogEventWrap::LogEventWrap(LogEvent::ptr e)
:m_event(e)
{
}
LogEventWrap::~LogEventWrap()
{
m_event->SetContent(m_event->GetSS().str());
Logger::Instance()->AddEvent(m_event);
}
}
main.cpp
#include "ELog.h"
int main()
{
for (int i = 0; i < 10; i++)
{
log_info << "AsyncLogging info." << 100;
log_debug << "AsyncLogging info." << 159;
log_error << "AsyncLogging info." << 258;
log_warn << "AsyncLogging info." << 369;
}
return 0;
}