什么是日志(Log)?
1. 日志的定义
日志(Log)是程序运行时记录的重要信息,通常用于调试、监控和故障排查。它可以帮助开发者了解程序的运行状态,分析错误,甚至用于安全审计。
2. 常见的日志级别
级别 | 描述 |
---|---|
DEBUG | 详细信息,仅在调试时使用 |
INFO | 关键信息,如程序启动、结束等 |
WARNING | 警告信息,不影响运行,但需要注意 |
ERROR | 发生错误,可能影响功能 |
FATAL | 严重错误,程序可能崩溃 |
实现日志类
思路
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] - hello world
我们想实现一个日志类,是这样的,helloworld是我们手动输入的,前面的日志信息,是日志类自己做的,DEBUG是我们传进去的参数,我们来实现一下这个类。
首先日志类,我们看到要实现两种打印方式,一种是直接打印在屏幕上,一种是打印在文件当中,由于是两种模式,所以第一时间想到的肯定是虚类,我们创建一个虚类,然后内部定义一个打印方式,打印方式的函数我们定义为虚函数,我们在创建一个打印在屏幕上的类,创建一个打印在文件当中的类,两个类继承虚类,然后实现打印方式。
为了防止和系统当中的命名冲突,所以我们使用命名空间
namespace lyrics
{
}
输出方式的实现
虚类:
// 策略模式
class Strategy_Pattern
{
public:
// 使用系统默认的析构函数
virtual ~Strategy_Pattern() = default;
// 纯虚函数,需要基类实现
virtual void Log_refresh_mode(const std::string &message) = 0;
};
打印在屏幕上:
// 刷新到屏幕上
class Refresh_to_screen : public Strategy_Pattern
{
public:
Refresh_to_screen()
{
int n = pthread_mutex_init(&_mutex, nullptr);
}
~Refresh_to_screen()
{
pthread_mutex_destroy(&_mutex);
}
void Log_refresh_mode(const std::string &message)
{
pthread_mutex_lock(&_mutex);
std::cout << message << std::endl;
pthread_mutex_unlock(&_mutex);
}
private:
pthread_mutex_t _mutex;
};
因为屏幕是公共资源,所以需要加锁。
打印在文件当中:
const std::string Default_Path = "./log/";
const std::string Default_Name = "Log.txt";
// 刷新到文件中
class Refresh_to_file : public Strategy_Pattern
{
public:
// 初始化函数用于初始化
Refresh_to_file(const std::string &Path = Default_Path, const std::string &Name = Default_Name)
: _Default_Path(Path), _Default_Name(Name)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_mutex_lock(&_mutex);
// 需要检查文件是否创建
if (std::filesystem::exists(_Default_Path)) return;
try
{
std::filesystem::create_directories(_Default_Path);
}
catch (std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << "\n";
}
pthread_mutex_unlock(&_mutex);
}
void Log_refresh_mode(const std::string &message)
{
pthread_mutex_lock(&_mutex);
std::string log = _Default_Path + _Default_Name;
std::ofstream out(log, std::ios::app);
if (!out.is_open()) return;
out << message << std::endl;
out.close();
pthread_mutex_unlock(&_mutex);
}
~Refresh_to_file()
{
pthread_mutex_destroy(&_mutex);
}
private:
pthread_mutex_t _mutex;
std::string _Default_Path; // 默认路径
std::string _Default_Name; // 默认文件名
};
我们这里引入了C++17的新特性,也就是filesystem,filesystem提供了文件的一系列操作。
日志类
上面我们已经实现了打印方式,接下来我们直接封装Log类即可:
// 日志类
class Log
{
public:
private:
std::shared_ptr<Strategy_Pattern> _Strategy;
};
这里成员变量肯定是打印方式,构造函数用于初始化成员变量,我们的默认打印方式是打印在屏幕上:
下面实现的三个方法分别是调用打印方式。
// 默认刷新方式
Log()
{
// 刷新到屏幕上
_Strategy = std::make_shared<Refresh_to_screen>();
}
// 刷新到文件当中
void RefreshToFile()
{
_Strategy = std::make_shared<Refresh_to_file>();
}
// 刷先到屏幕上
void RefreshToScreen()
{
_Strategy = std::make_shared<Refresh_to_screen>();
}
我们还需要定义一个内部类,用于构建整条Log信息:
class LogMessage
{
public:
// 初始化信息
LogMessage(std::string filename,LogLevel level,int line,Log &logger)
:_filename(filename),_line(line),_loglevel(level),
_currtime(GetCurrtime()),_pid(getpid()),_logger(logger)
{
std::stringstream ssbuffer;
ssbuffer << "[" << _currtime << "] "
<< "[" << LogLevelToStr(_loglevel) << "] "
<< "[" << _pid << "] "
<< "[" << _filename << "] "
<< "[" << _line << "] - ";
_loginfo = ssbuffer.str();
}
template<class T>
LogMessage& operator<<(const T& data)
{
std::stringstream ss;
ss << data;
_loginfo += ss.str();
return *this;
}
~LogMessage()
{
if(_logger._Strategy)
{
_logger._Strategy->Log_refresh_mode(_loginfo);
}
}
private:
std::string _filename; // 文件名
LogLevel _loglevel; // 日志等级
std::string _currtime; // 当前时间
pid_t _pid; // 进程pid
int _line; // 行号
std::string _loginfo; //完整的日志信息
Log &_logger; //日志
};
重载()运算符
//operator()重载----传递文件还有行号,构建临时对象返回拷贝
LogMessage operator()(LogLevel level,const std::string filename,int line)
{
return LogMessage(filename,level,line,*this);
}
这个函数主要是为了我们调用起来比较方便。
Log整体:
class Log
{
public:
class LogMessage
{
public:
// 初始化信息
LogMessage(std::string filename,LogLevel level,int line,Log &logger)
:_filename(filename),_line(line),_loglevel(level),
_currtime(GetCurrtime()),_pid(getpid()),_logger(logger)
{
std::stringstream ssbuffer;
ssbuffer << "[" << _currtime << "] "
<< "[" << LogLevelToStr(_loglevel) << "] "
<< "[" << _pid << "] "
<< "[" << _filename << "] "
<< "[" << _line << "] - ";
_loginfo = ssbuffer.str();
}
template<class T>
LogMessage& operator<<(const T& data)
{
std::stringstream ss;
ss << data;
_loginfo += ss.str();
return *this;
}
~LogMessage()
{
if(_logger._Strategy)
{
_logger._Strategy->Log_refresh_mode(_loginfo);
}
}
private:
std::string _filename; // 文件名
LogLevel _loglevel; // 日志等级
std::string _currtime; // 当前时间
pid_t _pid; // 进程pid
int _line; // 行号
std::string _loginfo; //完整的日志信息
Log &_logger; //日志
};
public:
// 默认刷新方式
Log()
{
// 刷新到屏幕上
_Strategy = std::make_shared<Refresh_to_screen>();
}
//operator()重载----传递文件还有行号,构建临时对象返回拷贝
LogMessage operator()(LogLevel level,const std::string filename,int line)
{
return LogMessage(filename,level,line,*this);
}
// 刷新到文件当中
void RefreshToFile()
{
_Strategy = std::make_shared<Refresh_to_file>();
}
// 刷先到屏幕上
void RefreshToScreen()
{
_Strategy = std::make_shared<Refresh_to_screen>();
}
~Log() {}
private:
std::shared_ptr<Strategy_Pattern> _Strategy;
};
Log logger;
接下来我们定义一个宏:
Log logger;
#define LOG(Level) logger(Level, __FILE__, __LINE__)
运行结果
总结
通过封装日志类,我们为应用程序引入了高效、灵活且可扩展的日志记录机制。日志类不仅能帮助我们记录程序运行过程中的关键信息,还能在发生错误时提供调试和故障排除的依据。通过合理的日志等级管理、日志输出格式和日志文件的滚动机制,日志类能够有效地优化程序的调试过程和问题追踪。
在设计日志类时,我们确保了线程安全性、高效的日志写入以及灵活的配置,使得日志系统能够适应不同应用场景的需求。无论是开发阶段的调试,还是生产环境中的错误监控,日志类都能为开发者提供强大的支持。
封装日志类不仅提升了程序的可维护性和可扩展性,还能够增强团队协作时的信息共享和问题追溯的效率。通过这个日志类的封装,我们可以更好地管理应用程序中的日志数据,使系统在长时间运行时保持高效、稳定。
总的来说,日志类的封装为我们的应用程序提供了更加专业和系统的日志管理方案,不仅提升了开发效率,也提高了软件的可维护性和健壮性。