在应用中,需要记录程序运行过程中的一些关键信息以及异常输出等。这些信息用来排查程序故障或者其他用途。
日志模块可以自己实现或者是借用第三方库,之前写过一个类似的使用Qt的打印重定向将打印输出到文件:Qt将打印信息输出到文件_qt log输出到文件-CSDN博客
第三方日志库有plog、glog、spdlog、log4qt等等。主要介绍以下plog和spdlog这两个只需要包含对应文件而不需要编译生成库的第三方日志模块。
PLOG
下载链接:Releases · SergiusTheBest/plog · GitHub
下载之后直接引入对应文件即可,写一个简单的例子:
#include "mainwindow.h"
#include "plog/Appenders/RollingFileAppender.h"
#include "plog/Formatters/TxtFormatter.h"
#include "plog/Initializers/ConsoleInitializer.h"
#include "plog/Log.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
plog::RollingFileAppender<plog::TxtFormatter> file_logger("app.log",
1000000, 3);
plog::ColorConsoleAppender<plog::TxtFormatter> console_logger;
plog::init(plog::debug, &file_logger).addAppender(&console_logger);
PLOGD << "Debug message";
PLOGI << "Info message";
PLOGW << "Warning message";
PLOGE << "Error message";
MainWindow w;
w.show();
return a.exec();
}
函数plog::RollingFileAppender 时,需要提供三个参数,这些参数决定了日志文件的滚动和格式化方式。下面介绍对应三个参数的含义:
第一个参数是日志文件的名称,这是一个字符串,用于指定要写入的日志文件的名称。例如,我设设置的日志文件名是app.log。
第二个参数是日志文件的大小限制。当日志文件的大小达到这个限制时,Plog 将自动滚动日志文件并创建新的日志文件。这个大小通常以字节为单位,例如 1000000 表示 1MB。
第三个参数是滚动文件的数量限制。当日志文件达到大小限制并滚动时,Plog 将保留多少个滚动文件。例如,如果将其设置为 3,则在滚动后将保留最多 3 个滚动文件,旧的滚动文件将被删除。
函数plog::ColorConsoleAppender将日志以彩色形式打印到控制台进行输出。
plog::init(plog::debug, &file_logger).addAppender(&console_logger)进行日志初始化,将日志等级设置为debug。编译运行查看:
对应日志打印到了控制台。然后查看是否有日志文件生成:
查看日志文件内容:
SPDLOG
下载链接:GitHub - gabime/spdlog: Fast C++ logging library.
spdlog与plog一样,只需要包含对应文件即可无需额外编译。
写一个简单的例子:
#include "mainwindow.h"
#include "spdlog/spdlog.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/stdout_sinks.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
auto console_logger = spdlog::stdout_logger_mt("console_logger");
auto file_logger = spdlog::basic_logger_mt("file_logger", "log.txt");
console_logger->info("console_logger info");
file_logger->error("file_logger error");
MainWindow w;
w.show();
return a.exec();
}
初始化了两个logger,console_logger将会打印到控制台,file_logger将会将日志内容生成到文件log.txt中,编译运行:
console_logger已经将日志打印到控制台,查看是否有日志文件生成以及日志文件中是否有内容:
查看log.txt却是空的:
关闭程序之后log.txt中有文本生成:
需要设置日志刷新
file_logger->flush_on(spdlog::level::debug);
这样设置之后日志级别在debug及以上的日志都会实施刷新在文件中。另外日志显示的格式也是可以通过set_pattern设置的,例如我是这样设置的
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e][%t][%s %! %#]%v");
即[时间][线程id][文件名 函数 行号]日志内容。
可以看到文件名、函数、行号没有正确打印,如果要正确打印这三者需要用到对应宏SPDLOG_LOGGER:
SPDLOG_LOGGER_INFO(console_logger,"console_logger info");
SPDLOG_LOGGER_ERROR(file_logger,"file_logger error");
可以看到文件名、函数、行号有正常打印:
简单封装一下。
头文件:
#ifndef LOG_H
#define LOG_H
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/spdlog.h"
#define LOGD(...) \
SPDLOG_LOGGER_DEBUG(Log::instance().GetDebugLogger(), __VA_ARGS__);
#define LOGI(...) \
SPDLOG_LOGGER_INFO(Log::instance().GetInfoLogger(), __VA_ARGS__);
#define LOGW(...) \
SPDLOG_LOGGER_WARN(Log::instance().GetWarnLogger(), __VA_ARGS__);
#define LOGE(...) \
SPDLOG_LOGGER_ERROR(Log::instance().GetErrorLogger(), __VA_ARGS__);
class Log {
private:
std::shared_ptr<spdlog::logger> m_DebugLogger;
std::shared_ptr<spdlog::logger> m_InfoLogger;
std::shared_ptr<spdlog::logger> m_WarnLogger;
std::shared_ptr<spdlog::logger> m_ErrorLogger;
public:
static Log &instance();
auto GetDebugLogger() -> decltype(m_DebugLogger) { return m_DebugLogger; }
auto GetInfoLogger() -> decltype(m_InfoLogger) { return m_InfoLogger; }
auto GetWarnLogger() -> decltype(m_WarnLogger) { return m_WarnLogger; }
auto GetErrorLogger() -> decltype(m_ErrorLogger) { return m_ErrorLogger; }
void init(const std::string &fileName);
private:
Log();
};
#endif // LOG_H
源文件:
#include "log.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"
Log &Log::instance() {
static Log log;
return log;
}
void Log::init(const std::string &fileName) {
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e][%t][%s %! %#]%v");
m_DebugLogger =
spdlog::basic_logger_mt("debug_logger", fileName + "_debug.log");
m_DebugLogger->set_level(spdlog::level::debug);
m_DebugLogger->flush_on(spdlog::level::debug);
m_InfoLogger =
spdlog::basic_logger_mt("info_logger", fileName + "_info.log");
m_InfoLogger->set_level(spdlog::level::info);
m_InfoLogger->flush_on(spdlog::level::info);
m_WarnLogger =
spdlog::basic_logger_mt("warn_logger", fileName + "_warn.log");
m_WarnLogger->set_level(spdlog::level::warn);
m_WarnLogger->flush_on(spdlog::level::warn);
m_ErrorLogger =
spdlog::basic_logger_mt("error_logger", fileName + "_error.log");
m_ErrorLogger->set_level(spdlog::level::err);
m_ErrorLogger->flush_on(spdlog::level::err);
}
Log::Log() {}
调用示例:
#include "log.h"
#include "mainwindow.h"
#include <QApplication>
#include <QDateTime>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
Log::instance().init(
QString("log/%1_%2")
.arg("Test")
.arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz"))
.toStdString());
LOGD("debug");
LOGI("info");
LOGW("warn");
LOGE("error");
MainWindow w;
w.show();
return a.exec();
}
注意修改spdlog源码中的这部分:
每次程序启动后调用init初始化日志后都会生成对应四个日志文件。
最后贴一个纯C++实现的简单的日志功能:
#ifndef LOG_H
#define LOG_H
#include <ctime>
#include <fstream>
#include <iostream>
#include <mutex>
#include <sstream>
#include <string>
// 日志级别
enum class LogLevel { DEBUG, INFO, WARNING, ERROR };
// 日志类
class Logger {
public:
Logger() {
// 打开日志文件
logFile.open("app.log", std::ios::app);
}
~Logger() {
// 关闭日志文件
if (logFile.is_open()) { logFile.close(); }
}
// 日志接口
void Log(const std::string &message, LogLevel level) {
std::lock_guard<std::mutex> lock(mtx); // 线程安全
// 获取当前时间
std::time_t now = std::time(nullptr);
char buf[100] = {0};
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S",
std::localtime(&now));
// 构造日志消息
std::ostringstream logStream;
logStream << "[" << buf << "] [" << ToString(level) << "] " << message
<< std::endl;
// 输出到控制台
std::cout << logStream.str();
// 输出到文件
if (logFile.is_open()) { logFile << logStream.str(); }
}
private:
std::ofstream logFile; // 日志文件
std::mutex mtx; // 互斥锁
// 将日志级别转换为字符串
std::string ToString(LogLevel level) {
switch (level) {
case LogLevel::DEBUG: return "DEBUG";
case LogLevel::INFO: return "INFO";
case LogLevel::WARNING: return "WARNING";
case LogLevel::ERROR: return "ERROR";
default: return "UNKNOWN";
}
}
};
// 日志类的单例
class LoggerSingleton {
public:
static Logger &GetInstance() {
static Logger instance; // 实例化一个Logger对象
return instance;
}
// 删除拷贝构造函数和赋值操作
LoggerSingleton(const LoggerSingleton &) = delete;
LoggerSingleton &operator=(const LoggerSingleton &) = delete;
private:
LoggerSingleton() {} // 私有构造函数
};
// 定义宏简化日志调用
#define LOG_DEBUG(msg) LoggerSingleton::GetInstance().Log(msg, LogLevel::DEBUG)
#define LOG_INFO(msg) LoggerSingleton::GetInstance().Log(msg, LogLevel::INFO)
#define LOG_WARNING(msg) \
LoggerSingleton::GetInstance().Log(msg, LogLevel::WARNING)
#define LOG_ERROR(msg) LoggerSingleton::GetInstance().Log(msg, LogLevel::ERROR)
#endif // LOG_H
调用示例:
#include "log.h"
#include <iostream>
using namespace std;
int main() {
// 记录不同级别的日志
LOG_DEBUG("This is a debug message.");
LOG_INFO("This is an info message.");
LOG_WARNING("This is a warning message.");
LOG_ERROR("This is an error message.");
cout << "Hello World!" << endl;
return 0;
}