Browse Source

v1.0.0

master
tianzhendong 10 months ago
commit
d4d18d81a0
  1. 7
      CMakeLists.txt
  2. 7
      MyLogger.pri
  3. 68
      README.md
  4. 91
      Singleton.h
  5. 245
      loghandler.cpp
  6. 65
      loghandler.h

7
CMakeLists.txt

@ -0,0 +1,7 @@
project(MyLogger)
add_library(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/loghandler.cpp)
target_link_libraries(${PROJECT_NAME}
Qt5::Core
)

7
MyLogger.pri

@ -0,0 +1,7 @@
INCLUDEPATH += $$PWD/
#在release下输出debug信息
DEFINES += QT_MESSAGELOGCONTEXT
SOURCES += \
$$PWD/loghandler.cpp

68
README.md

@ -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);
.....
}
```

91
Singleton.h

@ -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 QSettingsQSqlDatabase
* ( 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

245
loghandler.cpp

@ -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;
}

65
loghandler.h

@ -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…
Cancel
Save