在Logger.h中定义首先需要定义日志的级别,我将其定义成枚举类型:
enum LogLevel
{
kTrace, // 最详细的跟踪信息(调试阶段使用)
kDebug, // 调试信息(开发人员排查问题)
kInfo, // 正常运行的信息(记录关键流程)
kWarn, // 警告(非错误但需注意的情况)
kError, // 错误(功能失败但程序继续运行)
kMaxNumOfLogLevel, // 总级别数(非实际日志级别)
};
随后设置Logger类,这个类需要实现:1、设置当前的日志等级SetLogLevel。2、获取日志等级。3、写日志。
Logger.h
#pragma once
#include <iostream>
#include "NonCopyable.h"
namespace tmms
{
namespace base
{
enum LogLevel
{
kTrace,
kDebug,
kInfo,
kWarn,
kError,
kMaxNumOfLogLevel,
};
class Logger:public NonCopyable
{
public:
Logger() = default;
~Logger() = default;
void SetLogLevel(const LogLevel &level);
LogLevel GetLogLevel() const;
void Write( const std::string &msg);
private:
LogLevel level_{kDebug};
};
}
}
无法复制或赋值Logger
对象,避免多实例导致的资源竞争。
Logger.cpp
#include "Logger.h"
#include <iostream>
using namespace tmms::base;
void Logger::SetLogLevel(const LogLevel &level){
level_ = level;
}
LogLevel Logger::GetLogLevel()const{
return level_;
}
void Logger::Write(const std::string &msg){
std::cout << msg;
}
在LogStream中实现Stream的拼接用于格式化输出日志,日志中包含有[时间][线程ID][文件名:行号][函数名]
LogStream.h
#pragma once
#include "Logger.h"
#include <sstream>
namespace tmms
{
namespace base
{
extern Logger * g_logger;
class LogStream
{
public:
LogStream(Logger* logger, const char* file, int line, LogLevel l, const char *func);
~LogStream();
// 模板实现必须放在头文件中
template<class T>
LogStream& operator<<(const T& value)
{
stream_<< value;
return *this;
}
private:
std::ostringstream stream_;
Logger *logger_{nullptr};
};
}
}
// 修正日志宏定义(添加缺失的__func__参数)
#define LOG_TRACE LogStream(tmms::base::g_logger, __FILE__, __LINE__, tmms::base::kTrace, __func__)
#define LOG_DEBUG LogStream(tmms::base::g_logger, __FILE__, __LINE__, tmms::base::kDebug, __func__)
#define LOG_INFO LogStream(tmms::base::g_logger, __FILE__, __LINE__, tmms::base::kInfo, __func__)
#define LOG_WARN LogStream(tmms::base::g_logger, __FILE__, __LINE__, tmms::base::kWarn, __func__)
#define LOG_ERROR LogStream(tmms::base::g_logger, __FILE__, __LINE__, tmms::base::kError, __func__)
LogStream.cpp
#include "LogStream.h"
#include <string>
#include "TTime.h"
#include <unistd.h>
#include <sys/syscall.h>
#include <cstring>
using namespace tmms::base;
Logger* tmms::base::g_logger = nullptr;
static thread_local pid_t thread_id = 0;
const char *log_string[]{
};
LogStream::LogStream(Logger* logger, const char* file, int line, LogLevel l, const char *func)
:logger_(logger)
{
if(l < tmms::base::g_logger->GetLogLevel())
return;
// 初始化时清空流缓冲区
stream_.str("");
stream_.clear();
// 统一格式:[时间][线程ID][文件名:行号][函数名]
stream_ << "[" << TTime::ISOTime() << "]"; // 时间戳
// 获取线程ID
if(thread_id == 0){
thread_id = static_cast<pid_t>(syscall(SYS_gettid));
}
stream_ << "[TID:" << thread_id << "]"; // 线程信息
// 文件名处理
const char * file_name = strrchr(file,'/');
file_name = file_name ? file_name + 1 : file;
stream_ << "[" << file_name << ":" << line << "]"; // 源码位置
// 函数名处理
if(func){
stream_ << "[FN:" << func << "]";
}
// 日志内容前缀
stream_ << " ";
}
LogStream::~LogStream()
{
stream_ << "\n"; // 添加换行
logger_->Write(stream_.str());
}
对这个代码的测试可以
#include "LogStream.h"
#include "Logger.h"
using namespace tmms::base;
void TestLog()
{
LOG_TRACE << "test trace!!!";
LOG_DEBUG << "test debug!!!";
LOG_INFO << "test info!!!";
LOG_WARN << "test warn!!!"; // 修正宏名大小写
LOG_ERROR << "test error!!!";
}
int main (int argc,const char **argv){
tmms::base::g_logger = new Logger();
tmms::base::g_logger->SetLogLevel(kTrace);
//tmms::base::g_logger->SetLogLevel(kWarn);
TestLog();
return 0;
}
在创建Logger实例子之后设置需要记录的日志等级,随后测试日志系统。
注意,在运行我的代码的时候需要保证写一个不能多次实例化的NonCopyable.h
#pragma once
namespace tmms
{
namespace base
{
class NonCopyable
{
protected:
NonCopyable() = default;
~NonCopyable() = default;
private:
NonCopyable(const NonCopyable &) = delete;
NonCopyable &operator=(const NonCopyable &) = delete;
};
}
}
以及一个关于事件的库TTime.cpp
#include "TTime.h"
using namespace tmms::base;
#include <sys/time.h>
int64_t TTime::NowMS()
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec*1000+tv.tv_usec/1000;
}
int64_t TTime::Now()
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec;
}
int64_t TTime::Now(int &year,int &month,int&day,int &hour,int&minute,int&second)
{
struct tm tm;
time_t t = time(NULL);
localtime_r(&t, &tm);
year = tm.tm_year + 1900;
month = tm.tm_mon + 1;
day = tm.tm_mday;
hour = tm.tm_hour;
minute = tm.tm_min;
second = tm.tm_sec;
return t;
}
std::string TTime::ISOTime()
{
struct timeval tv;
struct tm tm;
gettimeofday(&tv, NULL);
time_t t = time(NULL);
localtime_r(&t, &tm);
char buf[128] = {0};
auto n = sprintf(buf, "%4d-%02d-%02dT%02d:%02d:%02d",
tm.tm_year + 1900,
tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
return std::string(buf,buf+n);
}
TTime.h
#pragma once
#include <cstdint>
#include <string>
namespace tmms
{
namespace base
{
class TTime
{
public:
static int64_t NowMS();
static int64_t Now();
static int64_t Now(int &year,int &month,int&day,int &hour,int&minute,int&second);
static std::string ISOTime();
};
}
}
时间的库主要是为了获取当前时间,记录日志的开始时间。