tianzhendong
11 months ago
commit
d4d18d81a0
6 changed files with 483 additions and 0 deletions
@ -0,0 +1,7 @@ |
|||
project(MyLogger) |
|||
|
|||
add_library(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/loghandler.cpp) |
|||
|
|||
target_link_libraries(${PROJECT_NAME} |
|||
Qt5::Core |
|||
) |
@ -0,0 +1,7 @@ |
|||
INCLUDEPATH += $$PWD/ |
|||
|
|||
#在release下输出debug信息 |
|||
DEFINES += QT_MESSAGELOGCONTEXT |
|||
|
|||
SOURCES += \ |
|||
$$PWD/loghandler.cpp |
@ -0,0 +1,68 @@ |
|||
## 概述 |
|||
|
|||
- Singleton.h |
|||
|
|||
懒汉模式的单例模板类 |
|||
|
|||
- loghandler.h/cpp |
|||
|
|||
日志类 |
|||
|
|||
- MyLogger.pri |
|||
|
|||
子模块qt pri文件 |
|||
|
|||
## 使用 |
|||
|
|||
### 克隆代码 |
|||
|
|||
克隆代码并复制到工程Libs目录下 |
|||
|
|||
### 添加子工程 |
|||
|
|||
在主工程pro文件中,添加: |
|||
|
|||
```pro |
|||
include($$PWD/Libs/MyLogger/MyLogger.pri) |
|||
``` |
|||
|
|||
### 使用 |
|||
|
|||
#### 日志使用 |
|||
|
|||
```c++ |
|||
#include <QApplication> |
|||
//头文件 |
|||
#include "loghandler.h" |
|||
int main(int argc, char *argv[]) |
|||
{ |
|||
QApplication a(argc, argv); |
|||
//安装 |
|||
LogHandler::Get().installMessageHandler(); |
|||
Debug() << "Hello"; |
|||
qDebug() << "当前时间是: " << QTime::currentTime().toString("hh:mm:ss"); |
|||
qInfo() << QString("God bless you!"); |
|||
//卸载 |
|||
LogHandler::Get().uninstallMessageHandler(); |
|||
return a.exec(); |
|||
} |
|||
``` |
|||
|
|||
|
|||
|
|||
#### 单例模板类使用 |
|||
|
|||
```c++ |
|||
#include "Singleton.h" |
|||
class AppConfig : public QObject |
|||
{ |
|||
Q_OBJECT |
|||
//通过宏 |
|||
SINGLETON(AppConfig); |
|||
..... |
|||
} |
|||
|
|||
``` |
|||
|
|||
|
|||
|
@ -0,0 +1,91 @@ |
|||
#ifndef SINGLETON_H |
|||
#define SINGLETON_H |
|||
|
|||
#include <QMutex> |
|||
#include <QScopedPointer> |
|||
|
|||
////////////////////////////////////////////////////////////////////////////////
|
|||
/// ///
|
|||
/// Singleton signature ///
|
|||
/// ///
|
|||
////////////////////////////////////////////////////////////////////////////////
|
|||
/**
|
|||
* 使用方法: |
|||
* 1. 定义类为单例: |
|||
* class ConnectionPool { |
|||
* SINGLETON(ConnectionPool) // Here
|
|||
* public: |
|||
* |
|||
* 2. 实现无参构造函数,析构函数 |
|||
* 3. 获取单例类的对象: |
|||
* Singleton<ConnectionPool>::getInstance(); |
|||
* ConnectionPool &pool = Singleton<ConnectionPool>::getInstance(); |
|||
* 注意: 如果单例的类需要释放的资源和 Qt 底层的信号系统有关系,例如 QSettings,QSqlDatabase 等, |
|||
* 需要在程序结束前手动释放(也就是在 main() 函数返回前调用释放资源的函数), |
|||
* 否则有可能在程序退出时报系统底层的信号错误,导致如 QSettings 的数据没有保存。 |
|||
*/ |
|||
template <typename T> |
|||
class Singleton |
|||
{ |
|||
public: |
|||
static T& getInstance() |
|||
{ |
|||
static T instance; |
|||
return instance; |
|||
}; |
|||
|
|||
Singleton(T &&) = delete; |
|||
Singleton(const T &) = delete; |
|||
void operator = (const T &) = delete; |
|||
T *operator &() = delete; |
|||
|
|||
private: |
|||
Singleton() = default; |
|||
virtual ~Singleton() = default; |
|||
// static QMutex mutex;
|
|||
// static QScopedPointer<T> instance;
|
|||
}; |
|||
|
|||
////////////////////////////////////////////////////////////////////////////////
|
|||
/// ///
|
|||
/// Singleton definition ///
|
|||
/// ///
|
|||
////////////////////////////////////////////////////////////////////////////////
|
|||
#if 0 |
|||
//template <typename T> QMutex Singleton<T>::mutex;
|
|||
//template <typename T> QScopedPointer<T> Singleton<T>::instance;
|
|||
|
|||
//template <typename T>
|
|||
//T& Singleton<T>::getInstance()
|
|||
//{
|
|||
// if (instance.isNull())
|
|||
// {
|
|||
// mutex.lock();
|
|||
// if (instance.isNull())
|
|||
// {
|
|||
// instance.reset(new T());
|
|||
// }
|
|||
// mutex.unlock();
|
|||
// }
|
|||
|
|||
// return *instance.data();
|
|||
//}
|
|||
#endif |
|||
|
|||
////////////////////////////////////////////////////////////////////////////////
|
|||
/// ///
|
|||
/// Singleton Macro ///
|
|||
/// ///
|
|||
////////////////////////////////////////////////////////////////////////////////
|
|||
#if 0 |
|||
//#define SINGLETON(Class) \ |
|||
//private: \ |
|||
// Class(); \ |
|||
// ~Class(); \ |
|||
// Class(const Class &other); \ |
|||
// Class& operator=(const Class &other); \ |
|||
// friend class Singleton<Class>; \ |
|||
// friend struct QScopedPointerDeleter<Class>;
|
|||
#endif |
|||
#define SINGLETON(Class) friend class Singleton<Class> |
|||
#endif // SINGLETON_H
|
@ -0,0 +1,245 @@ |
|||
#include "loghandler.h" |
|||
|
|||
#include <QCoreApplication> |
|||
|
|||
// 初始化 static 变量
|
|||
QMutex LogHandlerPrivate::logMutex; |
|||
QFile* LogHandlerPrivate::logFile = nullptr; |
|||
QTextStream* LogHandlerPrivate::logOut = nullptr; |
|||
|
|||
LogHandlerPrivate::LogHandlerPrivate() |
|||
{ |
|||
auto localApplicationDirPath = QCoreApplication::applicationDirPath(); |
|||
logDir.setPath(localApplicationDirPath + "/log"); // TODO: 日志文件夹的路径,为 exe 所在目录下的 log 文件夹,可从配置文件读取
|
|||
QString logPath = logDir.absoluteFilePath("today.log"); // 获取日志的路径
|
|||
|
|||
// ========获取日志文件创建的时间========
|
|||
// QFileInfo::created(): On most Unix systems, this function returns the time of the last status change.
|
|||
// 所以不能运行时使用这个函数检查创建时间,因为会在运行时变化,于是在程序启动时保存下日志文件的最后修改时间,
|
|||
logFileCreatedDate = QFileInfo(logPath).lastModified().date(); // 若日志文件不存在,返回nullptr
|
|||
|
|||
// 打开日志文件,如果不是当天创建的,备份已有日志文件
|
|||
openAndBackupLogFile(); |
|||
|
|||
// 十分钟检查一次日志文件创建时间
|
|||
renameLogFileTimer.setInterval(1000 * 10); // TODO: 可从配置文件读取
|
|||
renameLogFileTimer.start(); |
|||
QObject::connect(&renameLogFileTimer, &QTimer::timeout, [this] |
|||
{ |
|||
QMutexLocker locker(&LogHandlerPrivate::logMutex); |
|||
openAndBackupLogFile(); // 打开日志文件
|
|||
checkLogFiles(); // 检测当前日志文件大小
|
|||
autoDeleteLog(); // 自动删除30天前的日志
|
|||
}); |
|||
|
|||
// 定时刷新日志输出到文件,尽快的能在日志文件里看到最新的日志
|
|||
flushLogFileTimer.setInterval(1000); // TODO: 可从配置文件读取
|
|||
flushLogFileTimer.start(); |
|||
QObject::connect(&flushLogFileTimer, &QTimer::timeout, [] |
|||
{ |
|||
// qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); // 测试不停的写入内容到日志文件
|
|||
QMutexLocker locker(&LogHandlerPrivate::logMutex); |
|||
if (nullptr != logOut) |
|||
{ |
|||
logOut->flush(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
LogHandlerPrivate::~LogHandlerPrivate() |
|||
{ |
|||
if (nullptr != logFile) |
|||
{ |
|||
logFile->flush(); |
|||
logFile->close(); |
|||
delete logOut; |
|||
delete logFile; |
|||
|
|||
// 因为他们是 static 变量
|
|||
logOut = nullptr; |
|||
logFile = nullptr; |
|||
} |
|||
} |
|||
|
|||
// 打开日志文件 log.txt,如果不是当天创建的,则使用创建日期把其重命名为 yyyy-MM-dd.log,并重新创建一个 log.txt
|
|||
void LogHandlerPrivate::openAndBackupLogFile() |
|||
{ |
|||
// 总体逻辑:
|
|||
// 1. 程序启动时 logFile 为 nullptr,初始化 logFile,有可能是同一天打开已经存在的 logFile,所以使用 Append 模式
|
|||
// 2. logFileCreatedDate is nullptr, 说明日志文件在程序开始时不存在,所以记录下创建时间
|
|||
// 3. 程序运行时检查如果 logFile 的创建日期和当前日期不相等,则使用它的创建日期重命名,然后再生成一个新的 log.txt 文件
|
|||
// 4. 检查日志文件超过 LOGLIMIT_NUM 个,删除最早的
|
|||
// 备注:log.txt 始终为当天的日志文件,当第二天,会执行第3步,将使用 log.txt 的创建日期重命名它
|
|||
|
|||
// 如果日志所在目录不存在,则创建
|
|||
if (!logDir.exists()) |
|||
{ |
|||
logDir.mkpath("."); // 可以递归的创建文件夹
|
|||
} |
|||
QString logPath = logDir.absoluteFilePath("today.log"); // log.txt的路径
|
|||
|
|||
// [[1]] 程序每次启动时 logFile 为 nullptr
|
|||
if (logFile == nullptr) |
|||
{ |
|||
logFile = new QFile(logPath); |
|||
logOut = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) ? new QTextStream(logFile) : nullptr; |
|||
if (logOut != nullptr) |
|||
logOut->setCodec("UTF-8"); |
|||
|
|||
// [[2]] 如果文件是第一次创建,则创建日期是无效的,把其设置为当前日期
|
|||
if (logFileCreatedDate.isNull()) |
|||
{ |
|||
logFileCreatedDate = QDate::currentDate(); |
|||
} |
|||
} |
|||
|
|||
// [[3]] 程序运行时如果创建日期不是当前日期,则使用创建日期重命名,并生成一个新的 log.txt
|
|||
if (logFileCreatedDate != QDate::currentDate()) |
|||
{ |
|||
logFile->flush(); |
|||
logFile->close(); |
|||
delete logOut; |
|||
delete logFile; |
|||
|
|||
QString newLogPath = logDir.absoluteFilePath(logFileCreatedDate.toString("yyyy-MM-dd.log"));; |
|||
QFile::copy(logPath, newLogPath); // Bug: 按理说 rename 会更合适,但是 rename 时最后一个文件总是显示不出来,需要 killall Finder 后才出现
|
|||
QFile::remove(logPath); // 删除重新创建,改变创建时间
|
|||
|
|||
// 重新创建 log.txt
|
|||
logFile = new QFile(logPath); |
|||
logOut = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) ? new QTextStream(logFile) : nullptr; |
|||
logFileCreatedDate = QDate::currentDate(); |
|||
if (logOut != nullptr) |
|||
logOut->setCodec("UTF-8"); |
|||
} |
|||
} |
|||
|
|||
// 检测当前日志文件大小
|
|||
void LogHandlerPrivate::checkLogFiles() |
|||
{ |
|||
// 如果 protocal.log 文件大小超过5M,重新创建一个日志文件,原文件存档为yyyy-MM-dd_hhmmss.log
|
|||
if (logFile->size() > 1024 * g_logLimitSize) |
|||
{ |
|||
logFile->flush(); |
|||
logFile->close(); |
|||
delete logOut; |
|||
delete logFile; |
|||
|
|||
QString logPath = logDir.absoluteFilePath("today.log"); // 日志的路径
|
|||
QString newLogPath = logDir.absoluteFilePath(logFileCreatedDate.toString("yyyy-MM-dd.log")); |
|||
QFile::copy(logPath, newLogPath); // Bug: 按理说 rename 会更合适,但是 rename 时最后一个文件总是显示不出来,需要 killall Finder 后才出现
|
|||
QFile::remove(logPath); // 删除重新创建,改变创建时间
|
|||
|
|||
logFile = new QFile(logPath); |
|||
logOut = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) ? new QTextStream(logFile) : NULL; |
|||
logFileCreatedDate = QDate::currentDate(); |
|||
if (logOut != nullptr) |
|||
logOut->setCodec("UTF-8"); |
|||
} |
|||
} |
|||
|
|||
// 自动删除30天前的日志
|
|||
void LogHandlerPrivate::autoDeleteLog() |
|||
{ |
|||
QDateTime now = QDateTime::currentDateTime(); |
|||
|
|||
// 前30天
|
|||
QDateTime dateTime1 = now.addDays(-30); |
|||
QDateTime dateTime2; |
|||
|
|||
QString logPath = logDir.absoluteFilePath("today.log"); // 日志的路径
|
|||
QDir dir(logPath); |
|||
QFileInfoList fileList = dir.entryInfoList(); |
|||
foreach (QFileInfo f, fileList ) |
|||
{ |
|||
// "."和".."跳过
|
|||
if (f.baseName() == "" || f.baseName() == "today" || f.baseName() == "today.log") |
|||
continue; |
|||
|
|||
dateTime2 = QDateTime::fromString(f.baseName(), "yyyy-MM-dd"); |
|||
if (dateTime2 < dateTime1) // 只要日志时间小于前30天的时间就删除
|
|||
{ |
|||
dir.remove(f.absoluteFilePath()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 消息处理函数
|
|||
void LogHandlerPrivate::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) |
|||
{ |
|||
QMutexLocker locker(&LogHandlerPrivate::logMutex); |
|||
QString level; |
|||
|
|||
switch (type) |
|||
{ |
|||
case QtDebugMsg: |
|||
level = "DEBUG"; |
|||
break; |
|||
case QtInfoMsg: |
|||
level = "INFO "; |
|||
break; |
|||
case QtWarningMsg: |
|||
level = "WARN "; |
|||
break; |
|||
case QtCriticalMsg: |
|||
level = "ERROR"; |
|||
break; |
|||
case QtFatalMsg: |
|||
level = "FATAL"; |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
|
|||
// 输出到标准输出: Windows 下 std::cout 使用 GB2312,而 msg 使用 UTF-8,但是程序的 Local 也还是使用 UTF-8
|
|||
#if defined(Q_OS_WIN) |
|||
QByteArray localMsg = QTextCodec::codecForName("GB2312")->fromUnicode(msg); //msg.toLocal8Bit();
|
|||
#else |
|||
QByteArray localMsg = msg.toLocal8Bit(); |
|||
#endif |
|||
|
|||
// std::cout << std::string(localMsg) << std::endl;
|
|||
// qSetMessagePattern("%{appname} %{type} %{time [yyyy-MM-dd hh:mm:ss]} %{file} %{line} %{function} %{message}");
|
|||
|
|||
|
|||
if (nullptr == LogHandlerPrivate::logOut) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// 输出到日志文件, 格式: 时间 - [Level] (文件名:行数, 函数): 消息
|
|||
QString fileName = context.file; |
|||
int index = fileName.lastIndexOf(QDir::separator()); |
|||
fileName = fileName.mid(index + 1); |
|||
QString msgOut = QString("%1 - [%2] (%3:%4, %5): %6") |
|||
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")).arg(level) |
|||
.arg(fileName).arg(context.line).arg(context.function).arg(msg); |
|||
std::cout << msgOut.toLocal8Bit().constData() << std::endl; |
|||
(*LogHandlerPrivate::logOut) << msgOut << "\n"; |
|||
} |
|||
|
|||
LogHandler::LogHandler() : d(nullptr) |
|||
{ |
|||
} |
|||
|
|||
// 给Qt安装消息处理函数
|
|||
void LogHandler::installMessageHandler() |
|||
{ |
|||
QMutexLocker locker(&LogHandlerPrivate::logMutex); // 类似C++11的lock_guard,析构时自动解锁
|
|||
|
|||
if (nullptr == d) |
|||
{ |
|||
d = new LogHandlerPrivate(); |
|||
qInstallMessageHandler(LogHandlerPrivate::messageHandler); // 给 Qt 安装自定义消息处理函数
|
|||
} |
|||
} |
|||
|
|||
// 取消安装消息处理函数并释放资源
|
|||
void LogHandler::uninstallMessageHandler() |
|||
{ |
|||
QMutexLocker locker(&LogHandlerPrivate::logMutex); |
|||
|
|||
qInstallMessageHandler(nullptr); |
|||
delete d; |
|||
d = nullptr; |
|||
} |
@ -0,0 +1,65 @@ |
|||
#ifndef LOGHANDLER_H |
|||
#define LOGHANDLER_H |
|||
|
|||
#include <iostream> |
|||
#include <QDebug> |
|||
#include <QDateTime> |
|||
#include <QMutexLocker> |
|||
#include <QDir> |
|||
#include <QFile> |
|||
#include <QFileInfo> |
|||
#include <QTimer> |
|||
#include <QTextStream> |
|||
#include <QTextCodec> |
|||
#include "Singleton.h" |
|||
|
|||
//#define log_d(format,...) qDebug(format,__VA_ARGS__)
|
|||
//#define log_i(format,...) qInfo(format,__VA_ARGS__)
|
|||
//#define log_w(format,...) qWarning(format,__VA_ARGS__)
|
|||
//#define log_e(format,...) qCritical(format,__VA_ARGS__)
|
|||
//#define log_f(format,...) qFatal(format,__VA_ARGS__)
|
|||
|
|||
const int g_logLimitSize = 5; |
|||
|
|||
struct LogHandlerPrivate |
|||
{ |
|||
LogHandlerPrivate(); |
|||
~LogHandlerPrivate(); |
|||
// qDebug()
|
|||
// 打开日志文件 log.txt,如果日志文件不是当天创建的,则使用创建日期把其重命名为 yyyy-MM-dd.log,并重新创建一个 log.txt
|
|||
void openAndBackupLogFile(); |
|||
void checkLogFiles(); // 检测当前日志文件大小
|
|||
void autoDeleteLog(); // 自动删除30天前的日志
|
|||
|
|||
// 消息处理函数
|
|||
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg); |
|||
|
|||
QDir logDir; // 日志文件夹
|
|||
QTimer renameLogFileTimer; // 重命名日志文件使用的定时器
|
|||
QTimer flushLogFileTimer; // 刷新输出到日志文件的定时器
|
|||
QDate logFileCreatedDate; // 日志文件创建的时间
|
|||
|
|||
static QFile *logFile; // 日志文件
|
|||
static QTextStream *logOut; // 输出日志的 QTextStream,使用静态对象就是为了减少函数调用的开销
|
|||
static QMutex logMutex; // 同步使用的 mutex
|
|||
}; |
|||
|
|||
class LogHandler |
|||
{ |
|||
public: |
|||
void installMessageHandler(); // 给Qt安装消息处理函数
|
|||
void uninstallMessageHandler(); // 取消安装消息处理函数并释放资源
|
|||
|
|||
static LogHandler& Get() |
|||
{ |
|||
static LogHandler m_logHandler; |
|||
return m_logHandler; |
|||
} |
|||
|
|||
private: |
|||
LogHandler(); |
|||
|
|||
LogHandlerPrivate *d; |
|||
}; |
|||
|
|||
#endif // LOGHANDLER_H
|
Loading…
Reference in new issue